bugfix
[oweals/gnunet.git] / src / dht / test_dht_multipeer.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 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_multipeer.c
22  * @brief testcase for testing DHT service with
23  *        multiple peers.
24  */
25 #include "platform.h"
26 #include "gnunet_testing_lib.h"
27 #include "gnunet_core_service.h"
28 #include "gnunet_dht_service.h"
29
30 /* DEFINES */
31 #define VERBOSE GNUNET_NO
32
33 /* Timeout for entire testcase */
34 #define TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_MINUTES, 30)
35
36 /* Timeout for waiting for replies to get requests */
37 #define GET_TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 60)
38
39 /* Timeout for waiting for gets to complete */
40 #define GET_DELAY GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_MILLISECONDS, 50)
41
42 /* Timeout for waiting for puts to complete */
43 #define PUT_DELAY GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_MILLISECONDS, 50)
44
45 /* If number of peers not in config file, use this number */
46 #define DEFAULT_NUM_PEERS 10
47
48 #define TEST_DATA_SIZE 8
49
50 #define MAX_OUTSTANDING_PUTS 10
51
52 #define MAX_OUTSTANDING_GETS 10
53
54 #define PATH_TRACKING GNUNET_YES
55
56
57
58 struct TestPutContext
59 {
60   /**
61    * This is a linked list
62    */
63   struct TestPutContext *next;
64
65   /**
66    * This is a linked list
67    */
68   struct TestPutContext *prev;
69
70   /**
71    * Handle to the first peers DHT service (via the API)
72    */
73   struct GNUNET_DHT_Handle *dht_handle;
74
75   /**
76    *  Handle to the PUT peer daemon
77    */
78   struct GNUNET_TESTING_Daemon *daemon;
79
80   /**
81    *  Identifier for this PUT
82    */
83   uint32_t uid;
84
85   /**
86    * Task handle for processing of the put.
87    */
88   GNUNET_SCHEDULER_TaskIdentifier task;
89 };
90
91
92 struct TestGetContext
93 {
94   /**
95    * This is a linked list 
96    */
97   struct TestGetContext *next;
98
99   /**
100    * This is a linked list 
101    */
102   struct TestGetContext *prev;
103
104   /**
105    * Handle to the first peers DHT service (via the API)
106    */
107   struct GNUNET_DHT_Handle *dht_handle;
108
109   /**
110    * Handle for the DHT get request
111    */
112   struct GNUNET_DHT_GetHandle *get_handle;
113
114   /**
115    *  Handle to the GET peer daemon
116    */
117   struct GNUNET_TESTING_Daemon *daemon;
118
119   /**
120    *  Identifier for this GET
121    */
122   uint32_t uid;
123
124   /**
125    * Task for disconnecting DHT handles (and stopping GET)
126    */
127   GNUNET_SCHEDULER_TaskIdentifier task;
128
129   /**
130    * Whether or not this request has been fulfilled already.
131    */
132   int succeeded;
133 };
134
135
136 /**
137  * List of GETS to perform
138  */
139 static struct TestGetContext *all_gets_head;
140
141 /**
142  * List of GETS to perform
143  */
144 static struct TestGetContext *all_gets_tail;
145
146 /**
147  * List of PUTS to perform
148  */
149 static struct TestPutContext *all_puts_head;
150
151 /**
152  * List of PUTS to perform
153  */
154 static struct TestPutContext *all_puts_tail;
155
156 /**
157  * Handle to the set of all peers run for this test.
158  */
159 static struct GNUNET_TESTING_PeerGroup *pg;
160
161 /**
162  * Total number of peers to run, set based on config file.
163  */
164 static unsigned long long num_peers;
165
166 /**
167  * How many puts do we currently have in flight?
168  */
169 static unsigned long long outstanding_puts;
170
171 /**
172  * How many puts are done?
173  */
174 static unsigned long long puts_completed;
175
176 /**
177  * How many puts do we currently have in flight?
178  */
179 static unsigned long long outstanding_gets;
180
181 /**
182  * How many gets are done?
183  */
184 static unsigned long long gets_completed;
185
186 /**
187  * How many gets failed?
188  */
189 static unsigned long long gets_failed;
190
191 /**
192  * Directory to remove on shutdown.
193  */
194 static char *test_directory;
195
196 /**
197  * Option to use when routing.
198  */
199 static enum GNUNET_DHT_RouteOption route_option;
200
201 /**
202  * Task handle to use to schedule test failure / success.
203  */
204 static GNUNET_SCHEDULER_TaskIdentifier die_task;
205
206 /**
207  * Global return value (0 for success, anything else for failure)
208  */
209 static int ok;
210
211
212 /**
213  * Check whether peers successfully shut down.
214  */
215 static void
216 shutdown_callback (void *cls, const char *emsg)
217 {
218   if (emsg != NULL)
219   {
220     fprintf (stderr,
221              "Failed to shutdown testing topology: %s\n",
222              emsg);
223     if (ok == 0)
224       ok = 2;
225   }
226 }
227
228
229 /**
230  * Function scheduled to be run on the successful completion of this
231  * testcase.
232  */
233 static void
234 finish_testing (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
235 {
236   struct TestPutContext *test_put;
237   struct TestGetContext *test_get;
238
239   die_task = GNUNET_SCHEDULER_NO_TASK;
240   while (NULL != (test_put = all_puts_head))
241   {
242     if (test_put->task != GNUNET_SCHEDULER_NO_TASK)
243       GNUNET_SCHEDULER_cancel (test_put->task);
244     if (test_put->dht_handle != NULL)
245       GNUNET_DHT_disconnect (test_put->dht_handle);
246     GNUNET_CONTAINER_DLL_remove (all_puts_head,
247                                  all_puts_tail,
248                                  test_put);
249     GNUNET_free (test_put);
250   }
251
252   while (NULL != (test_get = all_gets_head))
253   {
254     if (test_get->task != GNUNET_SCHEDULER_NO_TASK)
255       GNUNET_SCHEDULER_cancel (test_get->task);
256     if (test_get->get_handle != NULL)
257       GNUNET_DHT_get_stop (test_get->get_handle);
258     if (test_get->dht_handle != NULL)
259       GNUNET_DHT_disconnect (test_get->dht_handle);
260     GNUNET_CONTAINER_DLL_remove (all_gets_head,
261                                  all_gets_tail,
262                                  test_get);
263     GNUNET_free (test_get);
264   }
265
266   ok = 0;
267   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
268   pg = NULL;
269 }
270
271
272 /**
273  * Check if the get_handle is being used, if so stop the request.  Either
274  * way, schedule the end_badly_cont function which actually shuts down the
275  * test.
276  */
277 static void
278 end_badly (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
279 {
280   const char *emsg = cls;
281   struct TestPutContext *test_put;
282   struct TestGetContext *test_get;
283
284   die_task = GNUNET_SCHEDULER_NO_TASK;
285   fprintf (stderr, 
286            "Failing test with error: `%s'!\n",
287            emsg);
288   while (NULL != (test_put = all_puts_head))
289   {
290     if (test_put->task != GNUNET_SCHEDULER_NO_TASK)
291       GNUNET_SCHEDULER_cancel (test_put->task);
292     if (test_put->dht_handle != NULL)
293       GNUNET_DHT_disconnect (test_put->dht_handle);
294     GNUNET_CONTAINER_DLL_remove (all_puts_head,
295                                  all_puts_tail,
296                                  test_put);
297     GNUNET_free (test_put);
298   }
299
300   while (NULL != (test_get = all_gets_head))
301   {
302     if (test_get->task != GNUNET_SCHEDULER_NO_TASK)
303       GNUNET_SCHEDULER_cancel (test_get->task);
304     if (test_get->get_handle != NULL)
305       GNUNET_DHT_get_stop (test_get->get_handle);
306     if (test_get->dht_handle != NULL)
307       GNUNET_DHT_disconnect (test_get->dht_handle);
308     GNUNET_CONTAINER_DLL_remove (all_gets_head,
309                                  all_gets_tail,
310                                  test_get);
311     GNUNET_free (test_get);
312   }
313   ok = 1;
314   GNUNET_TESTING_daemons_stop (pg, TIMEOUT, &shutdown_callback, NULL);
315   pg = NULL;
316 }
317
318
319 /**
320  * Task to release get handle.
321  */
322 static void
323 get_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
324 {
325   struct TestGetContext *test_get = cls;
326   GNUNET_HashCode search_key;   /* Key stored under */
327   char original_data[TEST_DATA_SIZE];   /* Made up data to store */
328
329   test_get->task = GNUNET_SCHEDULER_NO_TASK;
330   memset (original_data, test_get->uid, sizeof (original_data));
331   GNUNET_CRYPTO_hash (original_data, TEST_DATA_SIZE, &search_key);
332   if (test_get->succeeded != GNUNET_YES)
333   {
334     gets_failed++;
335     fprintf (stderr,
336              "Get from peer %s for key %s failed!\n",
337              GNUNET_i2s (&test_get->daemon->id), 
338              GNUNET_h2s (&search_key));
339   }
340   GNUNET_assert (test_get->get_handle != NULL);
341   GNUNET_DHT_get_stop (test_get->get_handle);
342   test_get->get_handle = NULL;
343
344   outstanding_gets--;           /* GET is really finished */
345   GNUNET_DHT_disconnect (test_get->dht_handle);
346   test_get->dht_handle = NULL;
347
348   fprintf (stderr,
349            "%llu gets succeeded, %llu gets failed!\n",
350            gets_completed, gets_failed);
351   GNUNET_CONTAINER_DLL_remove (all_gets_head,
352                                all_gets_tail,
353                                test_get);
354   GNUNET_free (test_get);
355
356   if ((gets_failed > 0) && (outstanding_gets == 0))       /* Had some failures */
357   {
358     GNUNET_SCHEDULER_cancel (die_task);
359     die_task = GNUNET_SCHEDULER_add_now (&end_badly, "not all gets succeeded");
360     return;
361   }
362
363   if ( (gets_completed == num_peers * num_peers) && 
364        (outstanding_gets == 0) )  /* All gets successful */
365   {
366     GNUNET_SCHEDULER_cancel (die_task);
367     die_task = GNUNET_SCHEDULER_add_now (&finish_testing, NULL);
368   }
369 }
370
371
372 /**
373  * Iterator called if the GET request initiated returns a response.
374  *
375  * @param cls closure
376  * @param exp when will this value expire
377  * @param key key of the result
378  * @param type type of the result
379  * @param size number of bytes in data
380  * @param data pointer to the result data
381  */
382 static void
383 get_result_iterator (void *cls, struct GNUNET_TIME_Absolute exp,
384                      const GNUNET_HashCode * key,
385                      const struct GNUNET_PeerIdentity *get_path,
386                      unsigned int get_path_length,
387                      const struct GNUNET_PeerIdentity *put_path,
388                      unsigned int put_path_length,
389                      enum GNUNET_BLOCK_Type type, size_t size, const void *data)
390 {
391   struct TestGetContext *test_get = cls;
392   GNUNET_HashCode search_key;   /* Key stored under */
393   char original_data[TEST_DATA_SIZE];   /* Made up data to store */
394   unsigned int i;
395
396   memset (original_data, test_get->uid, sizeof (original_data));
397   GNUNET_CRYPTO_hash (original_data, TEST_DATA_SIZE, &search_key);
398   if (test_get->succeeded == GNUNET_YES)
399     return;                     /* Get has already been successful, probably ending now */
400
401 #if PATH_TRACKING
402   if (put_path != NULL)
403   {
404     fprintf (stderr, "PUT (%u) Path: ",
405              test_get->uid);
406     for (i = 0; i<put_path_length; i++)
407       fprintf (stderr, "%s%s", i == 0 ? "" : "->", GNUNET_i2s (&put_path[i]));
408     fprintf (stderr, "\n");
409   }
410   if (get_path != NULL)
411   {
412     fprintf (stderr, "GET (%u) Path: ",
413              test_get->uid);
414     for (i = 0; i < get_path_length; i++)
415       fprintf (stderr, "%s%s", i == 0 ? "" : "->", GNUNET_i2s (&get_path[i]));
416     fprintf (stderr, "%s%s\n",
417              get_path_length > 0 ? "->":"",
418              GNUNET_i2s (&test_get->daemon->id));
419   }
420 #endif
421
422   if ((0 != memcmp (&search_key, key, sizeof (GNUNET_HashCode))) ||
423       (0 != memcmp (original_data, data, sizeof (original_data))))
424   {
425     fprintf (stderr,
426              "Key or data is not the same as was inserted!\n");
427     return;
428   }
429   gets_completed++;
430   test_get->succeeded = GNUNET_YES;  
431   GNUNET_SCHEDULER_cancel (test_get->task);
432   test_get->task = GNUNET_SCHEDULER_add_now (&get_stop_task, test_get);
433 }
434
435
436 /**
437  * Set up some data, and call API PUT function
438  */
439 static void
440 do_get (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
441 {
442   struct TestGetContext *test_get = cls;
443   GNUNET_HashCode key;          /* Made up key to store data under */
444   char data[TEST_DATA_SIZE];    /* Made up data to store */
445
446   if (outstanding_gets > MAX_OUTSTANDING_GETS)
447   {
448     test_get->task = GNUNET_SCHEDULER_add_delayed (GET_DELAY, &do_get, test_get);
449     return;
450   }
451   memset (data, test_get->uid, sizeof (data));
452   GNUNET_CRYPTO_hash (data, TEST_DATA_SIZE, &key);
453   test_get->dht_handle = GNUNET_DHT_connect (test_get->daemon->cfg, 10);
454   GNUNET_assert (test_get->dht_handle != NULL);
455   outstanding_gets++;
456   test_get->get_handle =
457     GNUNET_DHT_get_start (test_get->dht_handle, GNUNET_TIME_UNIT_FOREVER_REL,
458                           GNUNET_BLOCK_TYPE_TEST, &key,
459                           1, route_option, NULL, 0,
460                           &get_result_iterator, test_get);
461   test_get->task =
462     GNUNET_SCHEDULER_add_delayed (GET_TIMEOUT, &get_stop_task, test_get);
463 }
464
465
466 /**
467  * Task to release DHT handles for PUT
468  */
469 static void
470 put_disconnect_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
471 {
472   struct TestPutContext *test_put = cls;
473
474   test_put->task = GNUNET_SCHEDULER_NO_TASK;
475   GNUNET_DHT_disconnect (test_put->dht_handle);
476   test_put->dht_handle = NULL;
477   GNUNET_CONTAINER_DLL_remove (all_puts_head,
478                                all_puts_tail,
479                                test_put);
480   GNUNET_free (test_put);
481 }
482
483
484 /**
485  * Called when the PUT request has been transmitted to the DHT service.
486  * Schedule the GET request for some time in the future.
487  */
488 static void
489 put_finished (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
490 {
491   struct TestPutContext *test_put = cls;
492   unsigned long long i;
493   unsigned long long j;
494   struct TestGetContext *test_get;
495
496   outstanding_puts--;
497   puts_completed++;
498   GNUNET_SCHEDULER_cancel (test_put->task);
499   test_put->task =
500       GNUNET_SCHEDULER_add_now (&put_disconnect_task, test_put);
501   if (puts_completed != num_peers)
502     return;
503
504   GNUNET_assert (outstanding_puts == 0);
505   fprintf (stderr, 
506            "Issuing %llu GETs\n",
507            num_peers * num_peers);
508   for (i = 0; i < num_peers; i++)
509     for (j = 0; j < num_peers; j++)
510       {
511         test_get = GNUNET_malloc (sizeof (struct TestGetContext));
512         test_get->uid = i;
513         test_get->daemon = GNUNET_TESTING_daemon_get (pg, j);
514         GNUNET_CONTAINER_DLL_insert (all_gets_head,
515                                      all_gets_tail,
516                                      test_get);
517         test_get->task = GNUNET_SCHEDULER_add_now (&do_get,
518                                                    test_get);
519       }
520 }
521
522
523 /**
524  * Set up some data, and call API PUT function
525  */
526 static void
527 do_put (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
528 {
529   struct TestPutContext *test_put = cls;
530   GNUNET_HashCode key;          /* Made up key to store data under */
531   char data[TEST_DATA_SIZE];    /* Made up data to store */
532
533   test_put->task = GNUNET_SCHEDULER_NO_TASK;
534   if (outstanding_puts > MAX_OUTSTANDING_PUTS)
535   {
536     test_put->task = GNUNET_SCHEDULER_add_delayed (PUT_DELAY, &do_put, test_put);
537     return;
538   }
539   memset (data, test_put->uid, sizeof (data));
540   GNUNET_CRYPTO_hash (data, TEST_DATA_SIZE, &key);
541   test_put->dht_handle = GNUNET_DHT_connect (test_put->daemon->cfg, 10);
542   GNUNET_assert (test_put->dht_handle != NULL);
543   outstanding_puts++;
544   fprintf (stderr, "PUT %u at `%s'\n",
545            test_put->uid,
546            GNUNET_i2s (&test_put->daemon->id));
547   GNUNET_DHT_put (test_put->dht_handle, &key, 1,
548                   route_option, GNUNET_BLOCK_TYPE_TEST, sizeof (data), data,
549                   GNUNET_TIME_UNIT_FOREVER_ABS, GNUNET_TIME_UNIT_FOREVER_REL,
550                   &put_finished, test_put);
551   test_put->task =
552     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
553                                   &put_disconnect_task, test_put);
554 }
555
556
557
558 /**
559  * This function is called once testing has finished setting up the topology.
560  *
561  * @param cls unused
562  * @param emsg variable is NULL on success (peers connected), and non-NULL on
563  * failure (peers failed to connect).
564  */
565 static void
566 run_dht_test (void *cls, const char *emsg)
567 {
568   unsigned long long i;
569   struct TestPutContext *test_put;
570
571   if (emsg != NULL)
572   {
573     fprintf (stderr,
574              "Failed to setup topology: %s\n",
575              emsg);
576     die_task =
577       GNUNET_SCHEDULER_add_now (&end_badly,
578                                 "topology setup failed");
579     return;
580   }
581
582 #if PATH_TRACKING
583   route_option = GNUNET_DHT_RO_RECORD_ROUTE;
584 #else
585   route_option = GNUNET_DHT_RO_NONE;
586 #endif
587   die_task =
588     GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_badly,
589                                   "from setup puts/gets");
590   fprintf (stderr, 
591            "Issuing %llu PUTs (one per peer)\n", 
592            num_peers);
593   for (i = 0; i < num_peers; i++)
594   {
595     test_put = GNUNET_malloc (sizeof (struct TestPutContext));
596     test_put->uid = i;
597     test_put->daemon = GNUNET_TESTING_daemon_get (pg, i);    
598     test_put->task = GNUNET_SCHEDULER_add_now (&do_put, test_put);
599     GNUNET_CONTAINER_DLL_insert (all_puts_head,
600                                  all_puts_tail,
601                                  test_put);
602   }
603 }
604
605
606 static void
607 run (void *cls, char *const *args, const char *cfgfile,
608      const struct GNUNET_CONFIGURATION_Handle *cfg)
609 {
610   /* Get path from configuration file */
611   if (GNUNET_YES !=
612       GNUNET_CONFIGURATION_get_value_string (cfg, "paths", "servicehome",
613                                              &test_directory))
614   {
615     GNUNET_break (0);
616     ok = 404;
617     return;
618   }
619   if (GNUNET_SYSERR ==
620       GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "num_peers",
621                                              &num_peers))
622     num_peers = DEFAULT_NUM_PEERS;
623   pg = GNUNET_TESTING_peergroup_start (cfg,
624                                        num_peers,
625                                        TIMEOUT,
626                                        NULL,
627                                        &run_dht_test,
628                                        NULL,
629                                        NULL);
630   GNUNET_assert (NULL != pg);
631 }
632
633
634 static int
635 check ()
636 {
637   int ret;
638
639   /* Arguments for GNUNET_PROGRAM_run */
640   char *const argv[] = { "test-dht-multipeer",  /* Name to give running binary */
641     "-c",
642     "test_dht_multipeer_data.conf",     /* Config file to use */
643 #if VERBOSE
644     "-L", "DEBUG",
645 #endif
646     NULL
647   };
648   struct GNUNET_GETOPT_CommandLineOption options[] = {
649     GNUNET_GETOPT_OPTION_END
650   };
651   /* Run the run function as a new program */
652   ret =
653       GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1, argv,
654                           "test-dht-multipeer", "nohelp", options, &run, &ok);
655   if (ret != GNUNET_OK)
656   {
657     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
658                 "`test-dht-multipeer': Failed with error code %d\n", ret);
659   }
660   return ok;
661 }
662
663
664 int
665 main (int argc, char *argv[])
666 {
667   int ret;
668
669
670   GNUNET_log_setup ("test-dht-multipeer",
671 #if VERBOSE
672                     "DEBUG",
673 #else
674                     "WARNING",
675 #endif
676                     NULL);
677   ret = check ();
678   /**
679    * Need to remove base directory, subdirectories taken care
680    * of by the testing framework.
681    */
682   if (GNUNET_DISK_directory_remove (test_directory) != GNUNET_OK)
683   {
684     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
685                 "Failed to remove testing directory %s\n", test_directory);
686   }
687   return ret;
688 }
689
690 /* end of test_dht_multipeer.c */