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