From 8da74da33f616eecdba93e9c94715a3e6244876f Mon Sep 17 00:00:00 2001 From: "Nathan S. Evans" Date: Tue, 5 Apr 2011 14:37:56 +0000 Subject: initial peergroup convenience implementation --- src/testing/Makefile.am | 3 +- src/testing/test_testing_group_remote.c | 2 +- src/testing/testing_peergroup.c | 644 ++++++++++++++++++++++++++++++++ 3 files changed, 647 insertions(+), 2 deletions(-) create mode 100644 src/testing/testing_peergroup.c diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 8084a97798..1d7806a2a5 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -13,7 +13,8 @@ lib_LTLIBRARIES = libgnunettesting.la libgnunettesting_la_SOURCES = \ testing.c \ - testing_group.c + testing_group.c \ + testing_peergroup.c libgnunettesting_la_LIBADD = $(XLIB) \ $(top_builddir)/src/core/libgnunetcore.la \ $(top_builddir)/src/statistics/libgnunetstatistics.la \ diff --git a/src/testing/test_testing_group_remote.c b/src/testing/test_testing_group_remote.c index 6a4d0cc1e4..58f3c4143f 100644 --- a/src/testing/test_testing_group_remote.c +++ b/src/testing/test_testing_group_remote.c @@ -56,7 +56,7 @@ shutdown_callback (void *cls, const char *emsg) if (emsg != NULL) { #if VERBOSE - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed!\n"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown of peers failed (error %s)!\n", emsg); #endif if (ok == 0) ok = 666; diff --git a/src/testing/testing_peergroup.c b/src/testing/testing_peergroup.c new file mode 100644 index 0000000000..bd725fedc7 --- /dev/null +++ b/src/testing/testing_peergroup.c @@ -0,0 +1,644 @@ +/* + This file is part of GNUnet + (C) 2008-2011 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + */ + +/** + * @file testing/testing_peergroup.c + * @brief API implementation for easy peer group creation + * @author Nathan Evans + * @author Christian Grothoff + * + */ +#include "platform.h" +#include "gnunet_constants.h" +#include "gnunet_arm_service.h" +#include "gnunet_testing_lib.h" +#include "gnunet_core_service.h" + +/** Globals **/ +#define DEFAULT_CONNECT_TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 30) + +#define DEFAULT_CONNECT_ATTEMPTS 2 + +/** Struct definitions **/ + +struct PeerGroupStartupContext +{ + struct GNUNET_TESTING_PeerGroup *pg; + const struct GNUNET_CONFIGURATION_Handle *cfg; + unsigned int total; + unsigned int peers_left; + unsigned int max_concurrent_connections; + unsigned int max_concurrent_ssh; + struct GNUNET_TIME_Absolute timeout; + GNUNET_TESTING_NotifyConnection connect_cb; + void *connect_cb_cls; + GNUNET_TESTING_NotifyCompletion peergroup_cb; + void *peergroup_cb_cls; + const struct GNUNET_TESTING_Host *hostnames; + enum GNUNET_TESTING_Topology topology; + enum GNUNET_TESTING_Topology restrict_topology; + const char *restrict_transports; + enum GNUNET_TESTING_Topology connect_topology; + enum GNUNET_TESTING_TopologyOption connect_topology_option; + double connect_topology_option_modifier; + int verbose; + + struct ProgressMeter *hostkey_meter; + struct ProgressMeter *peer_start_meter; + struct ProgressMeter *connect_meter; + + /** + * Task used to kill the peergroup. + */ + GNUNET_SCHEDULER_TaskIdentifier die_task; + + char *fail_reason; + + /** + * Variable used to store the number of connections we should wait for. + */ + unsigned int expected_connections; + + /** + * Time when the connecting peers was started. + */ + struct GNUNET_TIME_Absolute connect_start_time; + + /** + * The total number of connections that have been created so far. + */ + unsigned int total_connections; + + /** + * The total number of connections that have failed so far. + */ + unsigned int failed_connections; +}; + +/** + * Simple struct to keep track of progress, and print a + * percentage meter for long running tasks. + */ +struct ProgressMeter +{ + /** + * Total number of tasks to complete. + */ + unsigned int total; + + /** + * Print percentage done after modnum tasks. + */ + unsigned int modnum; + + /** + * Print a . each dotnum tasks. + */ + unsigned int dotnum; + + /** + * Total number completed thus far. + */ + unsigned int completed; + + /** + * Whether or not to print. + */ + int print; + + /** + * Startup string for progress meter. + */ + char *startup_string; +}; + + +/** Utility functions **/ + +/** + * Create a meter to keep track of the progress of some task. + * + * @param total the total number of items to complete + * @param start_string a string to prefix the meter with (if printing) + * @param print GNUNET_YES to print the meter, GNUNET_NO to count + * internally only + * + * @return the progress meter + */ +static struct ProgressMeter * +create_meter(unsigned int total, char * start_string, int print) +{ + struct ProgressMeter *ret; + ret = GNUNET_malloc(sizeof(struct ProgressMeter)); + ret->print = print; + ret->total = total; + ret->modnum = total / 4; + ret->dotnum = (total / 50) + 1; + if (start_string != NULL) + ret->startup_string = GNUNET_strdup(start_string); + else + ret->startup_string = GNUNET_strdup(""); + + return ret; +} + +/** + * Update progress meter (increment by one). + * + * @param meter the meter to update and print info for + * + * @return GNUNET_YES if called the total requested, + * GNUNET_NO if more items expected + */ +static int +update_meter(struct ProgressMeter *meter) +{ + if (meter->print == GNUNET_YES) + { + if (meter->completed % meter->modnum == 0) + { + if (meter->completed == 0) + { + fprintf (stdout, "%sProgress: [0%%", meter->startup_string); + } + else + fprintf (stdout, "%d%%", (int) (((float) meter->completed + / meter->total) * 100)); + } + else if (meter->completed % meter->dotnum == 0) + fprintf (stdout, "."); + + if (meter->completed + 1 == meter->total) + fprintf (stdout, "%d%%]\n", 100); + fflush (stdout); + } + meter->completed++; + + if (meter->completed == meter->total) + return GNUNET_YES; + return GNUNET_NO; +} + +/** + * Reset progress meter. + * + * @param meter the meter to reset + * + * @return GNUNET_YES if meter reset, + * GNUNET_SYSERR on error + */ +static int +reset_meter(struct ProgressMeter *meter) +{ + if (meter == NULL) + return GNUNET_SYSERR; + + meter->completed = 0; + return GNUNET_YES; +} + +/** + * Release resources for meter + * + * @param meter the meter to free + */ +static void +free_meter(struct ProgressMeter *meter) +{ + GNUNET_free_non_null (meter->startup_string); + GNUNET_free (meter); +} + + +/** Functions for creating, starting and connecting the peergroup **/ + +/** + * Check whether peers successfully shut down. + */ +static void +internal_shutdown_callback(void *cls, const char *emsg) +{ + struct PeerGroupStartupContext *pg_start_ctx = cls; + if (emsg != NULL) + pg_start_ctx->peergroup_cb(pg_start_ctx->peergroup_cb_cls, emsg); + else + pg_start_ctx->peergroup_cb(pg_start_ctx->peergroup_cb_cls, pg_start_ctx->fail_reason); +} + +/** + * Check if the get_handle is being used, if so stop the request. Either + * way, schedule the end_badly_cont function which actually shuts down the + * test. + */ +static void +end_badly(void *cls, const struct GNUNET_SCHEDULER_TaskContext * tc) +{ + struct PeerGroupStartupContext *pg_start_ctx = cls; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failing peer group startup with error: `%s'!\n", + pg_start_ctx->fail_reason); + + GNUNET_TESTING_daemons_stop (pg_start_ctx->pg, GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout), &internal_shutdown_callback, pg_start_ctx); + + if (pg_start_ctx->hostkey_meter != NULL) + free_meter (pg_start_ctx->hostkey_meter); + if (pg_start_ctx->peer_start_meter != NULL) + free_meter (pg_start_ctx->peer_start_meter); + if (pg_start_ctx->connect_meter != NULL) + free_meter (pg_start_ctx->connect_meter); +} + +/** + * This function is called whenever a connection attempt is finished between two of + * the started peers (started with GNUNET_TESTING_daemons_start). The total + * number of times this function is called should equal the number returned + * from the GNUNET_TESTING_connect_topology call. + * + * The emsg variable is NULL on success (peers connected), and non-NULL on + * failure (peers failed to connect). + */ +static void +internal_topology_callback( + void *cls, + const struct GNUNET_PeerIdentity *first, + const struct GNUNET_PeerIdentity *second, + uint32_t distance, + const struct GNUNET_CONFIGURATION_Handle *first_cfg, + const struct GNUNET_CONFIGURATION_Handle *second_cfg, + struct GNUNET_TESTING_Daemon *first_daemon, + struct GNUNET_TESTING_Daemon *second_daemon, + const char *emsg) +{ + struct PeerGroupStartupContext *pg_start_ctx = cls; +#if TIMING + unsigned long long duration; + unsigned long long total_duration; + unsigned int new_connections; + unsigned int new_failed_connections; + double conns_per_sec_recent; + double conns_per_sec_total; + double failed_conns_per_sec_recent; + double failed_conns_per_sec_total; +#endif + +#if TIMING + if (GNUNET_TIME_absolute_get_difference (connect_last_time, + GNUNET_TIME_absolute_get ()).rel_value + > GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + CONN_UPDATE_DURATION).rel_value) + { + /* Get number of new connections */ + new_connections = total_connections - previous_connections; + + /* Get number of new FAILED connections */ + new_failed_connections = failed_connections - previous_failed_connections; + + /* Get duration in seconds */ + duration + = GNUNET_TIME_absolute_get_difference (connect_last_time, + GNUNET_TIME_absolute_get ()).rel_value + / 1000; + total_duration + = GNUNET_TIME_absolute_get_difference (connect_start_time, + GNUNET_TIME_absolute_get ()).rel_value + / 1000; + + failed_conns_per_sec_recent = (double) new_failed_connections / duration; + failed_conns_per_sec_total = (double) failed_connections / total_duration; + conns_per_sec_recent = (double) new_connections / duration; + conns_per_sec_total = (double) total_connections / total_duration; + GNUNET_log ( + GNUNET_ERROR_TYPE_WARNING, + "Recent: %.2f/s, Total: %.2f/s, Recent failed: %.2f/s, total failed %.2f/s\n", + conns_per_sec_recent, CONN_UPDATE_DURATION, + conns_per_sec_total, failed_conns_per_sec_recent, + failed_conns_per_sec_total); + connect_last_time = GNUNET_TIME_absolute_get (); + previous_connections = total_connections; + previous_failed_connections = failed_connections; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "have %u total_connections, %u failed\n", total_connections, + failed_connections); + } +#endif + + + if (emsg == NULL) + { + pg_start_ctx->total_connections++; +#if VERBOSE > 1 + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "connected peer %s to peer %s, distance %u\n", + first_daemon->shortname, + second_daemon->shortname, + distance); +#endif + } + else + { + pg_start_ctx->failed_connections++; +#if VERBOSE + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to connect peer %s to peer %s with error :\n%s\n", + first_daemon->shortname, + second_daemon->shortname, emsg); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Failed to connect peer %s to peer %s with error :\n%s\n", + first_daemon->shortname, + second_daemon->shortname, emsg); +#endif + } + + GNUNET_assert(pg_start_ctx->connect_meter != NULL); + if (pg_start_ctx->connect_cb != NULL) + pg_start_ctx->connect_cb(pg_start_ctx->connect_cb_cls, first, + second, + distance, + first_cfg, + second_cfg, + first_daemon, + second_daemon, + emsg); + if (GNUNET_YES == update_meter (pg_start_ctx->connect_meter)) + { +#if VERBOSE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Created %d total connections, which is our target number! Starting next phase of testing.\n", + total_connections); +#endif + +#if TIMING + total_duration + = GNUNET_TIME_absolute_get_difference (connect_start_time, + GNUNET_TIME_absolute_get ()).rel_value + / 1000; + failed_conns_per_sec_total = (double) failed_connections / total_duration; + conns_per_sec_total = (double) total_connections / total_duration; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Overall connection info --- Total: %u, Total Failed %u/s\n", + total_connections, failed_connections); + GNUNET_log ( + GNUNET_ERROR_TYPE_WARNING, + "Overall connection info --- Total: %.2f/s, Total Failed %.2f/s\n", + conns_per_sec_total, failed_conns_per_sec_total); +#endif + + GNUNET_assert(pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK); + GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task); + + /* Call final callback, signifying that the peer group has been started and connected */ + } +} + +static void +internal_peers_started_callback(void *cls, const struct GNUNET_PeerIdentity *id, + const struct GNUNET_CONFIGURATION_Handle *cfg, + struct GNUNET_TESTING_Daemon *d, const char *emsg) +{ + struct PeerGroupStartupContext *pg_start_ctx = cls; + if (emsg != NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Failed to start daemon with error: `%s'\n", emsg); + return; + } + GNUNET_assert (id != NULL); + +#if VERBOSE > 1 + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Started daemon %llu out of %llu\n", + (num_peers - peers_left) + 1, num_peers); +#endif + + pg_start_ctx->peers_left--; + + if (GNUNET_YES == update_meter (pg_start_ctx->peer_start_meter)) + { +#if VERBOSE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "All %d daemons started, now connecting peers!\n", + num_peers); +#endif + GNUNET_assert(pg_start_ctx->die_task != GNUNET_SCHEDULER_NO_TASK); + GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task); + + pg_start_ctx->expected_connections = UINT_MAX; + if ((pg_start_ctx->pg != NULL) && (pg_start_ctx->peers_left == 0)) + { + pg_start_ctx->connect_start_time = GNUNET_TIME_absolute_get (); + pg_start_ctx->expected_connections + = GNUNET_TESTING_connect_topology ( + pg_start_ctx->pg, + pg_start_ctx->connect_topology, + pg_start_ctx->connect_topology_option, + pg_start_ctx->connect_topology_option_modifier, + DEFAULT_CONNECT_TIMEOUT, + DEFAULT_CONNECT_ATTEMPTS, + NULL, NULL); + + pg_start_ctx->connect_meter + = create_meter (pg_start_ctx->expected_connections, + "Peer connection ", pg_start_ctx->verbose); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Have %d expected connections\n", + pg_start_ctx->expected_connections); + } + + if (pg_start_ctx->expected_connections == 0) + { + GNUNET_free_non_null(pg_start_ctx->fail_reason); + pg_start_ctx->fail_reason = GNUNET_strdup("from connect topology (bad return)"); + pg_start_ctx->die_task + = GNUNET_SCHEDULER_add_now (&end_badly, + pg_start_ctx); + } + + GNUNET_free_non_null(pg_start_ctx->fail_reason); + pg_start_ctx->fail_reason = GNUNET_strdup("from connect topology (timeout)"); + pg_start_ctx->die_task + = GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_absolute_get_remaining (pg_start_ctx->timeout), + &end_badly, + pg_start_ctx); + } +} + +/** + * Callback indicating that the hostkey was created for a peer. + * + * @param cls NULL + * @param id the peer identity + * @param d the daemon handle (pretty useless at this point, remove?) + * @param emsg non-null on failure + */ +static void +internal_hostkey_callback(void *cls, const struct GNUNET_PeerIdentity *id, + struct GNUNET_TESTING_Daemon *d, const char *emsg) +{ + struct PeerGroupStartupContext *pg_start_ctx = cls; + unsigned int create_expected_connections; + + if (emsg != NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Hostkey callback received error: %s\n", emsg); + } + +#if VERBOSE > 1 + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Hostkey (%d/%d) created for peer `%s'\n", + num_peers - peers_left, num_peers, GNUNET_i2s(id)); +#endif + + pg_start_ctx->peers_left--; + if (GNUNET_YES == update_meter (pg_start_ctx->hostkey_meter)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "All %d hostkeys created, now creating topology!\n", + pg_start_ctx->total); + GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task); + /* Set up task in case topology creation doesn't finish + * within a reasonable amount of time */ + pg_start_ctx->die_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout), + &end_badly, + "from create_topology"); + pg_start_ctx->peers_left = pg_start_ctx->total; /* Reset counter */ + create_expected_connections = GNUNET_TESTING_create_topology (pg_start_ctx->pg, pg_start_ctx->topology, pg_start_ctx->restrict_topology, + pg_start_ctx->restrict_transports); + if (create_expected_connections > 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Topology set up, have %u expected connections, now starting peers!\n", create_expected_connections); + GNUNET_TESTING_daemons_continue_startup (pg_start_ctx->pg); + } + else + { + GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task); + pg_start_ctx->die_task = GNUNET_SCHEDULER_add_now (&end_badly, + "from create topology (bad return)"); + } + + GNUNET_SCHEDULER_cancel (pg_start_ctx->die_task); + pg_start_ctx->die_task + = GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout), + &end_badly, + "from continue startup (timeout)"); + } +} + + +/** + * Start a peer group with a given number of peers. Notify + * on completion of peer startup and connection based on given + * topological constraints. Optionally notify on each + * established connection. + * + * @param cfg configuration template to use + * @param total number of daemons to start + * @param max_concurrent_connections for testing, how many peers can +* we connect to simultaneously + * @param max_concurrent_ssh when starting with ssh, how many ssh + * connections will we allow at once (based on remote hosts allowed!) + * @param timeout total time allowed for peers to start + * @param connect_cb function to call each time two daemons are connected + * @param connect_cb_cls closure for connect_callback + * @param peergroup_cb function to call once all peers are up and connected + * @param peergroup_cb_cls closure for peergroup_cb + * @param hostnames linked list of host structs to use to start peers on + * (NULL to run on localhost only) + * @param topology allowed overlay topology + * @param restrict_topology blacklist connections to this topology + * @param restrict_transports specific transports to blacklist + * @param connect_topology topology to connect peers in (defaults to allowed + * topology) + * @param connect_topology_options options for connect topology + * @param connect_topology_option_modifier option modifier for connect topology + * @param verbose GNUNET_YES to print progress bars, GNUNET_NO otherwise + * + * @return NULL on error, otherwise handle to control peer group + */ +struct GNUNET_TESTING_PeerGroup * +GNUNET_TESTING_PeerGroup_start( + const struct GNUNET_CONFIGURATION_Handle *cfg, + unsigned int total, + unsigned int max_concurrent_connections, + unsigned int max_concurrent_ssh, + struct GNUNET_TIME_Relative timeout, + GNUNET_TESTING_NotifyConnection connect_cb, + void *connect_cb_cls, + GNUNET_TESTING_NotifyCompletion peergroup_cb, + void *peergroup_cb_cls, + const struct GNUNET_TESTING_Host *hostnames, + enum GNUNET_TESTING_Topology topology, + enum GNUNET_TESTING_Topology restrict_topology, + const char *restrict_transports, + enum GNUNET_TESTING_Topology connect_topology, + enum GNUNET_TESTING_TopologyOption connect_topology_options, + double connect_topology_option_modifier, int verbose) +{ + struct PeerGroupStartupContext *pg_start_ctx; + + GNUNET_assert(total > 0); + GNUNET_assert(cfg != NULL); + + pg_start_ctx = GNUNET_malloc(sizeof(struct PeerGroupStartupContext)); + pg_start_ctx->cfg = cfg; + pg_start_ctx->total = total; + pg_start_ctx->peers_left = total; + pg_start_ctx->max_concurrent_connections = max_concurrent_connections; + pg_start_ctx->max_concurrent_ssh = max_concurrent_ssh; + pg_start_ctx->timeout = GNUNET_TIME_relative_to_absolute(timeout); + pg_start_ctx->connect_cb = connect_cb_cls; + pg_start_ctx->peergroup_cb = peergroup_cb; + pg_start_ctx->peergroup_cb_cls = peergroup_cb_cls; + pg_start_ctx->hostnames = hostnames; + pg_start_ctx->topology = topology; + pg_start_ctx->restrict_topology = restrict_topology; + pg_start_ctx->restrict_transports = restrict_transports; + pg_start_ctx->connect_topology = connect_topology; + pg_start_ctx->connect_topology_option = connect_topology_options; + pg_start_ctx->connect_topology_option_modifier = connect_topology_option_modifier; + pg_start_ctx->verbose = verbose; + pg_start_ctx->hostkey_meter = create_meter (pg_start_ctx->peers_left, "Hostkeys created ", pg_start_ctx->verbose); + pg_start_ctx->peer_start_meter = create_meter (pg_start_ctx->peers_left, "Peers started ", pg_start_ctx->verbose); + /* Make compilers happy */ + reset_meter(pg_start_ctx->peer_start_meter); + pg_start_ctx->die_task + = GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_absolute_get_remaining ( + pg_start_ctx->timeout), + &end_badly, + "didn't generate all hostkeys within allowed startup time!"); + + pg_start_ctx->pg + = GNUNET_TESTING_daemons_start ( + pg_start_ctx->cfg, + pg_start_ctx->peers_left, + pg_start_ctx->max_concurrent_connections, + pg_start_ctx->max_concurrent_ssh, + GNUNET_TIME_absolute_get_remaining(pg_start_ctx->timeout), + &internal_hostkey_callback, pg_start_ctx, + &internal_peers_started_callback, + pg_start_ctx, + &internal_topology_callback, + pg_start_ctx, pg_start_ctx->hostnames); + + return pg_start_ctx->pg; +} + + +/* end of testing_peergroup.c */ -- cgit v1.2.3-18-g5258