766a87ad2c318684ff8442d571a4d47710e5795c
[oweals/gnunet.git] / src / testbed / testbed_api_hosts.c
1 /*
2       This file is part of GNUnet
3       (C) 2008--2012 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 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
27  */
28 #include "platform.h"
29 #include "gnunet_testbed_service.h"
30 #include "gnunet_core_service.h"
31 #include "gnunet_constants.h"
32 #include "gnunet_transport_service.h"
33 #include "gnunet_hello_lib.h"
34 #include "gnunet_container_lib.h"
35
36
37
38 /**
39  * Opaque handle to a host running experiments managed by the testing framework.
40  * The master process must be able to SSH to this host without password (via
41  * ssh-agent).
42  */
43 struct GNUNET_TESTBED_Host
44 {
45
46   /**
47    * The next pointer for DLL
48    */
49   struct GNUNET_TESTBED_Host *next;
50
51   /**
52    * The prev pointer for DLL
53    */
54   struct GNUNET_TESTBED_Host *prev;
55
56   /**
57    * The hostname of the host; NULL for localhost
58    */
59   const char *hostname;
60
61   /**
62    * The username to be used for SSH login
63    */
64   const char *username;
65
66   /**
67    * Global ID we use to refer to a host on the network
68    */
69   uint32_t unique_id;
70
71   /**
72    * The port which is to be used for SSH
73    */
74   uint16_t port;
75
76   /**
77    * Set this flag to 1 if host is registered with a controller; 0 if not
78    */
79   uint8_t is_registered;
80 };
81
82
83 /**
84  * Head element in the list of available hosts
85  */
86 static struct GNUNET_TESTBED_Host *host_list_head;
87
88 /**
89  * Tail element in the list of available hosts
90  */
91 static struct GNUNET_TESTBED_Host *host_list_tail;
92
93
94 /**
95  * Lookup a host by ID.
96  * 
97  * @param id global host ID assigned to the host; 0 is
98  *        reserved to always mean 'localhost'
99  * @return handle to the host, NULL if host not found
100  */
101 struct GNUNET_TESTBED_Host *
102 GNUNET_TESTBED_host_lookup_by_id_ (uint32_t id)
103 {
104   struct GNUNET_TESTBED_Host *host;
105
106   for (host = host_list_head; NULL != host; host=host->next)
107     if (id == host->unique_id)
108       return host;
109   return NULL;
110 }
111
112
113 /**
114  * Create a host by ID; given this host handle, we could not
115  * run peers at the host, but we can talk about the host
116  * internally.
117  * 
118  * @param id global host ID assigned to the host; 0 is
119  *        reserved to always mean 'localhost'
120  * @return handle to the host, NULL on error
121  */
122 struct GNUNET_TESTBED_Host *
123 GNUNET_TESTBED_host_create_by_id_ (uint32_t id)
124 {
125   struct GNUNET_TESTBED_Host *host;
126   
127   host = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_Host));
128   host->unique_id = id;
129   return host;
130 }
131
132
133 /**
134  * Obtain the host's unique global ID.
135  * 
136  * @param host handle to the host, NULL means 'localhost'
137  * @return id global host ID assigned to the host (0 is
138  *         'localhost', but then obviously not globally unique)
139  */
140 uint32_t
141 GNUNET_TESTBED_host_get_id_ (const struct GNUNET_TESTBED_Host *host)
142 {
143   return host->unique_id;
144 }
145
146
147 /**
148  * Obtain the host's hostname.
149  * 
150  * @param host handle to the host, NULL means 'localhost'
151  * @return hostname of the host
152  */
153 const char *
154 GNUNET_TESTBED_host_get_hostname_ (const struct GNUNET_TESTBED_Host *host)
155 {
156   return host->hostname;
157 }
158
159
160 /**
161  * Obtain the host's username
162  * 
163  * @param host handle to the host, NULL means 'localhost'
164  * @return username to login to the host
165  */
166 const char *
167 GNUNET_TESTBED_host_get_username_ (const struct GNUNET_TESTBED_Host *host)
168 {
169   return host->username;
170 }
171
172
173 /**
174  * Obtain the host's ssh port
175  * 
176  * @param host handle to the host, NULL means 'localhost'
177  * @return username to login to the host
178  */
179 uint16_t
180 GNUNET_TESTBED_host_get_ssh_port_ (const struct GNUNET_TESTBED_Host *host)
181 {
182   return host->port;
183 }
184
185
186 /**
187  * Create a host to run peers and controllers on.
188  * 
189  * @param id global host ID assigned to the host; 0 is
190  *        reserved to always mean 'localhost'
191  * @param hostname name of the host, use "NULL" for localhost
192  * @param username username to use for the login; may be NULL
193  * @param port port number to use for ssh; use 0 to let ssh decide
194  * @return handle to the host, NULL on error
195  */
196 struct GNUNET_TESTBED_Host *
197 GNUNET_TESTBED_host_create_with_id (uint32_t id,
198                                     const char *hostname,
199                                     const char *username,
200                                     uint16_t port)
201 {
202   struct GNUNET_TESTBED_Host *host;
203
204   host = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_Host));
205   host->hostname = hostname;
206   host->username = username;
207   host->unique_id = id;
208   host->port = (0 == port) ? 22 : port;
209   GNUNET_CONTAINER_DLL_insert_tail (host_list_head, host_list_tail, host);
210   return host;
211 }
212
213
214 /**
215  * Create a host to run peers and controllers on.
216  * 
217  * @param hostname name of the host, use "NULL" for localhost
218  * @param username username to use for the login; may be NULL
219  * @param port port number to use for ssh; use 0 to let ssh decide
220  * @return handle to the host, NULL on error
221  */
222 struct GNUNET_TESTBED_Host *
223 GNUNET_TESTBED_host_create (const char *hostname,
224                             const char *username,
225                             uint16_t port)
226 {
227   static uint32_t uid_generator;
228
229   if (NULL == hostname)
230     return GNUNET_TESTBED_host_create_with_id (0, hostname, username, port);
231   return GNUNET_TESTBED_host_create_with_id (++uid_generator, 
232                                              hostname, username,
233                                              port);
234 }
235
236
237 /**
238  * Load a set of hosts from a configuration file.
239  *
240  * @param filename file with the host specification
241  * @param hosts set to the hosts found in the file
242  * @return number of hosts returned in 'hosts', 0 on error
243  */
244 unsigned int
245 GNUNET_TESTBED_hosts_load_from_file (const char *filename,
246                                      struct GNUNET_TESTBED_Host **hosts)
247 {
248   // see testing_group.c, GNUNET_TESTING_hosts_load
249   GNUNET_break (0);
250   return 0;
251 }
252
253
254 /**
255  * Destroy a host handle.  Must only be called once everything
256  * running on that host has been stopped.
257  *
258  * @param host handle to destroy
259  */
260 void
261 GNUNET_TESTBED_host_destroy (struct GNUNET_TESTBED_Host *host)
262 {  
263   GNUNET_CONTAINER_DLL_remove (host_list_head, host_list_tail, host);
264   GNUNET_free (host);
265 }
266
267
268 /**
269  * Wrapper around GNUNET_HELPER_Handle
270  */
271 struct GNUNET_TESTBED_HelperHandle
272 {
273   /**
274    * The process handle
275    */
276   struct GNUNET_OS_Process *process;
277
278   /**
279    * Pipe connecting to stdin of the process.
280    */
281   struct GNUNET_DISK_PipeHandle *cpipe;
282
283   /**
284    * The port number for ssh; used for helpers starting ssh
285    */
286   char *port;
287
288   /**
289    * The ssh destination string; used for helpers starting ssh
290    */
291   char *dst; 
292 };
293
294
295 /**
296  * Run a given helper process at the given host.  Communication
297  * with the helper will be via GNUnet messages on stdin/stdout.
298  * Runs the process via 'ssh' at the specified host, or locally.
299  * Essentially an SSH-wrapper around the 'gnunet_helper_lib.h' API.
300  * 
301  * @param host host to use, use "NULL" for localhost
302  * @param binary_argv binary name and command-line arguments to give to the binary
303  * @return handle to terminate the command, NULL on error
304  */
305 struct GNUNET_TESTBED_HelperHandle *
306 GNUNET_TESTBED_host_run_ (const struct GNUNET_TESTBED_Host *host,
307                           char *const binary_argv[])
308 {
309   struct GNUNET_TESTBED_HelperHandle *h;
310   unsigned int argc;
311
312   argc = 0;
313   while (NULL != binary_argv[argc]) 
314     argc++;
315   h = GNUNET_malloc (sizeof (struct GNUNET_TESTBED_HelperHandle));
316   h->cpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_YES, GNUNET_NO);
317   if (0 == host->unique_id)
318   {
319     h->process = GNUNET_OS_start_process_vap (GNUNET_YES,
320                                               h->cpipe, NULL,
321                                               "gnunet-service-testbed", 
322                                               binary_argv);
323   }
324   else
325   {    
326     char *remote_args[argc + 6 + 1];
327     unsigned int argp;
328
329     GNUNET_asprintf (&h->port, "%d", host->port);
330     if (NULL == host->username)
331       GNUNET_asprintf (&h->dst, "%s", host->hostname);
332     else 
333       GNUNET_asprintf (&h->dst, "%s@%s", host->hostname, host->username);
334     argp = 0;
335     remote_args[argp++] = "ssh";
336     remote_args[argp++] = "-p";
337     remote_args[argp++] = h->port;
338     remote_args[argp++] = "-q";
339     remote_args[argp++] = h->dst;
340     remote_args[argp++] = "gnunet-service-testbed";
341     while (NULL != binary_argv[argp-6])
342     {
343       remote_args[argp] = binary_argv[argp - 6];
344       argp++;
345     } 
346     remote_args[argp++] = NULL;
347     GNUNET_assert (argp == argc + 6 + 1);
348     h->process = GNUNET_OS_start_process_vap (GNUNET_YES,
349                                               h->cpipe, NULL,
350                                               "ssh", 
351                                               remote_args);
352   }
353   if (NULL == h->process)
354   {
355     GNUNET_break (GNUNET_OK == GNUNET_DISK_pipe_close (h->cpipe));
356     GNUNET_free_non_null (h->port);
357     GNUNET_free_non_null (h->dst);
358     GNUNET_free (h);
359     return NULL;
360   } 
361   GNUNET_break (GNUNET_OK == GNUNET_DISK_pipe_close_end (h->cpipe, GNUNET_DISK_PIPE_END_READ));
362   return h;
363 }
364
365
366 /**
367  * Stops a helper in the HelperHandle using GNUNET_HELPER_stop
368  *
369  * @param handle the handle returned from GNUNET_TESTBED_host_start_
370  */
371 void
372 GNUNET_TESTBED_host_stop_ (struct GNUNET_TESTBED_HelperHandle *handle)
373 {
374   GNUNET_break (GNUNET_OK == GNUNET_DISK_pipe_close (handle->cpipe));
375   GNUNET_break (0 == GNUNET_OS_process_kill (handle->process, SIGTERM));
376   GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (handle->process));
377   GNUNET_OS_process_destroy (handle->process);
378   GNUNET_free_non_null (handle->port);
379   GNUNET_free_non_null (handle->dst);
380   GNUNET_free (handle);
381 }
382
383
384 /**
385  * Marks a host as registered with a controller
386  *
387  * @param host the host to mark
388  */
389 void
390 GNUNET_TESTBED_mark_host_as_registered_ (struct GNUNET_TESTBED_Host *host)
391 {
392   host->is_registered = 1;
393 }
394
395
396 /**
397  * Checks whether a host has been registered
398  *
399  * @param host the host to check
400  * @return GNUNET_YES if registered; GNUNET_NO if not
401  */
402 int
403 GNUNET_TESTBED_is_host_registered_ (const struct GNUNET_TESTBED_Host *host)
404 {
405   return (1 == host->is_registered) ? GNUNET_YES : GNUNET_NO;
406 }
407
408
409 /* end of testbed_api_hosts.c */