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