-dce
[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 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 (GNUNET_HashCode) == size)
203   {
204     const 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, 4);
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, NULL,        /* xquery */
312                                 0,      /* xquery bits */
313                                 &dht_get_id_handler, (void *)1);
314   if (TORUS == test_topology)
315   {
316     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  looking for %s\n",
317                 GNUNET_h2s_full (&d2->id.hashPubKey));
318     get_h_2 = GNUNET_DHT_get_start (hs[0],
319                                     GNUNET_BLOCK_TYPE_TEST,       /* type */
320                                     &d2->id.hashPubKey,   /*key to search */
321                                     4U,   /* replication level */
322                                     GNUNET_DHT_RO_RECORD_ROUTE | GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE, NULL,      /* xquery */
323                                     0,    /* xquery bits */
324                                     &dht_get_id_handler, (void *)2);
325     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "  looking for %s\n",
326                 GNUNET_h2s_full (&d_far->id.hashPubKey));
327     get_h_far = GNUNET_DHT_get_start (hs[0], 
328                                       GNUNET_BLOCK_TYPE_TEST,     /* type */
329                                       &d_far->id.hashPubKey,      /*key to search */
330                                       4U, /* replication level */
331                                       GNUNET_DHT_RO_RECORD_ROUTE | GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE, NULL,    /* xquery */
332                                       0,  /* xquery bits */
333                                       &dht_get_id_handler, (void *)3);
334   }
335   GNUNET_SCHEDULER_cancel (disconnect_task);
336   disconnect_task =
337       GNUNET_SCHEDULER_add_delayed (GET_TIMEOUT, &disconnect_peers, NULL);
338 }
339
340 /**
341  * Task to put the id of each peer into teh DHT.
342  * 
343  * @param cls Closure (unused)
344  * @param tc Task context
345  * 
346  */
347 static void
348 put_id (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
349 {
350   struct GNUNET_TESTING_Daemon *d;
351   unsigned int i;
352
353   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "putting id's in DHT\n");
354   for (i = 0; i < num_peers; i++)
355   {
356     d = GNUNET_TESTING_daemon_get (pg, i);
357     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "   putting into DHT: %s\n",
358                 GNUNET_h2s_full (&d->id.hashPubKey));
359     GNUNET_DHT_put (hs[i], &d->id.hashPubKey, 10U,
360                     GNUNET_DHT_RO_RECORD_ROUTE |
361                     GNUNET_DHT_RO_DEMULTIPLEX_EVERYWHERE,
362                     GNUNET_BLOCK_TYPE_TEST, sizeof (struct GNUNET_PeerIdentity),
363                     (const char *) &d->id, GNUNET_TIME_UNIT_FOREVER_ABS,
364                     GNUNET_TIME_UNIT_FOREVER_REL, NULL, NULL);
365
366   }
367   put_task = GNUNET_SCHEDULER_add_delayed (PUT_FREQUENCY, &put_id, NULL);
368   if (GNUNET_SCHEDULER_NO_TASK == test_task)
369     test_task = GNUNET_SCHEDULER_add_now (&do_test, NULL);
370 }
371
372
373 /**
374  * peergroup_ready: start test when all peers are connected
375  * 
376  * @param cls closure
377  * @param emsg error message
378  * 
379  */
380 static void
381 peergroup_ready (void *cls, const char *emsg)
382 {
383   struct GNUNET_TESTING_Daemon *d;
384   char *buf;
385   int buf_len;
386   unsigned int i;
387
388   if (emsg != NULL)
389   {
390     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
391                 "Peergroup callback called with error, aborting test!\n");
392     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
393                 "Error from testing: `%s'\n",
394                 emsg);
395     ok++;
396     GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
397     return;
398   }
399   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
400               "Peer Group started successfully with %u connections\n",
401               total_connections);
402   if (data_file != NULL)
403   {
404     buf = NULL;
405     buf_len = GNUNET_asprintf (&buf, "CONNECTIONS_0: %u\n", total_connections);
406     if (buf_len > 0)
407       GNUNET_DISK_file_write (data_file, buf, buf_len);
408     GNUNET_free (buf);
409   }
410   peers_running = GNUNET_TESTING_daemons_running (pg);
411
412   GNUNET_assert (peers_running == num_peers);
413   hs = GNUNET_malloc (num_peers * sizeof (struct GNUNET_DHT_Handle *));
414   for (i = 0; i < num_peers; i++)
415   {
416     d = GNUNET_TESTING_daemon_get (pg, i);
417     hs[i] = GNUNET_DHT_connect (d->cfg, 32);
418   }
419
420   test_task = GNUNET_SCHEDULER_NO_TASK;
421   put_task = GNUNET_SCHEDULER_add_now (&put_id, NULL);
422   disconnect_task =
423       GNUNET_SCHEDULER_add_delayed (GET_TIMEOUT, &disconnect_peers, NULL);
424
425 }
426
427
428 /**
429  * Function that will be called whenever two daemons are connected by
430  * the testing library.
431  *
432  * @param cls closure
433  * @param first peer id for first daemon
434  * @param second peer id for the second daemon
435  * @param distance distance between the connected peers
436  * @param first_cfg config for the first daemon
437  * @param second_cfg config for the second daemon
438  * @param first_daemon handle for the first daemon
439  * @param second_daemon handle for the second daemon
440  * @param emsg error message (NULL on success)
441  */
442 static void
443 connect_cb (void *cls, const struct GNUNET_PeerIdentity *first,
444             const struct GNUNET_PeerIdentity *second, uint32_t distance,
445             const struct GNUNET_CONFIGURATION_Handle *first_cfg,
446             const struct GNUNET_CONFIGURATION_Handle *second_cfg,
447             struct GNUNET_TESTING_Daemon *first_daemon,
448             struct GNUNET_TESTING_Daemon *second_daemon, const char *emsg)
449 {
450   if (emsg == NULL)
451   {
452     total_connections++;
453     GNUNET_PEER_intern (first);
454     GNUNET_PEER_intern (second);
455   }
456   else
457   {
458     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
459                 "Problem with new connection (%s)\n", emsg);
460   }
461 }
462
463
464 /**
465  * run: load configuration options and schedule test to run (start peergroup)
466  * @param cls closure
467  * @param args argv
468  * @param cfgfile configuration file name (can be NULL)
469  * @param cfg configuration handle
470  */
471 static void
472 run (void *cls, char *const *args, const char *cfgfile,
473      const struct GNUNET_CONFIGURATION_Handle *cfg)
474 {
475   char *temp_str;
476   struct GNUNET_TESTING_Host *hosts;
477   char *data_filename;
478
479   ok = 1;
480   testing_cfg = GNUNET_CONFIGURATION_dup (cfg);
481
482   GNUNET_log_setup ("test_dht_topo",
483                     "WARNING",
484                     NULL);
485   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting daemons.\n");
486   GNUNET_CONFIGURATION_set_value_string (testing_cfg, "testing",
487                                          "use_progressbars", "YES");
488   if (GNUNET_OK !=
489       GNUNET_CONFIGURATION_get_value_number (testing_cfg, "testing",
490                                              "num_peers", &num_peers))
491   {
492     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
493                 "Option TESTING:NUM_PEERS is required!\n");
494     return;
495   }
496
497   if (GNUNET_OK !=
498       GNUNET_CONFIGURATION_get_value_string (testing_cfg, "testing",
499                                              "topology_output_file",
500                                              &topology_file))
501   {
502     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
503                 "Option test_dht_topo:topology_output_file is required!\n");
504     return;
505   }
506
507   if (GNUNET_OK ==
508       GNUNET_CONFIGURATION_get_value_string (testing_cfg, "test_dht_topo",
509                                              "data_output_file",
510                                              &data_filename))
511   {
512     data_file =
513         GNUNET_DISK_file_open (data_filename,
514                                GNUNET_DISK_OPEN_READWRITE |
515                                GNUNET_DISK_OPEN_CREATE,
516                                GNUNET_DISK_PERM_USER_READ |
517                                GNUNET_DISK_PERM_USER_WRITE);
518     if (data_file == NULL)
519     {
520       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n",
521                   data_filename);
522       GNUNET_free (data_filename);
523     }
524   }
525
526   if (GNUNET_YES ==
527       GNUNET_CONFIGURATION_get_value_string (cfg, "test_dht_topo",
528                                              "output_file", &temp_str))
529   {
530     output_file =
531         GNUNET_DISK_file_open (temp_str,
532                                GNUNET_DISK_OPEN_READWRITE |
533                                GNUNET_DISK_OPEN_CREATE,
534                                GNUNET_DISK_PERM_USER_READ |
535                                GNUNET_DISK_PERM_USER_WRITE);
536     if (output_file == NULL)
537       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to open %s for output!\n",
538                   temp_str);
539   }
540   GNUNET_free_non_null (temp_str);
541
542   hosts = GNUNET_TESTING_hosts_load (testing_cfg);
543
544   pg = GNUNET_TESTING_peergroup_start (testing_cfg, num_peers, TIMEOUT,
545                                        &connect_cb, &peergroup_ready, NULL,
546                                        hosts);
547   GNUNET_assert (pg != NULL);
548   shutdown_handle =
549       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
550                                     &shutdown_task, NULL);
551 }
552
553
554 /**
555  * Main: start test
556  */
557 int
558 main (int xargc, char *xargv[])
559 {
560   static struct GNUNET_GETOPT_CommandLineOption options[] = {
561     GNUNET_GETOPT_OPTION_END
562   };
563   static char *const argv_torus[] = { "test-dht-2dtorus",
564     "-c",
565     "test_dht_2dtorus.conf",
566     NULL
567   };
568   static char *const argv_line[] = { "test-dht-line",
569     "-c",
570     "test_dht_line.conf",
571     NULL
572   };
573   char *const *argv;
574   int argc;
575   
576   if (strstr (xargv[0], "test_dht_2dtorus") != NULL)
577   {
578     argv = argv_torus;
579     argc = sizeof (argv_torus) / sizeof (char *);
580     test_topology = TORUS;
581   }
582   else if (strstr (xargv[0], "test_dht_line") != NULL)
583   {
584     argv = argv_line;
585     argc = sizeof (argv_line) / sizeof (char *);
586     test_topology = LINE;
587   }
588   else
589   {
590     GNUNET_break (0);
591     return 1;
592   }
593   GNUNET_PROGRAM_run (argc - 1, argv,
594                       xargv[0],
595                       gettext_noop ("Test dht in different topologies."),
596                       options,
597                       &run, NULL);
598 #if REMOVE_DIR
599   GNUNET_DISK_directory_remove ("/tmp/test_dht_topo");
600 #endif
601   if (0 == found_1)
602   {
603     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID 1 not found!\n");
604   }
605   if (TORUS == test_topology)
606   {
607     if (0 == found_2)
608     {
609       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID 2 not found!\n");
610     }
611     if (0 == found_far)
612     {
613       GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "ID far not found!\n");
614     }
615   }
616   return ok;
617 }
618
619 /* end of test_dht_topo.c */