2 This file is part of GNUnet
3 (C) 2008--2012 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 testbed/testbed_api_hosts.c
23 * @brief API for manipulating 'hosts' controlled by the GNUnet testing service;
24 * allows parsing hosts files, starting, stopping and communicating (via
25 * SSH/stdin/stdout) with the remote (or local) processes
26 * @author Christian Grothoff
29 #include "gnunet_util_lib.h"
30 #include "gnunet_testbed_service.h"
31 #include "gnunet_core_service.h"
32 #include "gnunet_transport_service.h"
34 #include "testbed_api.h"
35 #include "testbed_api_hosts.h"
36 #include "testbed_api_operations.h"
37 #include "testbed_api_sd.h"
40 * Generic logging shorthand
42 #define LOG(kind, ...) \
43 GNUNET_log_from (kind, "testbed-api-hosts", __VA_ARGS__);
46 * Number of extra elements we create space for when we grow host list
48 #define HOST_LIST_GROW_STEP 10
52 * A list entry for registered controllers list
54 struct RegisteredController
57 * The controller at which this host is registered
59 const struct GNUNET_TESTBED_Controller *controller;
62 * The next ptr for DLL
64 struct RegisteredController *next;
67 * The prev ptr for DLL
69 struct RegisteredController *prev;
74 * A slot to record time taken by an overlay connect operation
79 * A key to identify this timeslot
86 struct GNUNET_TIME_Relative time;
89 * Number of timing values accumulated
96 * Opaque handle to a host running experiments managed by the testing framework.
97 * The master process must be able to SSH to this host without password (via
100 struct GNUNET_TESTBED_Host
104 * The next pointer for DLL
106 struct GNUNET_TESTBED_Host *next;
109 * The prev pointer for DLL
111 struct GNUNET_TESTBED_Host *prev;
114 * The hostname of the host; NULL for localhost
116 const char *hostname;
119 * The username to be used for SSH login
121 const char *username;
124 * The head for the list of controllers where this host is registered
126 struct RegisteredController *rc_head;
129 * The tail for the list of controllers where this host is registered
131 struct RegisteredController *rc_tail;
134 * Operation queue for simultaneous overlay connect operations target at this
137 struct OperationQueue *opq_parallel_overlay_connect_operations;
140 * An array of timing slots; size should be equal to the current number of parallel
143 struct TimeSlot *tslots;
146 * Handle for SD calculations amount parallel overlay connect operation finish
149 struct SDHandle *poc_sd;
152 * The number of parallel overlay connects we do currently
154 unsigned int num_parallel_connects;
157 * Counter to indicate when all the available time slots are filled
159 unsigned int tslots_filled;
162 * Global ID we use to refer to a host on the network
167 * The port which is to be used for SSH
175 * Array of available hosts
177 static struct GNUNET_TESTBED_Host **host_list;
180 * The size of the available hosts list
182 static unsigned int host_list_size;
186 * Lookup a host by ID.
188 * @param id global host ID assigned to the host; 0 is
189 * reserved to always mean 'localhost'
190 * @return handle to the host, NULL if host not found
192 struct GNUNET_TESTBED_Host *
193 GNUNET_TESTBED_host_lookup_by_id_ (uint32_t id)
195 if (host_list_size <= id)
197 return host_list[id];
202 * Create a host by ID; given this host handle, we could not
203 * run peers at the host, but we can talk about the host
206 * @param id global host ID assigned to the host; 0 is
207 * reserved to always mean 'localhost'
208 * @return handle to the host, NULL on error
210 struct GNUNET_TESTBED_Host *
211 GNUNET_TESTBED_host_create_by_id_ (uint32_t id)
213 return GNUNET_TESTBED_host_create_with_id (id, NULL, NULL, 0);
218 * Obtain the host's unique global ID.
220 * @param host handle to the host, NULL means 'localhost'
221 * @return id global host ID assigned to the host (0 is
222 * 'localhost', but then obviously not globally unique)
225 GNUNET_TESTBED_host_get_id_ (const struct GNUNET_TESTBED_Host * host)
232 * Obtain the host's hostname.
234 * @param host handle to the host, NULL means 'localhost'
235 * @return hostname of the host
238 GNUNET_TESTBED_host_get_hostname (const struct GNUNET_TESTBED_Host *host)
240 return host->hostname;
245 * Obtain the host's username
247 * @param host handle to the host, NULL means 'localhost'
248 * @return username to login to the host
251 GNUNET_TESTBED_host_get_username_ (const struct GNUNET_TESTBED_Host *host)
253 return host->username;
258 * Obtain the host's ssh port
260 * @param host handle to the host, NULL means 'localhost'
261 * @return username to login to the host
264 GNUNET_TESTBED_host_get_ssh_port_ (const struct GNUNET_TESTBED_Host * host)
271 * Create a host to run peers and controllers on.
273 * @param id global host ID assigned to the host; 0 is
274 * reserved to always mean 'localhost'
275 * @param hostname name of the host, use "NULL" for localhost
276 * @param username username to use for the login; may be NULL
277 * @param port port number to use for ssh; use 0 to let ssh decide
278 * @return handle to the host, NULL on error
280 struct GNUNET_TESTBED_Host *
281 GNUNET_TESTBED_host_create_with_id (uint32_t id, const char *hostname,
282 const char *username, uint16_t port)
284 struct GNUNET_TESTBED_Host *host;
285 unsigned int new_size;
287 if ((id < host_list_size) && (NULL != host_list[id]))
289 LOG (GNUNET_ERROR_TYPE_WARNING, "Host with id: %u already created\n", id);
292 host = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_Host));
293 host->hostname = (NULL != hostname) ? GNUNET_strdup (hostname) : NULL;
294 host->username = (NULL != username) ? GNUNET_strdup (username) : NULL;
296 host->port = (0 == port) ? 22 : port;
297 host->opq_parallel_overlay_connect_operations =
298 GNUNET_TESTBED_operation_queue_create_ (0);
299 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (host, 1);
300 host->poc_sd = GNUNET_TESTBED_SD_init_ (10);
301 new_size = host_list_size;
302 while (id >= new_size)
303 new_size += HOST_LIST_GROW_STEP;
304 if (new_size != host_list_size)
305 GNUNET_array_grow (host_list, host_list_size, new_size);
306 GNUNET_assert (id < host_list_size);
307 LOG (GNUNET_ERROR_TYPE_DEBUG, "Adding host with id: %u\n", host->id);
308 host_list[id] = host;
314 * Create a host to run peers and controllers on.
316 * @param hostname name of the host, use "NULL" for localhost
317 * @param username username to use for the login; may be NULL
318 * @param port port number to use for ssh; use 0 to let ssh decide
319 * @return handle to the host, NULL on error
321 struct GNUNET_TESTBED_Host *
322 GNUNET_TESTBED_host_create (const char *hostname, const char *username,
325 static uint32_t uid_generator;
327 if (NULL == hostname)
328 return GNUNET_TESTBED_host_create_with_id (0, hostname, username, port);
329 return GNUNET_TESTBED_host_create_with_id (++uid_generator, hostname,
335 * Load a set of hosts from a configuration file.
337 * @param filename file with the host specification
338 * @param hosts set to the hosts found in the file; caller must free this if
339 * number of hosts returned is greater than 0
340 * @return number of hosts returned in 'hosts', 0 on error
343 GNUNET_TESTBED_hosts_load_from_file (const char *filename,
344 struct GNUNET_TESTBED_Host ***hosts)
346 //struct GNUNET_TESTBED_Host **host_array;
347 struct GNUNET_TESTBED_Host *starting_host;
359 GNUNET_assert (NULL != filename);
360 if (GNUNET_YES != GNUNET_DISK_file_test (filename))
362 LOG (GNUNET_ERROR_TYPE_WARNING, _("Hosts file %s not found\n"), filename);
366 GNUNET_DISK_file_size (filename, &fs, GNUNET_YES, GNUNET_YES))
370 LOG (GNUNET_ERROR_TYPE_WARNING, _("Hosts file %s has no data\n"), filename);
373 data = GNUNET_malloc (fs);
374 if (fs != GNUNET_DISK_fn_read (filename, data, fs))
377 LOG (GNUNET_ERROR_TYPE_WARNING, _("Hosts file %s cannot be read\n"),
383 starting_host = NULL;
385 while (offset < (fs - 1))
388 if (((data[offset] == '\n')) && (buf != &data[offset]))
392 SSCANF (buf, "%255[a-zA-Z0-9_]@%255[a-zA-Z0-9.]:%5hd", username,
396 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
397 "Successfully read host %s, port %d and user %s from file\n",
398 hostname, port, username);
399 /* We store hosts in a static list; hence we only require the starting
400 * host pointer in that list to access the newly created list of hosts */
401 if (NULL == starting_host)
402 starting_host = GNUNET_TESTBED_host_create (hostname, username, port);
404 (void) GNUNET_TESTBED_host_create (hostname, username, port);
408 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
409 "Error reading line `%s' in hostfile\n", buf);
410 buf = &data[offset + 1];
412 else if ((data[offset] == '\n') || (data[offset] == '\0'))
413 buf = &data[offset + 1];
416 if (NULL == starting_host)
418 *hosts = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_Host *) * count);
419 memcpy (*hosts, &host_list[GNUNET_TESTBED_host_get_id_ (starting_host)],
420 sizeof (struct GNUNET_TESTBED_Host *) * count);
426 * Destroy a host handle. Must only be called once everything
427 * running on that host has been stopped.
429 * @param host handle to destroy
432 GNUNET_TESTBED_host_destroy (struct GNUNET_TESTBED_Host *host)
434 struct RegisteredController *rc;
437 GNUNET_assert (host->id < host_list_size);
438 GNUNET_assert (host_list[host->id] == host);
439 host_list[host->id] = NULL;
440 /* clear registered controllers list */
441 for (rc = host->rc_head; NULL != rc; rc = host->rc_head)
443 GNUNET_CONTAINER_DLL_remove (host->rc_head, host->rc_tail, rc);
446 GNUNET_free_non_null ((char *) host->username);
447 GNUNET_free_non_null ((char *) host->hostname);
448 GNUNET_TESTBED_operation_queue_destroy_
449 (host->opq_parallel_overlay_connect_operations);
450 GNUNET_TESTBED_SD_destroy_ (host->poc_sd);
451 GNUNET_free_non_null (host->tslots);
453 while (host_list_size >= HOST_LIST_GROW_STEP)
455 for (id = host_list_size - 1; id > host_list_size - HOST_LIST_GROW_STEP;
457 if (NULL != host_list[id])
459 if (id != host_list_size - HOST_LIST_GROW_STEP)
461 if (NULL != host_list[id])
463 host_list_size -= HOST_LIST_GROW_STEP;
466 GNUNET_realloc (host_list,
467 sizeof (struct GNUNET_TESTBED_Host *) * host_list_size);
472 * Marks a host as registered with a controller
474 * @param host the host to mark
475 * @param controller the controller at which this host is registered
478 GNUNET_TESTBED_mark_host_registered_at_ (struct GNUNET_TESTBED_Host *host,
479 const struct GNUNET_TESTBED_Controller
482 struct RegisteredController *rc;
484 for (rc = host->rc_head; NULL != rc; rc = rc->next)
486 if (controller == rc->controller) /* already registered at controller */
492 rc = GNUNET_malloc (sizeof (struct RegisteredController));
493 rc->controller = controller;
494 GNUNET_CONTAINER_DLL_insert_tail (host->rc_head, host->rc_tail, rc);
499 * Checks whether a host has been registered
501 * @param host the host to check
502 * @param controller the controller at which host's registration is checked
503 * @return GNUNET_YES if registered; GNUNET_NO if not
506 GNUNET_TESTBED_is_host_registered_ (const struct GNUNET_TESTBED_Host *host,
507 const struct GNUNET_TESTBED_Controller
510 struct RegisteredController *rc;
512 for (rc = host->rc_head; NULL != rc; rc = rc->next)
514 if (controller == rc->controller) /* already registered at controller */
524 * The handle for whether a host is habitable or not
526 struct GNUNET_TESTBED_HostHabitableCheckHandle
531 const struct GNUNET_TESTBED_Host *host;
534 /* * the configuration handle to lookup the path of the testbed helper */
536 /* const struct GNUNET_CONFIGURATION_Handle *cfg; */
539 * The callback to call once we have the status
541 GNUNET_TESTBED_HostHabitableCallback cb;
544 * The callback closure
549 * The process handle for the SSH process
551 struct GNUNET_OS_Process *auxp;
554 * The SSH destination address string
559 * The destination port string
564 * The path for hte testbed helper binary
566 char *helper_binary_path;
569 * Task id for the habitability check task
571 GNUNET_SCHEDULER_TaskIdentifier habitability_check_task;
574 * How long we wait before checking the process status. Should grow
577 struct GNUNET_TIME_Relative wait_time;
583 * Task for checking whether a host is habitable or not
585 * @param cls GNUNET_TESTBED_HostHabitableCheckHandle
586 * @param tc the scheduler task context
589 habitability_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
591 struct GNUNET_TESTBED_HostHabitableCheckHandle *h = cls;
593 GNUNET_TESTBED_HostHabitableCallback cb;
594 const struct GNUNET_TESTBED_Host *host;
596 enum GNUNET_OS_ProcessStatusType type;
599 h->habitability_check_task = GNUNET_SCHEDULER_NO_TASK;
600 ret = GNUNET_OS_process_status (h->auxp, &type, &code);
601 if (GNUNET_SYSERR == ret)
607 if (GNUNET_NO == ret)
609 h->wait_time = GNUNET_TIME_STD_BACKOFF (h->wait_time);
610 h->habitability_check_task =
611 GNUNET_SCHEDULER_add_delayed (h->wait_time, &habitability_check, h);
614 GNUNET_OS_process_destroy (h->auxp);
616 ret = (0 != code) ? GNUNET_NO : GNUNET_YES;
619 GNUNET_free (h->ssh_addr);
620 GNUNET_free (h->portstr);
621 GNUNET_free (h->helper_binary_path);
623 GNUNET_OS_process_destroy (h->auxp);
629 cb (cb_cls, host, ret);
634 * Checks whether a host can be used to start testbed service
636 * @param host the host to check
637 * @param config the configuration handle to lookup the path of the testbed
639 * @param cb the callback to call to inform about habitability of the given host
640 * @param cb_cls the closure for the callback
641 * @return NULL upon any error or a handle which can be passed to
642 * GNUNET_TESTBED_is_host_habitable_cancel()
644 struct GNUNET_TESTBED_HostHabitableCheckHandle *
645 GNUNET_TESTBED_is_host_habitable (const struct GNUNET_TESTBED_Host *host,
646 const struct GNUNET_CONFIGURATION_Handle
648 GNUNET_TESTBED_HostHabitableCallback cb,
651 struct GNUNET_TESTBED_HostHabitableCheckHandle *h;
652 char *remote_args[11];
653 const char *hostname;
656 h = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_HostHabitableCheckHandle));
660 hostname = (NULL == host->hostname) ? "127.0.0.1" : host->hostname;
661 if (NULL == host->username)
662 h->ssh_addr = GNUNET_strdup (hostname);
664 GNUNET_asprintf (&h->ssh_addr, "%s@%s", host->username, hostname);
666 GNUNET_CONFIGURATION_get_value_string (config, "testbed",
667 "HELPER_BINARY_PATH",
668 &h->helper_binary_path))
669 h->helper_binary_path =
670 GNUNET_OS_get_libexec_binary_path (HELPER_TESTBED_BINARY);
672 remote_args[argp++] = "ssh";
673 GNUNET_asprintf (&h->portstr, "%u", host->port);
674 remote_args[argp++] = "-p";
675 remote_args[argp++] = h->portstr;
676 remote_args[argp++] = "-o";
677 remote_args[argp++] = "BatchMode=yes";
678 remote_args[argp++] = "-o";
679 remote_args[argp++] = "NoHostAuthenticationForLocalhost=yes";
680 remote_args[argp++] = h->ssh_addr;
681 remote_args[argp++] = "stat";
682 remote_args[argp++] = h->helper_binary_path;
683 remote_args[argp++] = NULL;
684 GNUNET_assert (argp == 11);
686 GNUNET_OS_start_process_vap (GNUNET_NO, GNUNET_OS_INHERIT_STD_ERR, NULL,
687 NULL, "ssh", remote_args);
690 GNUNET_break (0); /* Cannot exec SSH? */
691 GNUNET_free (h->ssh_addr);
692 GNUNET_free (h->portstr);
693 GNUNET_free (h->helper_binary_path);
697 h->wait_time = GNUNET_TIME_STD_BACKOFF (h->wait_time);
698 h->habitability_check_task =
699 GNUNET_SCHEDULER_add_delayed (h->wait_time, &habitability_check, h);
705 * Function to cancel a request started using GNUNET_TESTBED_is_host_habitable()
707 * @param handle the habitability check handle
710 GNUNET_TESTBED_is_host_habitable_cancel (struct
711 GNUNET_TESTBED_HostHabitableCheckHandle
714 GNUNET_SCHEDULER_cancel (handle->habitability_check_task);
715 (void) GNUNET_OS_process_kill (handle->auxp, SIGTERM);
716 (void) GNUNET_OS_process_wait (handle->auxp);
717 GNUNET_OS_process_destroy (handle->auxp);
718 GNUNET_free (handle->ssh_addr);
719 GNUNET_free (handle->portstr);
720 GNUNET_free (handle->helper_binary_path);
721 GNUNET_free (handle);
726 * Initializes the operation queue for parallel overlay connects
728 * @param h the host handle
729 * @param npoc the number of parallel overlay connects - the queue size
732 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (struct
733 GNUNET_TESTBED_Host *h,
736 //fprintf (stderr, "%d", npoc);
737 GNUNET_free_non_null (h->tslots);
738 h->tslots_filled = 0;
739 h->num_parallel_connects = npoc;
740 h->tslots = GNUNET_malloc (npoc * sizeof (struct TimeSlot));
741 GNUNET_TESTBED_operation_queue_reset_max_active_
742 (h->opq_parallel_overlay_connect_operations, npoc);
747 * Returns a timing slot which will be exclusively locked
749 * @param h the host handle
750 * @param key a pointer which is associated to the returned slot; should not be
751 * NULL. It serves as a key to determine the correct owner of the slot
752 * @return the time slot index in the array of time slots in the controller
756 GNUNET_TESTBED_get_tslot_ (struct GNUNET_TESTBED_Host *h, void *key)
760 GNUNET_assert (NULL != h->tslots);
761 GNUNET_assert (NULL != key);
762 for (slot = 0; slot < h->num_parallel_connects; slot++)
763 if (NULL == h->tslots[slot].key)
765 h->tslots[slot].key = key;
768 GNUNET_assert (0); /* We should always find a free tslot */
773 * Decides whether any change in the number of parallel overlay connects is
774 * necessary to adapt to the load on the system
776 * @param h the host handle
779 decide_npoc (struct GNUNET_TESTBED_Host *h)
781 struct GNUNET_TIME_Relative avg;
786 if (h->tslots_filled != h->num_parallel_connects)
788 avg = GNUNET_TIME_UNIT_ZERO;
790 for (slot = 0; slot < h->num_parallel_connects; slot++)
792 avg = GNUNET_TIME_relative_add (avg, h->tslots[slot].time);
793 nvals += h->tslots[slot].nvals;
795 GNUNET_assert (nvals >= h->num_parallel_connects);
796 avg = GNUNET_TIME_relative_divide (avg, nvals);
797 GNUNET_assert (GNUNET_TIME_UNIT_FOREVER_REL.rel_value != avg.rel_value);
798 sd = GNUNET_TESTBED_SD_deviation_factor_ (h->poc_sd, (unsigned int) avg.rel_value);
800 (0 == GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
801 h->num_parallel_connects)) )
802 GNUNET_TESTBED_SD_add_data_ (h->poc_sd, (unsigned int) avg.rel_value);
803 if (GNUNET_SYSERR == sd)
805 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
806 h->num_parallel_connects);
809 GNUNET_assert (0 <= sd);
812 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
813 h->num_parallel_connects
819 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
820 h->num_parallel_connects
824 if (1 == h->num_parallel_connects)
826 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h, 1);
831 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
832 h->num_parallel_connects
836 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
837 h->num_parallel_connects /
843 * Releases a time slot thus making it available for be used again
845 * @param h the host handle
846 * @param index the index of the the time slot
847 * @param key the key to prove ownership of the timeslot
848 * @return GNUNET_YES if the time slot is successfully removed; GNUNET_NO if the
849 * time slot cannot be removed - this could be because of the index
850 * greater than existing number of time slots or `key' being different
853 GNUNET_TESTBED_release_time_slot_ (struct GNUNET_TESTBED_Host *h,
854 unsigned int index, void *key)
856 struct TimeSlot *slot;
858 GNUNET_assert (NULL != key);
859 if (index >= h->num_parallel_connects)
861 slot = &h->tslots[index];
862 if (key != slot->key)
870 * Function to update a time slot
872 * @param h the host handle
873 * @param index the index of the time slot to update
874 * @param key the key to identify ownership of the slot
875 * @param time the new time
876 * @param failed should this reading be treated as coming from a fail event
879 GNUNET_TESTBED_update_time_slot_ (struct GNUNET_TESTBED_Host *h,
880 unsigned int index, void *key,
881 struct GNUNET_TIME_Relative time, int failed)
883 struct TimeSlot *slot;
885 if (GNUNET_YES == failed)
887 if (1 == h->num_parallel_connects)
889 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h, 1);
892 GNUNET_TESTBED_set_num_parallel_overlay_connects_ (h,
893 h->num_parallel_connects
896 if (GNUNET_NO == GNUNET_TESTBED_release_time_slot_ (h, index, key))
898 slot = &h->tslots[index];
900 if (GNUNET_TIME_UNIT_ZERO.rel_value == slot->time.rel_value)
907 slot->time = GNUNET_TIME_relative_add (slot->time, time);
912 * Queues the given operation in the queue for parallel overlay connects of the
915 * @param h the host handle
916 * @param op the operation to queue in the given host's parally overlay connect
920 GNUNET_TESTBED_host_queue_oc_ (struct GNUNET_TESTBED_Host *h,
921 struct GNUNET_TESTBED_Operation *op)
923 GNUNET_TESTBED_operation_queue_insert_
924 (h->opq_parallel_overlay_connect_operations, op);
927 /* end of testbed_api_hosts.c */