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