testing bugfix, add scale free topology
[oweals/gnunet.git] / src / testing / test_testing_topology.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 2, 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 testing/test_testing_topology.c
22  * @brief base testcase for testing all the topologies provided
23  */
24 #include "platform.h"
25 #include "gnunet_testing_lib.h"
26 #include "gnunet_core_service.h"
27
28 #define VERBOSE GNUNET_YES
29
30 /**
31  * How long until we fail the whole testcase?
32  */
33 #define TEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 600)
34
35 /**
36  * How long until we give up on connecting the peers?
37  */
38 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60)
39
40 #define DEFAULT_NUM_PEERS 4
41
42 #define MAX_OUTSTANDING_CONNECTIONS 300
43
44 static float fail_percentage = 0.05;
45
46 static int ok;
47
48 static unsigned long long num_peers;
49
50 static unsigned int total_connections;
51
52 static unsigned int failed_connections;
53
54 static unsigned int total_server_connections;
55
56 static unsigned int total_messages_received;
57
58 static unsigned int expected_messages;
59
60 static unsigned int expected_connections;
61
62 static unsigned long long peers_left;
63
64 static struct GNUNET_TESTING_PeerGroup *pg;
65
66 static struct GNUNET_SCHEDULER_Handle *sched;
67
68 const struct GNUNET_CONFIGURATION_Handle *main_cfg;
69
70 GNUNET_SCHEDULER_TaskIdentifier die_task;
71
72 static char *dotOutFileName = "topology.dot";
73
74 static FILE *dotOutFile;
75
76 static char *topology_string;
77
78 static int transmit_ready_scheduled;
79
80 static int transmit_ready_failed;
81
82 static int transmit_ready_called;
83
84 #define MTYPE 12345
85
86 struct GNUNET_TestMessage
87 {
88   /**
89    * Header of the message
90    */
91   struct GNUNET_MessageHeader header;
92
93   /**
94    * Unique identifier for this message.
95    */
96   uint32_t uid;
97 };
98
99 struct TestMessageContext
100 {
101   /* This is a linked list */
102   struct TestMessageContext *next;
103
104   /* Handle to the sending peer core */
105   struct GNUNET_CORE_Handle *peer1handle;
106
107   /* Handle to the receiving peer core */
108   struct GNUNET_CORE_Handle *peer2handle;
109
110   /* Handle to the sending peer daemon */
111   struct GNUNET_TESTING_Daemon *peer1;
112
113   /* Handle to the receiving peer daemon */
114   struct GNUNET_TESTING_Daemon *peer2;
115
116   /* Identifier for this message, so we don't disconnect other peers! */
117   uint32_t uid;
118
119   /* Task for disconnecting cores, allow task to be cancelled on shutdown */
120   GNUNET_SCHEDULER_TaskIdentifier disconnect_task;
121
122 };
123
124 static struct TestMessageContext *test_messages;
125
126 static void
127 finish_testing ()
128 {
129   GNUNET_assert (pg != NULL);
130   struct TestMessageContext *pos;
131   struct TestMessageContext *free_pos;
132 #if VERBOSE
133   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
134               "Called finish testing, stopping daemons.\n");
135 #endif
136   int count;
137   count = 0;
138   pos = test_messages;
139   while (pos != NULL)
140     {
141       if (pos->peer1handle != NULL)
142         {
143           GNUNET_CORE_disconnect(pos->peer1handle);
144           pos->peer1handle = NULL;
145         }
146       if (pos->peer2handle != NULL)
147         {
148           GNUNET_CORE_disconnect(pos->peer2handle);
149           pos->peer2handle = NULL;
150         }
151       free_pos = pos;
152       pos = pos->next;
153       if (free_pos->disconnect_task != GNUNET_SCHEDULER_NO_TASK)
154         {
155           GNUNET_SCHEDULER_cancel(sched, free_pos->disconnect_task);
156         }
157       GNUNET_free(free_pos);
158     }
159 #if VERBOSE
160           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
161                       "transmit_ready's scheduled %d, failed %d, transmit_ready's called %d\n", transmit_ready_scheduled, transmit_ready_failed, transmit_ready_called);
162 #endif
163   sleep(1);
164 #if VERBOSE
165           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
166                       "Calling daemons_stop\n");
167 #endif
168   GNUNET_TESTING_daemons_stop (pg);
169 #if VERBOSE
170           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
171                       "daemons_stop finished\n");
172 #endif
173   if (dotOutFile != NULL)
174     {
175       fprintf(dotOutFile, "}");
176       fclose(dotOutFile);
177     }
178
179   ok = 0;
180 }
181
182
183 static void
184 disconnect_cores (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
185 {
186   struct TestMessageContext *pos = cls;
187
188   /* Disconnect from the respective cores */
189 #if VERBOSE
190   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
191               "Disconnecting from peer 1 `%4s'\n", GNUNET_i2s (&pos->peer1->id));
192 #endif
193   if (pos->peer1handle != NULL)
194     GNUNET_CORE_disconnect(pos->peer1handle);
195 #if VERBOSE
196   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
197               "Disconnecting from peer 2 `%4s'\n", GNUNET_i2s (&pos->peer2->id));
198 #endif
199   if (pos->peer2handle != NULL)
200     GNUNET_CORE_disconnect(pos->peer2handle);
201   /* Set handles to NULL so test case can be ended properly */
202   pos->peer1handle = NULL;
203   pos->peer2handle = NULL;
204   pos->disconnect_task = GNUNET_SCHEDULER_NO_TASK;
205   /* Decrement total connections so new can be established */
206   total_server_connections -= 2;
207 }
208
209 static int
210 process_mtype (void *cls,
211                const struct GNUNET_PeerIdentity *peer,
212                const struct GNUNET_MessageHeader *message,
213                struct GNUNET_TIME_Relative latency,
214                uint32_t distance)
215 {
216   struct TestMessageContext *pos = cls;
217   struct GNUNET_TestMessage *msg = (struct GNUNET_TestMessage *)message;
218   if (pos->uid != ntohl(msg->uid))
219     return GNUNET_OK;
220
221   total_messages_received++;
222 #if VERBOSE
223   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
224               "Received message from `%4s', type %d.\n", GNUNET_i2s (peer), ntohs(message->type));
225   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
226               "Total messages received %d, expected %d.\n", total_messages_received, expected_messages);
227 #endif
228
229   if (total_messages_received == expected_messages)
230     {
231       GNUNET_SCHEDULER_cancel (sched, die_task);
232       GNUNET_SCHEDULER_add_now (sched, &finish_testing, NULL);
233     }
234   else
235     {
236       pos->disconnect_task = GNUNET_SCHEDULER_add_now(sched, &disconnect_cores, pos);
237     }
238
239   return GNUNET_OK;
240 }
241
242 static void
243 end_badly (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
244 {
245   char *msg = cls;
246   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
247               "End badly was called (%s)... stopping daemons.\n", msg);
248   struct TestMessageContext *pos;
249   struct TestMessageContext *free_pos;
250
251   pos = test_messages;
252   while (pos != NULL)
253     {
254       if (pos->peer1handle != NULL)
255         {
256           GNUNET_CORE_disconnect(pos->peer1handle);
257           pos->peer1handle = NULL;
258         }
259       if (pos->peer2handle != NULL)
260         {
261           GNUNET_CORE_disconnect(pos->peer2handle);
262           pos->peer2handle = NULL;
263         }
264       free_pos = pos;
265       pos = pos->next;
266       GNUNET_free(free_pos);
267     }
268
269   if (pg != NULL)
270     {
271       GNUNET_TESTING_daemons_stop (pg);
272       ok = 7331;                /* Opposite of leet */
273     }
274   else
275     ok = 401;                   /* Never got peers started */
276
277   if (dotOutFile != NULL)
278     {
279       fprintf(dotOutFile, "}");
280       fclose(dotOutFile);
281     }
282 }
283
284
285
286 static size_t
287 transmit_ready (void *cls, size_t size, void *buf)
288 {
289   struct GNUNET_TestMessage *m;
290   struct TestMessageContext *pos = cls;
291
292   GNUNET_assert (buf != NULL);
293   m = (struct GNUNET_TestMessage *) buf;
294   m->header.type = htons (MTYPE);
295   m->header.size = htons (sizeof (struct GNUNET_TestMessage));
296   m->uid = htonl(pos->uid);
297   transmit_ready_called++;
298 #if VERBOSE
299   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
300               "transmit ready for peer %s\ntransmit_ready's scheduled %d, transmit_ready's called %d\n", GNUNET_i2s(&pos->peer1->id), transmit_ready_scheduled, transmit_ready_called);
301 #endif
302   return sizeof (struct GNUNET_TestMessage);
303 }
304
305
306 static struct GNUNET_CORE_MessageHandler no_handlers[] = {
307   {NULL, 0, 0}
308 };
309
310 static struct GNUNET_CORE_MessageHandler handlers[] = {
311   {&process_mtype, MTYPE, sizeof (struct GNUNET_TestMessage)},
312   {NULL, 0, 0}
313 };
314
315 static void
316 init_notify_peer2 (void *cls,
317              struct GNUNET_CORE_Handle *server,
318              const struct GNUNET_PeerIdentity *my_identity,
319              const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *publicKey)
320 {
321   struct TestMessageContext *pos = cls;
322
323 #if VERBOSE
324   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
325               "Core connection to `%4s' established, scheduling message send\n",
326               GNUNET_i2s (my_identity));
327 #endif
328   total_server_connections++;
329
330   if (NULL == GNUNET_CORE_notify_transmit_ready (pos->peer1handle,
331                                                  0,
332                                                  TIMEOUT,
333                                                  &pos->peer2->id,
334                                                  sizeof (struct GNUNET_TestMessage),
335                                                  &transmit_ready, pos))
336     {
337       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
338                   "RECEIVED NULL when asking core (1) for transmission to peer `%4s'\n",
339                   GNUNET_i2s (&pos->peer2->id));
340       transmit_ready_failed++;
341     }
342   else
343     {
344       transmit_ready_scheduled++;
345     }
346 }
347
348
349 static void
350 init_notify_peer1 (void *cls,
351              struct GNUNET_CORE_Handle *server,
352              const struct GNUNET_PeerIdentity *my_identity,
353              const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *publicKey)
354 {
355   struct TestMessageContext *pos = cls;
356   total_server_connections++;
357
358 #if VERBOSE
359   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
360               "Core connection to `%4s' established, setting up handles\n",
361               GNUNET_i2s (my_identity));
362 #endif
363
364   /*
365    * Connect to the receiving peer
366    */
367   pos->peer2handle = GNUNET_CORE_connect (sched,
368                        pos->peer2->cfg,
369                        TIMEOUT,
370                        pos,
371                        &init_notify_peer2,
372                        NULL,
373                        NULL,
374                        NULL,
375                        GNUNET_YES, NULL, GNUNET_YES, handlers);
376
377 }
378
379
380 static void
381 send_test_messages (void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
382 {
383   struct TestMessageContext *pos = cls;
384
385   if ((tc->reason == GNUNET_SCHEDULER_REASON_SHUTDOWN) || (cls == NULL))
386     return;
387
388   if (die_task == GNUNET_SCHEDULER_NO_TASK)
389     {
390       die_task = GNUNET_SCHEDULER_add_delayed (sched,
391                                                TEST_TIMEOUT,
392                                                &end_badly, "from create topology (timeout)");
393     }
394
395   if (total_server_connections >= MAX_OUTSTANDING_CONNECTIONS)
396     {
397       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1),
398                                     &send_test_messages, pos);
399       return; /* Otherwise we'll double schedule messages here! */
400     }
401
402   /*
403    * Connect to the sending peer
404    */
405   pos->peer1handle = GNUNET_CORE_connect (sched,
406                                           pos->peer1->cfg,
407                                           TIMEOUT,
408                                           pos,
409                                           &init_notify_peer1,
410                                           NULL,
411                                           NULL,
412                                           NULL,
413                                           GNUNET_NO, NULL, GNUNET_NO, no_handlers);
414
415   GNUNET_assert(pos->peer1handle != NULL);
416
417   if (total_server_connections < MAX_OUTSTANDING_CONNECTIONS)
418     {
419       GNUNET_SCHEDULER_add_now (sched,
420                                 &send_test_messages, pos->next);
421     }
422   else
423     {
424       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1),
425                                     &send_test_messages, pos->next);
426     }
427 }
428
429
430 void
431 topology_callback (void *cls,
432                    const struct GNUNET_PeerIdentity *first,
433                    const struct GNUNET_PeerIdentity *second,
434                    const struct GNUNET_CONFIGURATION_Handle *first_cfg,
435                    const struct GNUNET_CONFIGURATION_Handle *second_cfg,
436                    struct GNUNET_TESTING_Daemon *first_daemon,
437                    struct GNUNET_TESTING_Daemon *second_daemon,
438                    const char *emsg)
439 {
440   struct TestMessageContext *temp_context;
441   if (emsg == NULL)
442     {
443       total_connections++;
444 #if VERBOSE
445       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "connected peer %s to peer %s\n",
446                first_daemon->shortname,
447                second_daemon->shortname);
448 #endif
449       temp_context = GNUNET_malloc(sizeof(struct TestMessageContext));
450       temp_context->peer1 = first_daemon;
451       temp_context->peer2 = second_daemon;
452       temp_context->next = test_messages;
453       temp_context->uid = total_connections;
454       temp_context->disconnect_task = GNUNET_SCHEDULER_NO_TASK;
455       test_messages = temp_context;
456
457       expected_messages++;
458       if (dotOutFile != NULL)
459         fprintf(dotOutFile, "\tn%s -- n%s;\n", first_daemon->shortname, second_daemon->shortname);
460     }
461 #if VERBOSE
462   else
463     {
464       failed_connections++;
465       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to connect peer %s to peer %s with error :\n%s\n",
466                first_daemon->shortname,
467                second_daemon->shortname, emsg);
468     }
469 #endif
470
471   if (total_connections == expected_connections)
472     {
473 #if VERBOSE
474       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
475                   "Created %d total connections, which is our target number!  Calling send messages.\n",
476                   total_connections);
477 #endif
478
479       GNUNET_SCHEDULER_cancel (sched, die_task);
480       die_task = GNUNET_SCHEDULER_NO_TASK;
481       GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 1), &send_test_messages, test_messages);
482     }
483   else if (total_connections + failed_connections == expected_connections)
484     {
485       if (failed_connections < (unsigned int)(fail_percentage * total_connections))
486         {
487           GNUNET_SCHEDULER_cancel (sched, die_task);
488           die_task = GNUNET_SCHEDULER_NO_TASK;
489           GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 1), &send_test_messages, test_messages);
490         }
491       else
492         {
493           GNUNET_SCHEDULER_cancel (sched, die_task);
494           die_task = GNUNET_SCHEDULER_add_now (sched,
495                                                &end_badly, "from topology_callback (too many failed connections)");
496         }
497     }
498   else
499     {
500 #if VERBOSE
501       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502                   "Have %d total connections, %d failed connections, Want %d (at least %d)\n",
503                   total_connections, failed_connections, expected_connections, expected_connections - (unsigned int)(fail_percentage * expected_connections));
504 #endif
505     }
506 }
507
508
509 static void
510 create_topology ()
511 {
512   expected_connections = -1;
513   if ((pg != NULL) && (peers_left == 0))
514     {
515       /* create_topology will read the topology information from
516          the config already contained in the peer group, so should
517          we have create_topology called from start peers?  I think
518          maybe this way is best so that the client can know both
519          when peers are started, and when they are connected.
520        */
521       expected_connections = GNUNET_TESTING_create_topology (pg);
522 #if VERBOSE
523       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
524                   "Have %d expected connections\n", expected_connections);
525 #endif
526     }
527
528   GNUNET_SCHEDULER_cancel (sched, die_task);
529   if (expected_connections == GNUNET_SYSERR)
530     {
531       die_task = GNUNET_SCHEDULER_add_now (sched,
532                                            &end_badly, "from create topology (bad return)");
533     }
534   die_task = GNUNET_SCHEDULER_add_delayed (sched,
535                                            TEST_TIMEOUT,
536                                            &end_badly, "from create topology (timeout)");
537 }
538
539
540 static void
541 my_cb (void *cls,
542        const struct GNUNET_PeerIdentity *id,
543        const struct GNUNET_CONFIGURATION_Handle *cfg,
544        struct GNUNET_TESTING_Daemon *d, const char *emsg)
545 {
546   GNUNET_assert (id != NULL);
547 #if VERBOSE
548   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Started daemon %llu out of %llu\n",
549               (num_peers - peers_left) + 1, num_peers);
550 #endif
551   peers_left--;
552   if (peers_left == 0)
553     {
554 #if VERBOSE
555       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
556                   "All %d daemons started, now creating topology!\n",
557                   num_peers);
558 #endif
559       GNUNET_SCHEDULER_cancel (sched, die_task);
560       /* Set up task in case topology creation doesn't finish
561        * within a reasonable amount of time */
562       die_task = GNUNET_SCHEDULER_add_delayed (sched,
563                                                GNUNET_TIME_relative_multiply
564                                                (GNUNET_TIME_UNIT_MINUTES, 5),
565                                                &end_badly, "from my_cb");
566       create_topology ();
567       ok = 0;
568     }
569 }
570
571
572 static void
573 run (void *cls,
574      struct GNUNET_SCHEDULER_Handle *s,
575      char *const *args,
576      const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg)
577 {
578   sched = s;
579   ok = 1;
580
581   dotOutFile = fopen (dotOutFileName, "w");
582   if (dotOutFile != NULL)
583     {
584       fprintf (dotOutFile, "strict graph G {\n");
585     }
586
587 #if VERBOSE
588   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
589               "Starting daemons based on config file %s\n", cfgfile);
590 #endif
591   if (GNUNET_SYSERR ==
592       GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "num_peers",
593                                              &num_peers))
594     num_peers = DEFAULT_NUM_PEERS;
595
596   main_cfg = cfg;
597
598   peers_left = num_peers;
599
600   /* Set up a task to end testing if peer start fails */
601   die_task = GNUNET_SCHEDULER_add_delayed (sched,
602                                            GNUNET_TIME_relative_multiply
603                                            (GNUNET_TIME_UNIT_MINUTES, 5),
604                                            &end_badly, "didn't start all daemons in reasonable amount of time!!!");
605
606   pg = GNUNET_TESTING_daemons_start (sched, cfg,
607                                      peers_left, &my_cb, NULL,
608                                      &topology_callback, NULL, NULL);
609
610 }
611
612 static int
613 check ()
614 {
615   char *binary_name;
616   char *config_file_name;
617   GNUNET_asprintf(&binary_name, "test-testing-topology-%s", topology_string);
618   GNUNET_asprintf(&config_file_name, "test_testing_data_topology_%s.conf", topology_string);
619   char *const argv[] = {binary_name,
620     "-c",
621     config_file_name,
622 #if VERBOSE
623     "-L", "DEBUG",
624 #endif
625     NULL
626   };
627   struct GNUNET_GETOPT_CommandLineOption options[] = {
628     GNUNET_GETOPT_OPTION_END
629   };
630   GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
631                       argv, binary_name, "nohelp",
632                       options, &run, &ok);
633   GNUNET_free(binary_name);
634   GNUNET_free(config_file_name);
635   return ok;
636 }
637
638 int
639 main (int argc, char *argv[])
640 {
641   int ret;
642   char *binary_start_pos;
643   char *our_binary_name;
644
645   binary_start_pos = rindex(argv[0], '/');
646   topology_string = strstr (binary_start_pos,
647                             "_topology");
648   GNUNET_assert (topology_string != NULL);
649   topology_string++;
650   topology_string = strstr (topology_string, "_");
651   GNUNET_assert (topology_string != NULL);
652   topology_string++;
653
654   GNUNET_asprintf(&our_binary_name, "test-testing-topology_%s", topology_string);
655   GNUNET_log_setup (our_binary_name,
656 #if VERBOSE
657                     "DEBUG",
658 #else
659                     "WARNING",
660 #endif
661                     NULL);
662   ret = check ();
663
664   /**
665    * Need to remove base directory, subdirectories taken care
666    * of by the testing framework.
667    */
668   GNUNET_DISK_directory_remove ("/tmp/test-gnunet-testing");
669   GNUNET_free(our_binary_name);
670   return ret;
671 }
672
673 /* end of test_testing_group.c */