a4765ee89230d12bcab996170cf940b1aad03b8a
[oweals/gnunet.git] / src / dht / test_dht_topo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file dht/test_dht_topo.c
22  *
23  * @brief Test for the dht service: store and retrieve in various topologies.
24  * Each peer stores it own ID in the DHT and then a different peer tries to
25  * retrieve that key from it. The GET starts after a first round of PUTS has
26  * been made. Periodically, each peer stores its ID into the DHT. If after
27  * a timeout no result has been returned, the test fails.
28  */
29 #include "platform.h"
30 #include "gnunet_testing_lib.h"
31 #include "gnunet_dht_service.h"
32
33 #define REMOVE_DIR GNUNET_YES
34
35 /**
36  * DIFFERENT TESTS TO RUN
37  */
38 #define LINE 0
39 #define TORUS 1
40
41 /**
42  * How long until we give up on connecting the peers?
43  */
44 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1500)
45
46 #define GET_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 120)
47
48 #define PUT_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
49
50 /**
51  * Result of the test.
52  */
53 static int ok;
54
55 /**
56  * Total number of peers in the test.
57  */
58 static unsigned long long num_peers;
59
60 /**
61  * Global configuration file
62  */
63 static struct GNUNET_CONFIGURATION_Handle *testing_cfg;
64
65 /**
66  * Total number of currently running peers.
67  */
68 static unsigned long long peers_running;
69
70 /**
71  * Total number of connections in the whole network.
72  */
73 static unsigned int total_connections;
74
75 /**
76  * The currently running peer group.
77  */
78 static struct GNUNET_TESTING_PeerGroup *pg;
79
80 /**
81  * File to report results to.
82  */
83 static struct GNUNET_DISK_FileHandle *output_file;
84
85 /**
86  * File to log connection info, statistics to.
87  */
88 static struct GNUNET_DISK_FileHandle *data_file;
89
90 /**
91  * Task called to disconnect peers.
92  */
93 static GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
94
95 /**
96  * Task To perform tests
97  */
98 static GNUNET_SCHEDULER_TaskIdentifier test_task;
99
100 /**
101  * Task to do DHT_puts
102  */
103 static GNUNET_SCHEDULER_TaskIdentifier put_task;
104
105 /**
106  * Task called to shutdown test.
107  */
108 static GNUNET_SCHEDULER_TaskIdentifier shutdown_handle;
109
110 static char *topology_file;
111
112 static struct GNUNET_DHT_Handle **hs;
113
114 static struct GNUNET_DHT_GetHandle *get_h;
115
116 static struct GNUNET_DHT_GetHandle *get_h_2;
117
118 static struct GNUNET_DHT_GetHandle *get_h_far;
119
120 static int found_1;
121
122 static int found_2;
123
124 static int found_far;
125
126 /**
127  * Which topology are we to run
128  */
129 static int test_topology;
130
131
132 /**
133  * Check whether peers successfully shut down.
134  */
135 static void
136 shutdown_callback (void *cls, const char *emsg)
137 {
138   if (emsg != NULL)
139   {
140     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Shutdown of peers failed!\n");
141     ok++;
142   }
143   else
144   {
145     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "All peers successfully shut down!\n");
146   }
147   GNUNET_CONFIGURATION_destroy (testing_cfg);
148 }
149
150
151 static void
152 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
153 {
154   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Ending test.\n");
155
156   if (disconnect_task != GNUNET_SCHEDULER_NO_TASK)
157   {
158     GNUNET_SCHEDULER_cancel (disconnect_task);
159     disconnect_task = GNUNET_SCHEDULER_NO_TASK;
160   }
161
162   if (data_file != NULL)
163     GNUNET_DISK_file_close (data_file);
164   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
165 }
166
167
168 static void
169 disconnect_peers (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
170 {
171   unsigned int i;
172
173   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "disconnecting peers\n");
174   disconnect_task = GNUNET_SCHEDULER_NO_TASK;
175   GNUNET_SCHEDULER_cancel (put_task);
176   if (NULL != get_h)
177     GNUNET_DHT_get_stop (get_h);
178   if (NULL != get_h_2)
179     GNUNET_DHT_get_stop (get_h_2);
180   if (NULL != get_h_far)
181     GNUNET_DHT_get_stop (get_h_far);
182   for (i = 0; i < num_peers; i++)
183   {
184     GNUNET_DHT_disconnect (hs[i]);
185   }
186   GNUNET_SCHEDULER_cancel (shutdown_handle);
187   shutdown_handle = GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
188 }
189
190
191 static void
192 dht_get_id_handler (void *cls, struct GNUNET_TIME_Absolute exp,
193                     const struct GNUNET_HashCode * key,
194                     const struct GNUNET_PeerIdentity *get_path,
195                     unsigned int get_path_length,
196                     const struct GNUNET_PeerIdentity *put_path,
197                     unsigned int put_path_length, enum GNUNET_BLOCK_Type type,
198                     size_t size, const void *data)
199 {
200   int i;
201
202   if (sizeof (struct GNUNET_HashCode) == size)
203   {
204     const struct GNUNET_HashCode *h = data;
205
206     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  Contents: %s\n",
207                 GNUNET_h2s_full (h));
208
209   }
210   else
211   {
212     GNUNET_break(0);
213   }
214   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "PATH: (get %u, put %u)\n",
215               get_path_length, put_path_length);
216   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  LOCAL\n");
217   for (i = get_path_length - 1; i >= 0; i--)
218   {
219     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  %s\n",
220                 GNUNET_i2s (&get_path[i]));
221   }
222   for (i = put_path_length - 1; i >= 0; i--)
223   {
224     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  %s\n",
225                 GNUNET_i2s (&put_path[i]));
226   }
227   switch ((long)cls)
228   {
229     case 1:
230       found_1++;
231       GNUNET_log (GNUNET_ERROR_TYPE_INFO, "FOUND 1!\n");
232       break;
233     case 2:
234       found_2++;
235       GNUNET_log (GNUNET_ERROR_TYPE_INFO, "FOUND 2!\n");
236       break;
237     case 3:
238       found_far++;
239       GNUNET_log (GNUNET_ERROR_TYPE_INFO, "FOUND FAR!\n");
240       break;
241     default:
242       GNUNET_break(0);
243   }
244   if ( (TORUS == test_topology) &&
245        ( (found_1 == 0) || (found_2 == 0) || (found_far == 0)) )
246     return;
247   ok = 0;
248   GNUNET_SCHEDULER_cancel (disconnect_task);
249   disconnect_task = GNUNET_SCHEDULER_add_now (&disconnect_peers, NULL);
250 }
251
252
253 static void
254 do_test (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
255 {
256   struct GNUNET_TESTING_Daemon *d;
257   struct GNUNET_TESTING_Daemon *d2;
258   struct GNUNET_TESTING_Daemon *d_far;
259   struct GNUNET_TESTING_Daemon *o;
260   struct GNUNET_TESTING_Daemon *aux;
261   const char *id_aux;
262   const char *id_origin = "FC74";
263   const char *id_near = "9P6V";
264   const char *id_near2 = "2GDS";
265   const char *id_far = "KPST";
266   unsigned int i;
267
268   d = d2 = d_far = o = NULL;
269   found_1 = found_2 = found_far = 0;
270   if (LINE == test_topology)
271   {
272     o = GNUNET_TESTING_daemon_get (pg, 0);
273     d = GNUNET_TESTING_daemon_get (pg, num_peers - 1);
274   }
275   else if (TORUS == test_topology)
276   {
277     for (i = 0; i < num_peers; i++)
278     {
279       aux = GNUNET_TESTING_daemon_get (pg, i);
280       id_aux = GNUNET_i2s (&aux->id);
281       if (strcmp (id_aux, id_origin) == 0)
282         o = aux;
283       if (strcmp (id_aux, id_far) == 0)
284         d_far = aux;
285       if (strcmp (id_aux, id_near) == 0)
286         d = aux;
287       if (strcmp (id_aux, id_near2) == 0)
288         d2 = aux;
289     }
290     if ((NULL == o) || (NULL == d) || (NULL == d2) || (NULL == d_far))
291     {
292       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
293                   "Peers not found (hostkey file changed?)\n");
294       GNUNET_SCHEDULER_cancel (disconnect_task);
295       disconnect_task = GNUNET_SCHEDULER_add_now (&disconnect_peers, NULL);
296       return;
297     }
298   }
299   else
300   {
301     GNUNET_assert (0);
302   }
303   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "test_task\nfrom %s\n",
304               GNUNET_h2s_full (&o->id.hashPubKey));
305   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  looking for %s\n",
306               GNUNET_h2s_full (&d->id.hashPubKey));
307   get_h = GNUNET_DHT_get_start (hs[0], 
308                                 GNUNET_BLOCK_TYPE_TEST, /* type */
309                                 &d->id.hashPubKey,      /*key to search */
310                                 4U,     /* replication level */
311                                 GNUNET_DHT_RO_RECORD_ROUTE | GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
312                                 NULL,        /* xquery */
313                                 0,      /* xquery bits */
314                                 &dht_get_id_handler, (void *)1);
315   if (TORUS == test_topology)
316   {
317     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  looking for %s\n",
318                 GNUNET_h2s_full (&d2->id.hashPubKey));
319     get_h_2 = GNUNET_DHT_get_start (hs[0],
320                                     GNUNET_BLOCK_TYPE_TEST,       /* type */
321                                     &d2->id.hashPubKey,   /*key to search */
322                                     4U,   /* replication level */
323                                     GNUNET_DHT_RO_RECORD_ROUTE | GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
324                                     NULL,      /* xquery */
325                                     0,    /* xquery bits */
326                                     &dht_get_id_handler, (void *)2);
327     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  looking for %s\n",
328                 GNUNET_h2s_full (&d_far->id.hashPubKey));
329     get_h_far = GNUNET_DHT_get_start (hs[0], 
330                                       GNUNET_BLOCK_TYPE_TEST,     /* type */
331                                       &d_far->id.hashPubKey,      /*key to search */
332                                       4U, /* replication level */
333                                       GNUNET_DHT_RO_RECORD_ROUTE | GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
334                                       NULL,    /* xquery */
335                                       0,  /* xquery bits */
336                                       &dht_get_id_handler, (void *)3);
337   }
338   GNUNET_SCHEDULER_cancel (disconnect_task);
339   disconnect_task =
340       GNUNET_SCHEDULER_add_delayed (GET_TIMEOUT, &disconnect_peers, NULL);
341 }
342
343 /**
344  * Task to put the id of each peer into the DHT.
345  * 
346  * @param cls Closure (unused)
347  * @param tc Task context
348  * 
349  */
350 static void
351 put_id (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
352 {
353   struct GNUNET_TESTING_Daemon *d;
354   unsigned int i;
355
356   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "putting id's in DHT\n");
357   for (i = 0; i < num_peers; i++)
358   {
359     d = GNUNET_TESTING_daemon_get (pg, i);
360     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "   putting into DHT: %s\n",
361                 GNUNET_h2s_full (&d->id.hashPubKey));
362     GNUNET_DHT_put (hs[i], &d->id.hashPubKey, 10U,
363                     GNUNET_DHT_RO_RECORD_ROUTE |
364                     GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
365                     GNUNET_BLOCK_TYPE_TEST, sizeof (struct GNUNET_PeerIdentity),
366                     (const char *) &d->id, GNUNET_TIME_UNIT_FOREVER_ABS,
367                     GNUNET_TIME_UNIT_FOREVER_REL, NULL, NULL);
368
369   }
370   put_task = GNUNET_SCHEDULER_add_delayed (PUT_FREQUENCY, &put_id, NULL);
371   if (GNUNET_SCHEDULER_NO_TASK == test_task)
372     test_task = GNUNET_SCHEDULER_add_now (&do_test, NULL);
373 }
374
375
376 /**
377  * peergroup_ready: start test when all peers are connected
378  * 
379  * @param cls closure
380  * @param emsg error message
381  * 
382  */
383 static void
384 peergroup_ready (void *cls, const char *emsg)
385 {
386   struct GNUNET_TESTING_Daemon *d;
387   char *buf;
388   int buf_len;
389   unsigned int i;
390
391   if (emsg != NULL)
392   {
393     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
394                 "Peergroup callback called with error, aborting test!\n");
395     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
396                 "Error from testing: `%s'\n",
397                 emsg);
398     ok++;
399     GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
400     return;
401   }
402   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
403               "Peer Group started successfully with %u connections\n",
404               total_connections);
405   if (data_file != NULL)
406   {
407     buf = NULL;
408     buf_len = GNUNET_asprintf (&buf, "CONNECTIONS_0: %u\n", total_connections);
409     if (buf_len > 0)
410       GNUNET_DISK_file_write (data_file, buf, buf_len);
411     GNUNET_free (buf);
412   }
413   peers_running = GNUNET_TESTING_daemons_running (pg);
414
415   GNUNET_assert (peers_running == num_peers);
416   hs = GNUNET_malloc (num_peers * sizeof (struct GNUNET_DHT_Handle *));
417   for (i = 0; i < num_peers; i++)
418   {
419     d = GNUNET_TESTING_daemon_get (pg, i);
420     hs[i] = GNUNET_DHT_connect (d->cfg, 32);
421   }
422
423   test_task = GNUNET_SCHEDULER_NO_TASK;
424   put_task = GNUNET_SCHEDULER_add_now (&put_id, NULL);
425   disconnect_task =
426       GNUNET_SCHEDULER_add_delayed (GET_TIMEOUT, &disconnect_peers, NULL);
427
428 }
429
430
431 /**
432  * Function that will be called whenever two daemons are connected by
433  * the testing library.
434  *
435  * @param cls closure
436  * @param first peer id for first daemon
437  * @param second peer id for the second daemon
438  * @param distance distance between the connected peers
439  * @param first_cfg config for the first daemon
440  * @param second_cfg config for the second daemon
441  * @param first_daemon handle for the first daemon
442  * @param second_daemon handle for the second daemon
443  * @param emsg error message (NULL on success)
444  */
445 static void
446 connect_cb (void *cls, const struct GNUNET_PeerIdentity *first,
447             const struct GNUNET_PeerIdentity *second, uint32_t distance,
448             const struct GNUNET_CONFIGURATION_Handle *first_cfg,
449             const struct GNUNET_CONFIGURATION_Handle *second_cfg,
450             struct GNUNET_TESTING_Daemon *first_daemon,
451             struct GNUNET_TESTING_Daemon *second_daemon, const char *emsg)
452 {
453   if (emsg == NULL)
454   {
455     total_connections++;
456     GNUNET_PEER_intern (first);
457     GNUNET_PEER_intern (second);
458   }
459   else
460   {
461     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
462                 "Problem with new connection (%s)\n", emsg);
463   }
464 }
465
466
467 /**
468  * run: load configuration options and schedule test to run (start peergroup)
469  * @param cls closure
470  * @param args argv
471  * @param cfgfile configuration file name (can be NULL)
472  * @param cfg configuration handle
473  */
474 static void
475 run (void *cls, char *const *args, const char *cfgfile,
476      const struct GNUNET_CONFIGURATION_Handle *cfg)
477 {
478   char *temp_str;
479   struct GNUNET_TESTING_Host *hosts;
480   char *data_filename;
481
482   ok = 1;
483   testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
484
485   GNUNET_log_setup ("test_dht_topo",
486                     "WARNING",
487                     NULL);
488   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
489   GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing_old",
490                                          "use_progressbars", "YES");
491   if (GNUNET_OK !=
492       GNUNET_CONFIGURATION_get_value_number (testing_cfg, "testing_old",
493                                              "num_peers", &num_peers))
494   {
495     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
496                 "Option TESTING:NUM_PEERS is required!\n");
497     return;
498   }
499
500   if (GNUNET_OK !=
501       GNUNET_CONFIGURATION_get_value_string (testing_cfg, "testing_old",
502                                              "topology_output_file",
503                                              &topology_file))
504   {
505     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
506                 "Option test_dht_topo:topology_output_file is required!\n");
507     return;
508   }
509
510   if (GNUNET_OK ==
511       GNUNET_CONFIGURATION_get_value_string (testing_cfg, "test_dht_topo",
512                                              "data_output_file",
513                                              &data_filename))
514   {
515     data_file =
516         GNUNET_DISK_file_open (data_filename,
517                                GNUNET_DISK_OPEN_READWRITE |
518                                GNUNET_DISK_OPEN_CREATE,
519                                GNUNET_DISK_PERM_USER_READ |
520                                GNUNET_DISK_PERM_USER_WRITE);
521     if (data_file == NULL)
522     {
523       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n",
524                   data_filename);
525       GNUNET_free (data_filename);
526     }
527   }
528
529   if (GNUNET_YES ==
530       GNUNET_CONFIGURATION_get_value_string (cfg, "test_dht_topo",
531                                              "output_file", &temp_str))
532   {
533     output_file =
534         GNUNET_DISK_file_open (temp_str,
535                                GNUNET_DISK_OPEN_READWRITE |
536                                GNUNET_DISK_OPEN_CREATE,
537                                GNUNET_DISK_PERM_USER_READ |
538                                GNUNET_DISK_PERM_USER_WRITE);
539     if (output_file == NULL)
540       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n",
541                   temp_str);
542   }
543   GNUNET_free_non_null (temp_str);
544
545   hosts = GNUNET_TESTING_hosts_load (testing_cfg);
546
547   pg = GNUNET_TESTING_peergroup_start (testing_cfg, num_peers, TIMEOUT,
548                                        &connect_cb, &peergroup_ready, NULL,
549                                        hosts);
550   GNUNET_assert (pg != NULL);
551   shutdown_handle =
552       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
553                                     &shutdown_task, NULL);
554 }
555
556
557 /**
558  * Main: start test
559  */
560 int
561 main (int xargc, char *xargv[])
562 {
563   static struct GNUNET_GETOPT_CommandLineOption options[] = {
564     GNUNET_GETOPT_OPTION_END
565   };
566   static char *const argv_torus[] = { "test-dht-2dtorus",
567     "-c",
568     "test_dht_2dtorus.conf",
569     NULL
570   };
571   static char *const argv_line[] = { "test-dht-line",
572     "-c",
573     "test_dht_line.conf",
574     NULL
575   };
576   char *const *argv;
577   int argc;
578   
579   if (strstr (xargv[0], "test_dht_2dtorus") != NULL)
580   {
581     argv = argv_torus;
582     argc = sizeof (argv_torus) / sizeof (char *);
583     test_topology = TORUS;
584   }
585   else if (strstr (xargv[0], "test_dht_line") != NULL)
586   {
587     argv = argv_line;
588     argc = sizeof (argv_line) / sizeof (char *);
589     test_topology = LINE;
590   }
591   else
592   {
593     GNUNET_break (0);
594     return 1;
595   }
596   GNUNET_PROGRAM_run (argc - 1, argv,
597                       xargv[0],
598                       gettext_noop ("Test dht in different topologies."),
599                       options,
600                       &run, NULL);
601 #if REMOVE_DIR
602   GNUNET_DISK_directory_remove ("/tmp/test_dht_topo");
603 #endif
604   if (0 == found_1)
605   {
606     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID 1 not found!\n");
607   }
608   if (TORUS == test_topology)
609   {
610     if (0 == found_2)
611     {
612       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID 2 not found!\n");
613     }
614     if (0 == found_far)
615     {
616       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID far not found!\n");
617     }
618   }
619   return ok;
620 }
621
622 /* end of test_dht_topo.c */