add units to time, use configuration time api where appropriate, fixing Mantis #1875
[oweals/gnunet.git] / src / testing / testing_peergroup.c
1 /*
2  This file is part of GNUnet
3  (C) 2008-2011 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 /**
22  * @file testing/testing_peergroup.c
23  * @brief API implementation for easy peer group creation
24  * @author Nathan Evans
25  * @author Christian Grothoff
26  *
27  */
28 #include "platform.h"
29 #include "gnunet_constants.h"
30 #include "gnunet_arm_service.h"
31 #include "gnunet_testing_lib.h"
32 #include "gnunet_core_service.h"
33 #include "gnunet_disk_lib.h"
34
35 /** Globals **/
36 #define DEFAULT_CONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
37
38 #define DEFAULT_CONNECT_ATTEMPTS 2
39
40 /** Struct definitions **/
41
42 struct PeerGroupStartupContext
43 {
44   struct GNUNET_TESTING_PeerGroup *pg;
45   const struct GNUNET_CONFIGURATION_Handle *cfg;
46   unsigned int total;
47   unsigned int peers_left;
48   unsigned long long max_concurrent_connections;
49   
50   /**
51    * Maximum attemps to connect two daemons.
52    */
53   unsigned long long connect_attempts;
54
55   /**
56    * How long to spend trying to establish all the connections?
57    */
58   struct GNUNET_TIME_Relative connect_timeout;
59   
60   unsigned long long max_concurrent_ssh;
61   struct GNUNET_TIME_Absolute timeout;
62   GNUNET_TESTING_NotifyConnection connect_cb;
63   GNUNET_TESTING_NotifyCompletion peergroup_cb;
64
65   /**
66    * Closure for all peergroup callbacks.
67    */
68   void *cls;
69
70   const struct GNUNET_TESTING_Host *hostnames;
71   enum GNUNET_TESTING_Topology topology;
72
73   float topology_percentage;
74
75   float topology_probability;
76
77   enum GNUNET_TESTING_Topology restrict_topology;
78   char *restrict_transports;
79   enum GNUNET_TESTING_Topology connect_topology;
80   enum GNUNET_TESTING_TopologyOption connect_topology_option;
81   double connect_topology_option_modifier;
82   int verbose;
83
84   struct ProgressMeter *hostkey_meter;
85   struct ProgressMeter *peer_start_meter;
86   struct ProgressMeter *connect_meter;
87
88   /**
89    * Task used to kill the peergroup.
90    */
91   GNUNET_SCHEDULER_TaskIdentifier die_task;
92
93   char *fail_reason;
94
95   /**
96    * Variable used to store the number of connections we should wait for.
97    */
98   unsigned int expected_connections;
99
100   /**
101    * Time when the connecting peers was started.
102    */
103   struct GNUNET_TIME_Absolute connect_start_time;
104
105   /**
106    * The total number of connections that have been created so far.
107    */
108   unsigned int total_connections;
109
110   /**
111    * The total number of connections that have failed so far.
112    */
113   unsigned int failed_connections;
114
115   /**
116    * File handle to write out topology in dot format.
117    */
118   struct GNUNET_DISK_FileHandle *topology_output_file;
119 };
120
121 struct TopologyOutputContext
122 {
123   struct GNUNET_DISK_FileHandle *file;
124   GNUNET_TESTING_NotifyCompletion notify_cb;
125   void *notify_cb_cls;
126 };
127
128 /**
129  * Simple struct to keep track of progress, and print a
130  * percentage meter for long running tasks.
131  */
132 struct ProgressMeter
133 {
134   /**
135    * Total number of tasks to complete.
136    */
137   unsigned int total;
138
139   /**
140    * Print percentage done after modnum tasks.
141    */
142   unsigned int modnum;
143
144   /**
145    * Print a . each dotnum tasks.
146    */
147   unsigned int dotnum;
148
149   /**
150    * Total number completed thus far.
151    */
152   unsigned int completed;
153
154   /**
155    * Whether or not to print.
156    */
157   int print;
158
159   /**
160    * Startup string for progress meter.
161    */
162   char *startup_string;
163 };
164
165
166 /** Utility functions **/
167
168 /**
169  * Create a meter to keep track of the progress of some task.
170  *
171  * @param total the total number of items to complete
172  * @param start_string a string to prefix the meter with (if printing)
173  * @param print GNUNET_YES to print the meter, GNUNET_NO to count
174  *              internally only
175  *
176  * @return the progress meter
177  */
178 static struct ProgressMeter *
179 create_meter (unsigned int total, char *start_string, int print)
180 {
181   struct ProgressMeter *ret;
182
183   ret = GNUNET_malloc (sizeof (struct ProgressMeter));
184   ret->print = print;
185   ret->total = total;
186   ret->modnum = total / 4;
187   ret->dotnum = (total / 50) + 1;
188   if (start_string != NULL)
189     ret->startup_string = GNUNET_strdup (start_string);
190   else
191     ret->startup_string = GNUNET_strdup ("");
192
193   return ret;
194 }
195
196 /**
197  * Update progress meter (increment by one).
198  *
199  * @param meter the meter to update and print info for
200  *
201  * @return GNUNET_YES if called the total requested,
202  *         GNUNET_NO if more items expected
203  */
204 static int
205 update_meter (struct ProgressMeter *meter)
206 {
207   if (meter->print == GNUNET_YES)
208   {
209     if (meter->completed % meter->modnum == 0)
210     {
211       if (meter->completed == 0)
212       {
213         fprintf (stdout, "%sProgress: [0%%", meter->startup_string);
214       }
215       else
216         fprintf (stdout, "%d%%",
217                  (int) (((float) meter->completed / meter->total) * 100));
218     }
219     else if (meter->completed % meter->dotnum == 0)
220       fprintf (stdout, ".");
221
222     if (meter->completed + 1 == meter->total)
223       fprintf (stdout, "%d%%]\n", 100);
224     fflush (stdout);
225   }
226   meter->completed++;
227
228   if (meter->completed == meter->total)
229     return GNUNET_YES;
230   return GNUNET_NO;
231 }
232
233 /**
234  * Reset progress meter.
235  *
236  * @param meter the meter to reset
237  *
238  * @return GNUNET_YES if meter reset,
239  *         GNUNET_SYSERR on error
240  */
241 static int
242 reset_meter (struct ProgressMeter *meter)
243 {
244   if (meter == NULL)
245     return GNUNET_SYSERR;
246
247   meter->completed = 0;
248   return GNUNET_YES;
249 }
250
251 /**
252  * Release resources for meter
253  *
254  * @param meter the meter to free
255  */
256 static void
257 free_meter (struct ProgressMeter *meter)
258 {
259   GNUNET_free_non_null (meter->startup_string);
260   GNUNET_free (meter);
261 }
262
263
264 /** Functions for creating, starting and connecting the peergroup **/
265
266 /**
267  * Check whether peers successfully shut down.
268  */
269 static void
270 internal_shutdown_callback (void *cls, const char *emsg)
271 {
272   struct PeerGroupStartupContext *pg_start_ctx = cls;
273
274   if (emsg != NULL)
275     pg_start_ctx->peergroup_cb (pg_start_ctx->cls, emsg);
276   else
277     pg_start_ctx->peergroup_cb (pg_start_ctx->cls, pg_start_ctx->fail_reason);
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   struct PeerGroupStartupContext *pg_start_ctx = cls;
289
290   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
291               "Failing peer group startup with error: `%s'!\n",
292               pg_start_ctx->fail_reason);
293
294   GNUNET_TESTING_daemons_stop (pg_start_ctx->pg,
295                                GNUNET_TIME_absolute_get_remaining
296                                (pg_start_ctx->timeout),
297                                &internal_shutdown_callback, pg_start_ctx);
298
299   if (pg_start_ctx->hostkey_meter != NULL)
300     free_meter (pg_start_ctx->hostkey_meter);
301   if (pg_start_ctx->peer_start_meter != NULL)
302     free_meter (pg_start_ctx->peer_start_meter);
303   if (pg_start_ctx->connect_meter != NULL)
304     free_meter (pg_start_ctx->connect_meter);
305 }
306
307 /**
308  * This function is called whenever a connection attempt is finished between two of
309  * the started peers (started with GNUNET_TESTING_daemons_start).  The total
310  * number of times this function is called should equal the number returned
311  * from the GNUNET_TESTING_connect_topology call.
312  *
313  * The emsg variable is NULL on success (peers connected), and non-NULL on
314  * failure (peers failed to connect).
315  */
316 static void
317 internal_topology_callback (void *cls, const struct GNUNET_PeerIdentity *first,
318                             const struct GNUNET_PeerIdentity *second,
319                             uint32_t distance,
320                             const struct GNUNET_CONFIGURATION_Handle *first_cfg,
321                             const struct GNUNET_CONFIGURATION_Handle
322                             *second_cfg,
323                             struct GNUNET_TESTING_Daemon *first_daemon,
324                             struct GNUNET_TESTING_Daemon *second_daemon,
325                             const char *emsg)
326 {
327   struct PeerGroupStartupContext *pg_start_ctx = cls;
328   char *temp_str;
329   char *second_str;
330   int temp;
331
332 #if TIMING
333   unsigned long long duration;
334   unsigned long long total_duration;
335   unsigned int new_connections;
336   unsigned int new_failed_connections;
337   double conns_per_sec_recent;
338   double conns_per_sec_total;
339   double failed_conns_per_sec_recent;
340   double failed_conns_per_sec_total;
341 #endif
342
343 #if TIMING
344   if (GNUNET_TIME_absolute_get_difference
345       (connect_last_time,
346        GNUNET_TIME_absolute_get ()).rel_value >
347       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
348                                      CONN_UPDATE_DURATION).rel_value)
349   {
350     /* Get number of new connections */
351     new_connections = total_connections - previous_connections;
352
353     /* Get number of new FAILED connections */
354     new_failed_connections = failed_connections - previous_failed_connections;
355
356     /* Get duration in seconds */
357     duration =
358         GNUNET_TIME_absolute_get_difference (connect_last_time,
359                                              GNUNET_TIME_absolute_get
360                                              ()).rel_value / 1000;
361     total_duration =
362         GNUNET_TIME_absolute_get_difference (connect_start_time,
363                                              GNUNET_TIME_absolute_get
364                                              ()).rel_value / 1000;
365
366     failed_conns_per_sec_recent = (double) new_failed_connections / duration;
367     failed_conns_per_sec_total = (double) failed_connections / total_duration;
368     conns_per_sec_recent = (double) new_connections / duration;
369     conns_per_sec_total = (double) total_connections / total_duration;
370     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
371                 "Recent: %.2f/s, Total: %.2f/s, Recent failed: %.2f/s, total failed %.2f/s\n",
372                 conns_per_sec_recent, CONN_UPDATE_DURATION, conns_per_sec_total,
373                 failed_conns_per_sec_recent, failed_conns_per_sec_total);
374     connect_last_time = GNUNET_TIME_absolute_get ();
375     previous_connections = total_connections;
376     previous_failed_connections = failed_connections;
377     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
378                 "have %u total_connections, %u failed\n", total_connections,
379                 failed_connections);
380   }
381 #endif
382
383
384   if (emsg == NULL)
385   {
386     pg_start_ctx->total_connections++;
387 #if VERBOSE > 1
388     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
389                 "connected peer %s to peer %s, distance %u\n",
390                 first_daemon->shortname, second_daemon->shortname, distance);
391 #endif
392     if (pg_start_ctx->topology_output_file != NULL)
393     {
394       second_str = GNUNET_strdup (GNUNET_i2s (second));
395       temp =
396           GNUNET_asprintf (&temp_str, "\t\"%s\" -- \"%s\"\n",
397                            GNUNET_i2s (first), second_str);
398       GNUNET_free (second_str);
399       if (temp > 0)
400         GNUNET_DISK_file_write (pg_start_ctx->topology_output_file, temp_str,
401                                 temp);
402       GNUNET_free (temp_str);
403     }
404   }
405   else
406   {
407     pg_start_ctx->failed_connections++;
408 #if VERBOSE
409     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
410                 "Failed to connect peer %s to peer %s with error :\n%s\n",
411                 first_daemon->shortname, second_daemon->shortname, emsg);
412
413     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
414                 "Failed to connect peer %s to peer %s with error :\n%s\n",
415                 first_daemon->shortname, second_daemon->shortname, emsg);
416 #endif
417   }
418
419   GNUNET_assert (pg_start_ctx->connect_meter != NULL);
420   if (pg_start_ctx->connect_cb != NULL)
421     pg_start_ctx->connect_cb (pg_start_ctx->cls, first, second, distance,
422                               first_cfg, second_cfg, first_daemon,
423                               second_daemon, emsg);
424   if (GNUNET_YES == update_meter (pg_start_ctx->connect_meter))
425   {
426 #if VERBOSE
427     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
428                 "Created %d total connections, which is our target number!  Starting next phase of testing.\n",
429                 pg_start_ctx->total_connections);
430 #endif
431
432 #if TIMING
433     total_duration =
434         GNUNET_TIME_absolute_get_difference (connect_start_time,
435                                              GNUNET_TIME_absolute_get
436                                              ()).rel_value / 1000;
437     failed_conns_per_sec_total = (double) failed_connections / total_duration;
438     conns_per_sec_total = (double) total_connections / total_duration;
439     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
440                 "Overall connection info --- Total: %u, Total Failed %u/s\n",
441                 total_connections, failed_connections);
442     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
443                 "Overall connection info --- Total: %.2f/s, Total Failed %.2f/s\n",
444                 conns_per_sec_total, failed_conns_per_sec_total);
445 #endif
446
447     GNUNET_assert (pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK);
448     GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
449
450     /* Call final callback, signifying that the peer group has been started and connected */
451     if (pg_start_ctx->peergroup_cb != NULL)
452       pg_start_ctx->peergroup_cb (pg_start_ctx->cls, NULL);
453
454     if (pg_start_ctx->topology_output_file != NULL)
455     {
456       temp = GNUNET_asprintf (&temp_str, "}\n");
457       if (temp > 0)
458         GNUNET_DISK_file_write (pg_start_ctx->topology_output_file, temp_str,
459                                 temp);
460       GNUNET_free (temp_str);
461       GNUNET_DISK_file_close (pg_start_ctx->topology_output_file);
462     }
463   }
464 }
465
466 static void
467 internal_peers_started_callback (void *cls,
468                                  const struct GNUNET_PeerIdentity *id,
469                                  const struct GNUNET_CONFIGURATION_Handle *cfg,
470                                  struct GNUNET_TESTING_Daemon *d,
471                                  const char *emsg)
472 {
473   struct PeerGroupStartupContext *pg_start_ctx = cls;
474
475   if (emsg != NULL)
476   {
477     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
478                 "Failed to start daemon with error: `%s'\n", emsg);
479     return;
480   }
481   GNUNET_assert (id != NULL);
482
483 #if VERBOSE > 1
484   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Started daemon %llu out of %llu\n",
485               (pg_start_ctx->total - pg_start_ctx->peers_left) + 1,
486               pg_start_ctx->total);
487 #endif
488
489   pg_start_ctx->peers_left--;
490
491   if (GNUNET_YES == update_meter (pg_start_ctx->peer_start_meter))
492   {
493 #if VERBOSE
494     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
495                 "All %d daemons started, now connecting peers!\n",
496                 pg_start_ctx->total);
497 #endif
498     GNUNET_assert (pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK);
499     GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
500
501     pg_start_ctx->expected_connections = UINT_MAX;
502     // FIXME: why whould peers_left be != 0?? Or pg NULL?
503     if ((pg_start_ctx->pg != NULL) && (pg_start_ctx->peers_left == 0))
504     {
505       pg_start_ctx->connect_start_time = GNUNET_TIME_absolute_get ();
506       pg_start_ctx->expected_connections =
507           GNUNET_TESTING_connect_topology (pg_start_ctx->pg,
508                                            pg_start_ctx->connect_topology,
509                                            pg_start_ctx->connect_topology_option,
510                                            pg_start_ctx->connect_topology_option_modifier,
511                                            pg_start_ctx->connect_timeout,
512                                            pg_start_ctx->connect_attempts, NULL,
513                                            NULL);
514
515       pg_start_ctx->connect_meter =
516           create_meter (pg_start_ctx->expected_connections, "Peer connection ",
517                         pg_start_ctx->verbose);
518       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Have %d expected connections\n",
519                   pg_start_ctx->expected_connections);
520     }
521
522     if (pg_start_ctx->expected_connections == 0)
523     {
524       GNUNET_free_non_null (pg_start_ctx->fail_reason);
525       pg_start_ctx->fail_reason =
526           GNUNET_strdup ("from connect topology (bad return)");
527       pg_start_ctx->die_task =
528           GNUNET_SCHEDULER_add_now (&end_badly, pg_start_ctx);
529       return;
530     }
531
532     GNUNET_free_non_null (pg_start_ctx->fail_reason);
533     pg_start_ctx->fail_reason =
534         GNUNET_strdup ("from connect topology (timeout)");
535     pg_start_ctx->die_task =
536         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
537                                       (pg_start_ctx->timeout), &end_badly,
538                                       pg_start_ctx);
539   }
540 }
541
542 /**
543  * Callback indicating that the hostkey was created for a peer.
544  *
545  * @param cls NULL
546  * @param id the peer identity
547  * @param d the daemon handle (pretty useless at this point, remove?)
548  * @param emsg non-null on failure
549  */
550 static void
551 internal_hostkey_callback (void *cls, const struct GNUNET_PeerIdentity *id,
552                            struct GNUNET_TESTING_Daemon *d, const char *emsg)
553 {
554   struct PeerGroupStartupContext *pg_start_ctx = cls;
555   unsigned int create_expected_connections;
556
557   if (emsg != NULL)
558   {
559     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
560                 "Hostkey callback received error: %s\n", emsg);
561   }
562
563 #if VERBOSE > 1
564   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
565               "Hostkey (%d/%d) created for peer `%s'\n",
566               pg_start_ctx->total - pg_start_ctx->peers_left + 1,
567               pg_start_ctx->total, GNUNET_i2s (id));
568 #endif
569
570   pg_start_ctx->peers_left--;
571   if (GNUNET_YES == update_meter (pg_start_ctx->hostkey_meter))
572   {
573     GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
574     GNUNET_free_non_null (pg_start_ctx->fail_reason);
575     /* Set up task in case topology creation doesn't finish
576      * within a reasonable amount of time */
577     pg_start_ctx->fail_reason = GNUNET_strdup ("from create_topology");
578     pg_start_ctx->die_task =
579         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
580                                       (pg_start_ctx->timeout), &end_badly,
581                                       pg_start_ctx);
582     pg_start_ctx->peers_left = pg_start_ctx->total;     /* Reset counter */
583     create_expected_connections =
584         GNUNET_TESTING_create_topology (pg_start_ctx->pg,
585                                         pg_start_ctx->topology,
586                                         pg_start_ctx->restrict_topology,
587                                         pg_start_ctx->restrict_transports);
588     if (create_expected_connections > 0)
589     {
590       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
591                   "Topology set up, have %u expected connections, now starting peers!\n",
592                   create_expected_connections);
593       GNUNET_TESTING_daemons_continue_startup (pg_start_ctx->pg);
594     }
595     else
596     {
597       GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
598       GNUNET_free_non_null (pg_start_ctx->fail_reason);
599       pg_start_ctx->fail_reason =
600           GNUNET_strdup ("from create topology (bad return)");
601       pg_start_ctx->die_task =
602           GNUNET_SCHEDULER_add_now (&end_badly, pg_start_ctx);
603       return;
604     }
605
606     GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
607     GNUNET_free_non_null (pg_start_ctx->fail_reason);
608     pg_start_ctx->fail_reason =
609         GNUNET_strdup ("from continue startup (timeout)");
610     pg_start_ctx->die_task =
611         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
612                                       (pg_start_ctx->timeout), &end_badly,
613                                       pg_start_ctx);
614   }
615 }
616
617
618 /**
619  * Prototype of a callback function indicating that two peers
620  * are currently connected.
621  *
622  * @param cls closure
623  * @param first peer id for first daemon
624  * @param second peer id for the second daemon
625  * @param emsg error message (NULL on success)
626  */
627 void
628 write_topology_cb (void *cls, const struct GNUNET_PeerIdentity *first,
629                    const struct GNUNET_PeerIdentity *second, const char *emsg)
630 {
631   struct TopologyOutputContext *topo_ctx;
632   int temp;
633   char *temp_str;
634   char *temp_pid2;
635
636   topo_ctx = (struct TopologyOutputContext *) cls;
637   GNUNET_assert (topo_ctx->file != NULL);
638   if ((emsg == NULL) && (first != NULL) && (second != NULL))
639   {
640     GNUNET_assert (first != NULL);
641     GNUNET_assert (second != NULL);
642     temp_pid2 = GNUNET_strdup (GNUNET_i2s (second));
643     temp =
644         GNUNET_asprintf (&temp_str, "\t\"%s\" -- \"%s\"\n", GNUNET_i2s (first),
645                          temp_pid2);
646     GNUNET_free (temp_pid2);
647     GNUNET_DISK_file_write (topo_ctx->file, temp_str, temp);
648   }
649   else if ((emsg == NULL) && (first == NULL) && (second == NULL))
650   {
651     temp = GNUNET_asprintf (&temp_str, "}\n");
652     GNUNET_DISK_file_write (topo_ctx->file, temp_str, temp);
653     GNUNET_DISK_file_close (topo_ctx->file);
654     topo_ctx->notify_cb (topo_ctx->notify_cb_cls, NULL);
655     GNUNET_free (topo_ctx);
656   }
657   else
658   {
659     temp = GNUNET_asprintf (&temp_str, "}\n");
660     GNUNET_DISK_file_write (topo_ctx->file, temp_str, temp);
661     GNUNET_DISK_file_close (topo_ctx->file);
662     topo_ctx->notify_cb (topo_ctx->notify_cb_cls, emsg);
663     GNUNET_free (topo_ctx);
664   }
665 }
666
667 /**
668  * Print current topology to a graphviz readable file.
669  *
670  * @param pg a currently running peergroup to print to file
671  * @param output_filename the file to write the topology to
672  * @param notify_cb callback to call upon completion or failure
673  * @param notify_cb_cls closure for notify_cb
674  *
675  */
676 void
677 GNUNET_TESTING_peergroup_topology_to_file (struct GNUNET_TESTING_PeerGroup *pg,
678                                            const char *output_filename,
679                                            GNUNET_TESTING_NotifyCompletion
680                                            notify_cb, void *notify_cb_cls)
681 {
682   struct TopologyOutputContext *topo_ctx;
683   int temp;
684   char *temp_str;
685
686   topo_ctx = GNUNET_malloc (sizeof (struct TopologyOutputContext));
687
688   topo_ctx->notify_cb = notify_cb;
689   topo_ctx->notify_cb_cls = notify_cb_cls;
690   topo_ctx->file =
691       GNUNET_DISK_file_open (output_filename,
692                              GNUNET_DISK_OPEN_READWRITE |
693                              GNUNET_DISK_OPEN_CREATE,
694                              GNUNET_DISK_PERM_USER_READ |
695                              GNUNET_DISK_PERM_USER_WRITE);
696   if (topo_ctx->file == NULL)
697   {
698     notify_cb (notify_cb_cls, "Failed to open output file!");
699     GNUNET_free (topo_ctx);
700     return;
701   }
702
703   temp = GNUNET_asprintf (&temp_str, "strict graph G {\n");
704   if (temp > 0)
705     GNUNET_DISK_file_write (topo_ctx->file, temp_str, temp);
706   GNUNET_free_non_null (temp_str);
707   GNUNET_TESTING_get_topology (pg, &write_topology_cb, topo_ctx);
708 }
709
710 /**
711  * Start a peer group with a given number of peers.  Notify
712  * on completion of peer startup and connection based on given
713  * topological constraints.  Optionally notify on each
714  * established connection.
715  *
716  * @param cfg configuration template to use
717  * @param total number of daemons to start
718  * @param timeout total time allowed for peers to start
719  * @param connect_cb function to call each time two daemons are connected
720  * @param peergroup_cb function to call once all peers are up and connected
721  * @param peergroup_cls closure for peergroup callbacks
722  * @param hostnames linked list of host structs to use to start peers on
723  *                  (NULL to run on localhost only)
724  *
725  * @return NULL on error, otherwise handle to control peer group
726  */
727 struct GNUNET_TESTING_PeerGroup *
728 GNUNET_TESTING_peergroup_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
729                                 unsigned int total,
730                                 struct GNUNET_TIME_Relative timeout,
731                                 GNUNET_TESTING_NotifyConnection connect_cb,
732                                 GNUNET_TESTING_NotifyCompletion peergroup_cb,
733                                 void *peergroup_cls,
734                                 const struct GNUNET_TESTING_Host *hostnames)
735 {
736   struct PeerGroupStartupContext *pg_start_ctx;
737   unsigned long long temp_config_number;
738   char *temp_str;
739   int temp;
740   struct GNUNET_TIME_Relative rtimeout;
741
742   GNUNET_assert (total > 0);
743   GNUNET_assert (cfg != NULL);
744
745   pg_start_ctx = GNUNET_malloc (sizeof (struct PeerGroupStartupContext));
746
747   if (GNUNET_OK !=
748       GNUNET_CONFIGURATION_get_value_number (cfg, "testing", "connect_attempts",
749                                              &pg_start_ctx->connect_attempts))
750   {
751     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Must provide option %s:%s!\n",
752                 "testing", "connect_attempts");
753     GNUNET_free (pg_start_ctx);
754     return NULL;
755   }
756
757   if (GNUNET_OK !=
758       GNUNET_CONFIGURATION_get_value_time (cfg, "testing", "CONNECT_TIMEOUT",
759                                            &pg_start_ctx->connect_timeout))
760   {
761       pg_start_ctx->connect_timeout = DEFAULT_CONNECT_TIMEOUT;
762   }
763
764   if (GNUNET_OK !=
765       GNUNET_CONFIGURATION_get_value_number (cfg, "testing",
766                                              "max_outstanding_connections",
767                                              &pg_start_ctx->max_concurrent_connections))
768   {
769     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Must provide option %s:%s!\n",
770                 "testing", "max_outstanding_connections");
771     GNUNET_free (pg_start_ctx);
772     return NULL;
773   }
774
775   if (GNUNET_OK !=
776       GNUNET_CONFIGURATION_get_value_number (cfg, "testing",
777                                              "max_concurrent_ssh",
778                                              &pg_start_ctx->max_concurrent_ssh))
779   {
780     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Must provide option %s:%s!\n",
781                 "testing", "max_concurrent_ssh");
782     GNUNET_free (pg_start_ctx);
783     return NULL;
784   }
785
786   if (GNUNET_SYSERR ==
787       (pg_start_ctx->verbose =
788        GNUNET_CONFIGURATION_get_value_yesno (cfg, "testing",
789                                              "use_progressbars")))
790   {
791     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Must provide option %s:%s!\n",
792                 "testing", "use_progressbars");
793     GNUNET_free (pg_start_ctx);
794     return NULL;
795   }
796
797   if (GNUNET_OK !=
798       GNUNET_CONFIGURATION_get_value_number (cfg, "testing",
799                                              "PEERGROUP_TIMEOUT",
800                                              &rtimeout))
801   {
802     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Must provide option %s:%s!\n",
803                 "testing", "PEERGROUP_TIMEOUT");
804     GNUNET_free (pg_start_ctx);
805     return NULL;
806   }
807   pg_start_ctx->timeout =
808     GNUNET_TIME_relative_to_absolute (rtimeout);
809
810
811   /* Read topology related options from the configuration file */
812   temp_str = NULL;
813   if ((GNUNET_YES ==
814        GNUNET_CONFIGURATION_get_value_string (cfg, "testing", "topology",
815                                               &temp_str)) &&
816       (GNUNET_NO ==
817        GNUNET_TESTING_topology_get (&pg_start_ctx->topology, temp_str)))
818   {
819     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
820                 "Invalid topology `%s' given for section %s option %s\n",
821                 temp_str, "TESTING", "TOPOLOGY");
822     pg_start_ctx->topology = GNUNET_TESTING_TOPOLOGY_CLIQUE;    /* Defaults to NONE, so set better default here */
823   }
824   GNUNET_free_non_null (temp_str);
825
826   if (GNUNET_YES ==
827       GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
828                                              "topology_output_file", &temp_str))
829   {
830     pg_start_ctx->topology_output_file =
831         GNUNET_DISK_file_open (temp_str,
832                                GNUNET_DISK_OPEN_READWRITE |
833                                GNUNET_DISK_OPEN_CREATE,
834                                GNUNET_DISK_PERM_USER_READ |
835                                GNUNET_DISK_PERM_USER_WRITE);
836     if (pg_start_ctx->topology_output_file != NULL)
837     {
838       GNUNET_free (temp_str);
839       temp = GNUNET_asprintf (&temp_str, "strict graph G {\n");
840       if (temp > 0)
841         GNUNET_DISK_file_write (pg_start_ctx->topology_output_file, temp_str,
842                                 temp);
843     }
844     GNUNET_free (temp_str);
845   }
846
847   if (GNUNET_OK !=
848       GNUNET_CONFIGURATION_get_value_string (cfg, "testing", "percentage",
849                                              &temp_str))
850     pg_start_ctx->topology_percentage = 0.5;
851   else
852   {
853     pg_start_ctx->topology_percentage = atof (temp_str);
854     GNUNET_free (temp_str);
855   }
856
857   if (GNUNET_OK !=
858       GNUNET_CONFIGURATION_get_value_string (cfg, "testing", "probability",
859                                              &temp_str))
860     pg_start_ctx->topology_probability = 0.5;
861   else
862   {
863     pg_start_ctx->topology_probability = atof (temp_str);
864     GNUNET_free (temp_str);
865   }
866
867   if ((GNUNET_YES ==
868        GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
869                                               "connect_topology", &temp_str)) &&
870       (GNUNET_NO ==
871        GNUNET_TESTING_topology_get (&pg_start_ctx->connect_topology, temp_str)))
872   {
873     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
874                 "Invalid connect topology `%s' given for section %s option %s\n",
875                 temp_str, "TESTING", "CONNECT_TOPOLOGY");
876   }
877   GNUNET_free_non_null (temp_str);
878
879   if ((GNUNET_YES ==
880        GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
881                                               "connect_topology_option",
882                                               &temp_str)) &&
883       (GNUNET_NO ==
884        GNUNET_TESTING_topology_option_get
885        (&pg_start_ctx->connect_topology_option, temp_str)))
886   {
887     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
888                 "Invalid connect topology option `%s' given for section %s option %s\n",
889                 temp_str, "TESTING", "CONNECT_TOPOLOGY_OPTION");
890     pg_start_ctx->connect_topology_option = GNUNET_TESTING_TOPOLOGY_OPTION_ALL; /* Defaults to NONE, set to ALL */
891   }
892   GNUNET_free_non_null (temp_str);
893
894   if (GNUNET_YES ==
895       GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
896                                              "connect_topology_option_modifier",
897                                              &temp_str))
898   {
899     if (sscanf
900         (temp_str, "%lf", &pg_start_ctx->connect_topology_option_modifier) != 1)
901     {
902       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
903                   _
904                   ("Invalid value `%s' for option `%s' in section `%s': expected float\n"),
905                   temp_str, "connect_topology_option_modifier", "TESTING");
906       GNUNET_free (temp_str);
907       GNUNET_free (pg_start_ctx);
908       return NULL;
909     }
910     GNUNET_free (temp_str);
911   }
912
913   if (GNUNET_YES !=
914       GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
915                                              "blacklist_transports",
916                                              &pg_start_ctx->restrict_transports))
917     pg_start_ctx->restrict_transports = NULL;
918
919   pg_start_ctx->restrict_topology = GNUNET_TESTING_TOPOLOGY_NONE;
920   if ((GNUNET_YES ==
921        GNUNET_CONFIGURATION_get_value_string (cfg, "testing",
922                                               "blacklist_topology", &temp_str))
923       && (GNUNET_NO ==
924           GNUNET_TESTING_topology_get (&pg_start_ctx->restrict_topology,
925                                        temp_str)))
926   {
927     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
928                 "Invalid topology `%s' given for section %s option %s\n",
929                 temp_str, "TESTING", "BLACKLIST_TOPOLOGY");
930   }
931
932   GNUNET_free_non_null (temp_str);
933
934   pg_start_ctx->cfg = cfg;
935   pg_start_ctx->total = total;
936   pg_start_ctx->peers_left = total;
937   pg_start_ctx->connect_cb = connect_cb;
938   pg_start_ctx->peergroup_cb = peergroup_cb;
939   pg_start_ctx->cls = peergroup_cls;
940   pg_start_ctx->hostnames = hostnames;
941   pg_start_ctx->hostkey_meter =
942       create_meter (pg_start_ctx->peers_left, "Hostkeys created ",
943                     pg_start_ctx->verbose);
944   pg_start_ctx->peer_start_meter =
945       create_meter (pg_start_ctx->peers_left, "Peers started ",
946                     pg_start_ctx->verbose);
947   /* Make compilers happy */
948   reset_meter (pg_start_ctx->peer_start_meter);
949   pg_start_ctx->fail_reason =
950       GNUNET_strdup
951       ("didn't generate all hostkeys within allowed startup time!");
952   pg_start_ctx->die_task =
953       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining
954                                     (pg_start_ctx->timeout), &end_badly,
955                                     pg_start_ctx);
956
957   pg_start_ctx->pg =
958       GNUNET_TESTING_daemons_start (pg_start_ctx->cfg, pg_start_ctx->peers_left,
959                                     pg_start_ctx->max_concurrent_connections,
960                                     pg_start_ctx->max_concurrent_ssh,
961                                     GNUNET_TIME_absolute_get_remaining
962                                     (pg_start_ctx->timeout),
963                                     &internal_hostkey_callback, pg_start_ctx,
964                                     &internal_peers_started_callback,
965                                     pg_start_ctx, &internal_topology_callback,
966                                     pg_start_ctx, pg_start_ctx->hostnames);
967
968   return pg_start_ctx->pg;
969 }
970
971 /* end of testing_peergroup.c */