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