2 This file is part of GNUnet
3 (C) 2008-2011 Christian Grothoff (and other contributing authors)
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.
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.
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.
22 * @file testing/testing_peergroup.c
23 * @brief API implementation for easy peer group creation
24 * @author Nathan Evans
25 * @author Christian Grothoff
29 #include "gnunet_constants.h"
30 #include "gnunet_arm_service.h"
31 #include "gnunet_testing_lib.h"
32 #include "gnunet_core_service.h"
35 #define DEFAULT_CONNECT_TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 30)
37 #define DEFAULT_CONNECT_ATTEMPTS 2
39 /** Struct definitions **/
41 struct PeerGroupStartupContext
43 struct GNUNET_TESTING_PeerGroup *pg;
44 const struct GNUNET_CONFIGURATION_Handle *cfg;
46 unsigned int peers_left;
47 unsigned int max_concurrent_connections;
48 unsigned int max_concurrent_ssh;
49 struct GNUNET_TIME_Absolute timeout;
50 GNUNET_TESTING_NotifyConnection connect_cb;
52 GNUNET_TESTING_NotifyCompletion peergroup_cb;
53 void *peergroup_cb_cls;
54 const struct GNUNET_TESTING_Host *hostnames;
55 enum GNUNET_TESTING_Topology topology;
56 enum GNUNET_TESTING_Topology restrict_topology;
57 const char *restrict_transports;
58 enum GNUNET_TESTING_Topology connect_topology;
59 enum GNUNET_TESTING_TopologyOption connect_topology_option;
60 double connect_topology_option_modifier;
63 struct ProgressMeter *hostkey_meter;
64 struct ProgressMeter *peer_start_meter;
65 struct ProgressMeter *connect_meter;
68 * Task used to kill the peergroup.
70 GNUNET_SCHEDULER_TaskIdentifier die_task;
75 * Variable used to store the number of connections we should wait for.
77 unsigned int expected_connections;
80 * Time when the connecting peers was started.
82 struct GNUNET_TIME_Absolute connect_start_time;
85 * The total number of connections that have been created so far.
87 unsigned int total_connections;
90 * The total number of connections that have failed so far.
92 unsigned int failed_connections;
96 * Simple struct to keep track of progress, and print a
97 * percentage meter for long running tasks.
102 * Total number of tasks to complete.
107 * Print percentage done after modnum tasks.
112 * Print a . each dotnum tasks.
117 * Total number completed thus far.
119 unsigned int completed;
122 * Whether or not to print.
127 * Startup string for progress meter.
129 char *startup_string;
133 /** Utility functions **/
136 * Create a meter to keep track of the progress of some task.
138 * @param total the total number of items to complete
139 * @param start_string a string to prefix the meter with (if printing)
140 * @param print GNUNET_YES to print the meter, GNUNET_NO to count
143 * @return the progress meter
145 static struct ProgressMeter *
146 create_meter(unsigned int total, char * start_string, int print)
148 struct ProgressMeter *ret;
149 ret = GNUNET_malloc(sizeof(struct ProgressMeter));
152 ret->modnum = total / 4;
153 ret->dotnum = (total / 50) + 1;
154 if (start_string != NULL)
155 ret->startup_string = GNUNET_strdup(start_string);
157 ret->startup_string = GNUNET_strdup("");
163 * Update progress meter (increment by one).
165 * @param meter the meter to update and print info for
167 * @return GNUNET_YES if called the total requested,
168 * GNUNET_NO if more items expected
171 update_meter(struct ProgressMeter *meter)
173 if (meter->print == GNUNET_YES)
175 if (meter->completed % meter->modnum == 0)
177 if (meter->completed == 0)
179 fprintf (stdout, "%sProgress: [0%%", meter->startup_string);
182 fprintf (stdout, "%d%%", (int) (((float) meter->completed
183 / meter->total) * 100));
185 else if (meter->completed % meter->dotnum == 0)
186 fprintf (stdout, ".");
188 if (meter->completed + 1 == meter->total)
189 fprintf (stdout, "%d%%]\n", 100);
194 if (meter->completed == meter->total)
200 * Reset progress meter.
202 * @param meter the meter to reset
204 * @return GNUNET_YES if meter reset,
205 * GNUNET_SYSERR on error
208 reset_meter(struct ProgressMeter *meter)
211 return GNUNET_SYSERR;
213 meter->completed = 0;
218 * Release resources for meter
220 * @param meter the meter to free
223 free_meter(struct ProgressMeter *meter)
225 GNUNET_free_non_null (meter->startup_string);
230 /** Functions for creating, starting and connecting the peergroup **/
233 * Check whether peers successfully shut down.
236 internal_shutdown_callback(void *cls, const char *emsg)
238 struct PeerGroupStartupContext *pg_start_ctx = cls;
240 pg_start_ctx->peergroup_cb(pg_start_ctx->peergroup_cb_cls, emsg);
242 pg_start_ctx->peergroup_cb(pg_start_ctx->peergroup_cb_cls, pg_start_ctx->fail_reason);
246 * Check if the get_handle is being used, if so stop the request. Either
247 * way, schedule the end_badly_cont function which actually shuts down the
251 end_badly(void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc)
253 struct PeerGroupStartupContext *pg_start_ctx = cls;
254 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failing peer group startup with error: `%s'!\n",
255 pg_start_ctx->fail_reason);
257 GNUNET_TESTING_daemons_stop (pg_start_ctx->pg, GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout), &internal_shutdown_callback, pg_start_ctx);
259 if (pg_start_ctx->hostkey_meter != NULL)
260 free_meter (pg_start_ctx->hostkey_meter);
261 if (pg_start_ctx->peer_start_meter != NULL)
262 free_meter (pg_start_ctx->peer_start_meter);
263 if (pg_start_ctx->connect_meter != NULL)
264 free_meter (pg_start_ctx->connect_meter);
268 * This function is called whenever a connection attempt is finished between two of
269 * the started peers (started with GNUNET_TESTING_daemons_start). The total
270 * number of times this function is called should equal the number returned
271 * from the GNUNET_TESTING_connect_topology call.
273 * The emsg variable is NULL on success (peers connected), and non-NULL on
274 * failure (peers failed to connect).
277 internal_topology_callback(
279 const struct GNUNET_PeerIdentity *first,
280 const struct GNUNET_PeerIdentity *second,
282 const struct GNUNET_CONFIGURATION_Handle *first_cfg,
283 const struct GNUNET_CONFIGURATION_Handle *second_cfg,
284 struct GNUNET_TESTING_Daemon *first_daemon,
285 struct GNUNET_TESTING_Daemon *second_daemon,
288 struct PeerGroupStartupContext *pg_start_ctx = cls;
290 unsigned long long duration;
291 unsigned long long total_duration;
292 unsigned int new_connections;
293 unsigned int new_failed_connections;
294 double conns_per_sec_recent;
295 double conns_per_sec_total;
296 double failed_conns_per_sec_recent;
297 double failed_conns_per_sec_total;
301 if (GNUNET_TIME_absolute_get_difference (connect_last_time,
302 GNUNET_TIME_absolute_get ()).rel_value
303 > GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
304 CONN_UPDATE_DURATION).rel_value)
306 /* Get number of new connections */
307 new_connections = total_connections - previous_connections;
309 /* Get number of new FAILED connections */
310 new_failed_connections = failed_connections - previous_failed_connections;
312 /* Get duration in seconds */
314 = GNUNET_TIME_absolute_get_difference (connect_last_time,
315 GNUNET_TIME_absolute_get ()).rel_value
318 = GNUNET_TIME_absolute_get_difference (connect_start_time,
319 GNUNET_TIME_absolute_get ()).rel_value
322 failed_conns_per_sec_recent = (double) new_failed_connections / duration;
323 failed_conns_per_sec_total = (double) failed_connections / total_duration;
324 conns_per_sec_recent = (double) new_connections / duration;
325 conns_per_sec_total = (double) total_connections / total_duration;
327 GNUNET_ERROR_TYPE_WARNING,
328 "Recent: %.2f/s, Total: %.2f/s, Recent failed: %.2f/s, total failed %.2f/s\n",
329 conns_per_sec_recent, CONN_UPDATE_DURATION,
330 conns_per_sec_total, failed_conns_per_sec_recent,
331 failed_conns_per_sec_total);
332 connect_last_time = GNUNET_TIME_absolute_get ();
333 previous_connections = total_connections;
334 previous_failed_connections = failed_connections;
335 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
336 "have %u total_connections, %u failed\n", total_connections,
344 pg_start_ctx->total_connections++;
346 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "connected peer %s to peer %s, distance %u\n",
347 first_daemon->shortname,
348 second_daemon->shortname,
354 pg_start_ctx->failed_connections++;
356 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to connect peer %s to peer %s with error :\n%s\n",
357 first_daemon->shortname,
358 second_daemon->shortname, emsg);
360 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to connect peer %s to peer %s with error :\n%s\n",
361 first_daemon->shortname,
362 second_daemon->shortname, emsg);
366 GNUNET_assert(pg_start_ctx->connect_meter != NULL);
367 if (pg_start_ctx->connect_cb != NULL)
368 pg_start_ctx->connect_cb(pg_start_ctx->connect_cb_cls, first,
376 if (GNUNET_YES == update_meter (pg_start_ctx->connect_meter))
379 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
380 "Created %d total connections, which is our target number! Starting next phase of testing.\n",
386 = GNUNET_TIME_absolute_get_difference (connect_start_time,
387 GNUNET_TIME_absolute_get ()).rel_value
389 failed_conns_per_sec_total = (double) failed_connections / total_duration;
390 conns_per_sec_total = (double) total_connections / total_duration;
391 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
392 "Overall connection info --- Total: %u, Total Failed %u/s\n",
393 total_connections, failed_connections);
395 GNUNET_ERROR_TYPE_WARNING,
396 "Overall connection info --- Total: %.2f/s, Total Failed %.2f/s\n",
397 conns_per_sec_total, failed_conns_per_sec_total);
400 GNUNET_assert(pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK);
401 GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
403 /* Call final callback, signifying that the peer group has been started and connected */
408 internal_peers_started_callback(void *cls, const struct GNUNET_PeerIdentity *id,
409 const struct GNUNET_CONFIGURATION_Handle *cfg,
410 struct GNUNET_TESTING_Daemon *d, const char *emsg)
412 struct PeerGroupStartupContext *pg_start_ctx = cls;
415 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
416 "Failed to start daemon with error: `%s'\n", emsg);
419 GNUNET_assert (id != NULL);
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Started daemon %llu out of %llu\n",
423 (num_peers - peers_left) + 1, num_peers);
426 pg_start_ctx->peers_left--;
428 if (GNUNET_YES == update_meter (pg_start_ctx->peer_start_meter))
431 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432 "All %d daemons started, now connecting peers!\n",
435 GNUNET_assert(pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK);
436 GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
438 pg_start_ctx->expected_connections = UINT_MAX;
439 if ((pg_start_ctx->pg != NULL) && (pg_start_ctx->peers_left == 0))
441 pg_start_ctx->connect_start_time = GNUNET_TIME_absolute_get ();
442 pg_start_ctx->expected_connections
443 = GNUNET_TESTING_connect_topology (
445 pg_start_ctx->connect_topology,
446 pg_start_ctx->connect_topology_option,
447 pg_start_ctx->connect_topology_option_modifier,
448 DEFAULT_CONNECT_TIMEOUT,
449 DEFAULT_CONNECT_ATTEMPTS,
452 pg_start_ctx->connect_meter
453 = create_meter (pg_start_ctx->expected_connections,
454 "Peer connection ", pg_start_ctx->verbose);
455 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
456 "Have %d expected connections\n",
457 pg_start_ctx->expected_connections);
460 if (pg_start_ctx->expected_connections == 0)
462 GNUNET_free_non_null(pg_start_ctx->fail_reason);
463 pg_start_ctx->fail_reason = GNUNET_strdup("from connect topology (bad return)");
464 pg_start_ctx->die_task
465 = GNUNET_SCHEDULER_add_now (&end_badly,
469 GNUNET_free_non_null(pg_start_ctx->fail_reason);
470 pg_start_ctx->fail_reason = GNUNET_strdup("from connect topology (timeout)");
471 pg_start_ctx->die_task
472 = GNUNET_SCHEDULER_add_delayed (
473 GNUNET_TIME_absolute_get_remaining (pg_start_ctx->timeout),
480 * Callback indicating that the hostkey was created for a peer.
483 * @param id the peer identity
484 * @param d the daemon handle (pretty useless at this point, remove?)
485 * @param emsg non-null on failure
488 internal_hostkey_callback(void *cls, const struct GNUNET_PeerIdentity *id,
489 struct GNUNET_TESTING_Daemon *d, const char *emsg)
491 struct PeerGroupStartupContext *pg_start_ctx = cls;
492 unsigned int create_expected_connections;
496 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
497 "Hostkey callback received error: %s\n", emsg);
501 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502 "Hostkey (%d/%d) created for peer `%s'\n",
503 num_peers - peers_left, num_peers, GNUNET_i2s(id));
506 pg_start_ctx->peers_left--;
507 if (GNUNET_YES == update_meter (pg_start_ctx->hostkey_meter))
509 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
510 "All %d hostkeys created, now creating topology!\n",
511 pg_start_ctx->total);
512 GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
513 /* Set up task in case topology creation doesn't finish
514 * within a reasonable amount of time */
515 pg_start_ctx->die_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout),
517 "from create_topology");
518 pg_start_ctx->peers_left = pg_start_ctx->total; /* Reset counter */
519 create_expected_connections = GNUNET_TESTING_create_topology (pg_start_ctx->pg, pg_start_ctx->topology, pg_start_ctx->restrict_topology,
520 pg_start_ctx->restrict_transports);
521 if (create_expected_connections > 0)
523 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
524 "Topology set up, have %u expected connections, now starting peers!\n", create_expected_connections);
525 GNUNET_TESTING_daemons_continue_startup (pg_start_ctx->pg);
529 GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
530 pg_start_ctx->die_task = GNUNET_SCHEDULER_add_now (&end_badly,
531 "from create topology (bad return)");
534 GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task);
535 pg_start_ctx->die_task
536 = GNUNET_SCHEDULER_add_delayed (
537 GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout),
539 "from continue startup (timeout)");
545 * Start a peer group with a given number of peers. Notify
546 * on completion of peer startup and connection based on given
547 * topological constraints. Optionally notify on each
548 * established connection.
550 * @param cfg configuration template to use
551 * @param total number of daemons to start
552 * @param max_concurrent_connections for testing, how many peers can
553 * we connect to simultaneously
554 * @param max_concurrent_ssh when starting with ssh, how many ssh
555 * connections will we allow at once (based on remote hosts allowed!)
556 * @param timeout total time allowed for peers to start
557 * @param connect_cb function to call each time two daemons are connected
558 * @param connect_cb_cls closure for connect_callback
559 * @param peergroup_cb function to call once all peers are up and connected
560 * @param peergroup_cb_cls closure for peergroup_cb
561 * @param hostnames linked list of host structs to use to start peers on
562 * (NULL to run on localhost only)
563 * @param topology allowed overlay topology
564 * @param restrict_topology blacklist connections to this topology
565 * @param restrict_transports specific transports to blacklist
566 * @param connect_topology topology to connect peers in (defaults to allowed
568 * @param connect_topology_options options for connect topology
569 * @param connect_topology_option_modifier option modifier for connect topology
570 * @param verbose GNUNET_YES to print progress bars, GNUNET_NO otherwise
572 * @return NULL on error, otherwise handle to control peer group
574 struct GNUNET_TESTING_PeerGroup *
575 GNUNET_TESTING_PeerGroup_start(
576 const struct GNUNET_CONFIGURATION_Handle *cfg,
578 unsigned int max_concurrent_connections,
579 unsigned int max_concurrent_ssh,
580 struct GNUNET_TIME_Relative timeout,
581 GNUNET_TESTING_NotifyConnection connect_cb,
582 void *connect_cb_cls,
583 GNUNET_TESTING_NotifyCompletion peergroup_cb,
584 void *peergroup_cb_cls,
585 const struct GNUNET_TESTING_Host *hostnames,
586 enum GNUNET_TESTING_Topology topology,
587 enum GNUNET_TESTING_Topology restrict_topology,
588 const char *restrict_transports,
589 enum GNUNET_TESTING_Topology connect_topology,
590 enum GNUNET_TESTING_TopologyOption connect_topology_options,
591 double connect_topology_option_modifier, int verbose)
593 struct PeerGroupStartupContext *pg_start_ctx;
595 GNUNET_assert(total > 0);
596 GNUNET_assert(cfg != NULL);
598 pg_start_ctx = GNUNET_malloc(sizeof(struct PeerGroupStartupContext));
599 pg_start_ctx->cfg = cfg;
600 pg_start_ctx->total = total;
601 pg_start_ctx->peers_left = total;
602 pg_start_ctx->max_concurrent_connections = max_concurrent_connections;
603 pg_start_ctx->max_concurrent_ssh = max_concurrent_ssh;
604 pg_start_ctx->timeout = GNUNET_TIME_relative_to_absolute(timeout);
605 pg_start_ctx->connect_cb = connect_cb_cls;
606 pg_start_ctx->peergroup_cb = peergroup_cb;
607 pg_start_ctx->peergroup_cb_cls = peergroup_cb_cls;
608 pg_start_ctx->hostnames = hostnames;
609 pg_start_ctx->topology = topology;
610 pg_start_ctx->restrict_topology = restrict_topology;
611 pg_start_ctx->restrict_transports = restrict_transports;
612 pg_start_ctx->connect_topology = connect_topology;
613 pg_start_ctx->connect_topology_option = connect_topology_options;
614 pg_start_ctx->connect_topology_option_modifier = connect_topology_option_modifier;
615 pg_start_ctx->verbose = verbose;
616 pg_start_ctx->hostkey_meter = create_meter (pg_start_ctx->peers_left, "Hostkeys created ", pg_start_ctx->verbose);
617 pg_start_ctx->peer_start_meter = create_meter (pg_start_ctx->peers_left, "Peers started ", pg_start_ctx->verbose);
618 /* Make compilers happy */
619 reset_meter(pg_start_ctx->peer_start_meter);
620 pg_start_ctx->die_task
621 = GNUNET_SCHEDULER_add_delayed (
622 GNUNET_TIME_absolute_get_remaining (
623 pg_start_ctx->timeout),
625 "didn't generate all hostkeys within allowed startup time!");
628 = GNUNET_TESTING_daemons_start (
630 pg_start_ctx->peers_left,
631 pg_start_ctx->max_concurrent_connections,
632 pg_start_ctx->max_concurrent_ssh,
633 GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout),
634 &internal_hostkey_callback, pg_start_ctx,
635 &internal_peers_started_callback,
637 &internal_topology_callback,
638 pg_start_ctx, pg_start_ctx->hostnames);
640 return pg_start_ctx->pg;
644 /* end of testing_peergroup.c */