sensor: profiler reads topology from file
[oweals/gnunet.git] / src / sensor / gnunet-sensor-profiler.c
1 /*
2      This file is part of GNUnet.
3      (C)
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  * TODO:
23  * - Run X peers
24  * - Rewrite interval time (optional)
25  * - Run 1 dashboard
26  * - Monitor dashboard records
27  * - Prompt for anomalies when ready:
28  *  -- Cut Y peers (remove their connections to other X-Y peers but not the connections among themselves)
29  */
30
31 /**
32  * @file sensor/gnunet-sensor-profiler.c
33  * @brief Profiler for the sensor service
34  * @author Omar Tarabai
35  */
36 #include "platform.h"
37 #include "gnunet_util_lib.h"
38 #include "gnunet_testbed_service.h"
39 #include "gnunet_peerstore_service.h"
40 #include "gnunet_sensor_service.h"
41 #include "gnunet_sensor_util_lib.h"
42
43 /**
44  * Information about a single peer
45  */
46 struct PeerInfo
47 {
48
49   /**
50    * Peer Identity
51    */
52   struct GNUNET_PeerIdentity peer_id;
53
54   /**
55    * Testbed peer handle
56    */
57   struct GNUNET_TESTBED_Peer *testbed_peer;
58
59 };
60
61
62 /**
63  * Name of the configuration file used
64  */
65 static const char *cfg_filename = "gnunet-sensor-profiler.conf";
66
67 /**
68  * Directory to read sensor definitions from
69  */
70 static const char *sensor_src_dir = "sensors";
71
72 /**
73  * Directory to write new sensor definitions to
74  */
75 static const char *sensor_dst_dir = "/tmp/gnunet-sensor-profiler";
76
77 /**
78  * Return value of the program
79  */
80 static int ok = 1;
81
82 /**
83  * Number of peers to run (Option -p)
84  */
85 static unsigned int num_peers = 0;
86
87 /**
88  * Set sensors running interval to this value (Option -i)
89  */
90 static unsigned int sensors_interval = 0;
91
92 /**
93  * Path to topology file (Option -t)
94  */
95 static char *topology_file;
96
97 /**
98  * Array of peer info for all peers
99  */
100 static struct PeerInfo *all_peers_info;
101
102 /**
103  * Number of peers that we already collected and start their info
104  */
105 static int peers_known = 0;
106
107 /**
108  * TESTBED operation connecting us to peerstore service on collection point
109  */
110 static struct GNUNET_TESTBED_Operation *peerstore_op;
111
112 /**
113  * Handle to peerstore service on collection point
114  */
115 static struct GNUNET_PEERSTORE_Handle *peerstore;
116
117 /**
118  * Dashboard service on collection point started?
119  */
120 static int dashboard_service_started = GNUNET_NO;
121
122 /**
123  * Number of peers started the sensor service successfully
124  */
125 static int sensor_services_started = 0;
126
127 /**
128  * Array of sensor names to be used for watching peerstore records
129  */
130 static char **sensor_names;
131
132 /**
133  * Size of 'sensor_names' array
134  */
135 static unsigned int sensor_names_size = 0;
136
137
138 /**
139  * Copy directory recursively
140  *
141  * @param src Path to source directory
142  * @param dst Destination directory, will be created if it does not exist
143  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
144  */
145 static int
146 copy_dir (const char *src, const char *dst);
147
148
149 /**
150  * Do clean up and shutdown scheduler
151  */
152 static void
153 do_shutdown ()                  // TODO: schedule timeout shutdown
154 {
155   int i;
156
157   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutting down.\n");
158   if (NULL != sensor_names)
159   {
160     for (i = 0; i < sensor_names_size; i++)
161       GNUNET_free (sensor_names[i]);
162     GNUNET_array_grow (sensor_names, sensor_names_size, 0);
163   }
164   if (NULL != peerstore_op)
165   {
166     GNUNET_TESTBED_operation_done (peerstore_op);
167     peerstore_op = NULL;
168   }
169   if (NULL != all_peers_info)
170   {
171     GNUNET_free (all_peers_info);
172     all_peers_info = NULL;
173   }
174   GNUNET_SCHEDULER_shutdown ();
175 }
176
177
178 /**
179  * Function called with each file/folder inside a directory that is being copied.
180  *
181  * @param cls closure, destination directory
182  * @param filename complete filename (absolute path)
183  * @return #GNUNET_OK to continue to iterate.
184  *         #GNUNET_SYSERR to abort iteration with error
185  */
186 static int
187 copy_dir_scanner (void *cls, const char *filename)
188 {
189   char *dst_dir = cls;
190   char *dst;
191   int copy_result;
192
193   GNUNET_asprintf (&dst, "%s%s%s", dst_dir, DIR_SEPARATOR_STR,
194                    GNUNET_STRINGS_get_short_name (filename));
195   if (GNUNET_YES == GNUNET_DISK_directory_test (filename, GNUNET_YES))
196     copy_result = copy_dir (filename, dst);
197   else
198   {
199     if (GNUNET_YES == GNUNET_DISK_file_test (dst))
200       GNUNET_DISK_directory_remove (dst);
201     copy_result = GNUNET_DISK_file_copy (filename, dst);
202   }
203   GNUNET_free (dst);
204   return copy_result;
205 }
206
207
208 /**
209  * Copy directory recursively
210  *
211  * @param src Path to source directory
212  * @param dst Destination directory, will be created if it does not exist
213  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
214  */
215 static int
216 copy_dir (const char *src, const char *dst)
217 {
218   if (GNUNET_YES != GNUNET_DISK_directory_test (src, GNUNET_YES))
219     return GNUNET_SYSERR;
220   if (GNUNET_OK != GNUNET_DISK_directory_create (dst))
221     return GNUNET_SYSERR;
222   if (GNUNET_SYSERR ==
223       GNUNET_DISK_directory_scan (src, &copy_dir_scanner, (char *) dst))
224     return GNUNET_SYSERR;
225   return GNUNET_OK;
226 }
227
228
229 /**
230  * Function called with each file/folder inside source sensor directory.
231  *
232  * @param cls closure (unused)
233  * @param filename complete filename (absolute path)
234  * @return #GNUNET_OK to continue to iterate.
235  */
236 static int
237 sensor_dir_scanner (void *cls, const char *filename)
238 {
239   const char *file_basename;
240   char *dst_path;
241   struct GNUNET_CONFIGURATION_Handle *sensor_cfg;
242   char *sensor_name;
243
244   file_basename = GNUNET_STRINGS_get_short_name (filename);
245   GNUNET_asprintf (&dst_path, "%s%s%s", sensor_dst_dir, DIR_SEPARATOR_STR,
246                    file_basename);
247   if (GNUNET_YES == GNUNET_DISK_directory_test (filename, GNUNET_NO))
248   {
249     GNUNET_assert (GNUNET_OK == copy_dir (filename, dst_path));
250   }
251   else
252   {
253     sensor_name = GNUNET_strdup (file_basename);
254     GNUNET_array_append (sensor_names, sensor_names_size, sensor_name);
255     sensor_cfg = GNUNET_CONFIGURATION_create ();
256     GNUNET_assert (GNUNET_OK ==
257                    GNUNET_CONFIGURATION_parse (sensor_cfg, filename));
258     GNUNET_CONFIGURATION_set_value_string (sensor_cfg, file_basename,
259                                            "COLLECTION_POINT",
260                                            GNUNET_i2s_full (&all_peers_info[0].
261                                                             peer_id));
262     if (sensors_interval > 0)
263     {
264       GNUNET_CONFIGURATION_set_value_number (sensor_cfg, file_basename,
265                                              "INTERVAL",
266                                              (unsigned long long int)
267                                              sensors_interval);
268     }
269     GNUNET_CONFIGURATION_write (sensor_cfg, dst_path);
270     GNUNET_CONFIGURATION_destroy (sensor_cfg);
271   }
272   GNUNET_free (dst_path);
273   return GNUNET_OK;
274 }
275
276
277 /**
278  * Load sensor definitions and rewrite them to tmp location.
279  * Add collection point peer id and change running interval if needed.
280  */
281 static void
282 rewrite_sensors ()
283 {
284   GNUNET_assert (GNUNET_YES ==
285                  GNUNET_DISK_directory_test (sensor_src_dir, GNUNET_YES));
286   GNUNET_assert (GNUNET_OK == GNUNET_DISK_directory_create (sensor_dst_dir));
287   GNUNET_DISK_directory_scan (sensor_src_dir, &sensor_dir_scanner, NULL);
288 }
289
290
291 /**
292  * Callback to be called when dashboard service is started
293  *
294  * @param cls the callback closure from functions generating an operation
295  * @param op the operation that has been finished
296  * @param emsg error message in case the operation has failed; will be NULL if
297  *          operation has executed successfully.
298  */
299 static void
300 dashboard_started (void *cls, struct GNUNET_TESTBED_Operation *op,
301                    const char *emsg)
302 {
303   if (NULL != emsg)
304   {
305     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
306     GNUNET_assert (0);
307   }
308   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Dashboard service started.\n");
309   GNUNET_TESTBED_operation_done (op);
310   dashboard_service_started = GNUNET_YES;
311   //TODO:
312 }
313
314
315 /**
316  * Function called by PEERSTORE for each matching record.
317  *
318  * @param cls closure
319  * @param record peerstore record information
320  * @param emsg error message, or NULL if no errors
321  * @return #GNUNET_YES to continue iterating, #GNUNET_NO to stop
322  */
323 static int
324 peerstore_watch_cb (void *cls, struct GNUNET_PEERSTORE_Record *record,
325                     char *emsg)
326 {
327   struct PeerInfo *peer = cls;
328   struct GNUNET_SENSOR_DashboardAnomalyEntry *anomaly;
329
330   if (NULL != emsg)
331   {
332     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
333     GNUNET_assert (0);
334   }
335   GNUNET_assert (record->value_size ==
336                  sizeof (struct GNUNET_SENSOR_DashboardAnomalyEntry));
337   anomaly = record->value;
338   GNUNET_assert (0 ==
339                  GNUNET_CRYPTO_cmp_peer_identity (&peer->peer_id,
340                                                   record->peer));
341   printf ("Anomaly report:\n" "  Peer: `%s'\n" "  Sensor: `%s'\n"
342           "  Anomalous: `%d'\n" "  Anomalous neighbors: %f.\n\n",
343           GNUNET_i2s (&peer->peer_id), record->key, anomaly->anomalous,
344           anomaly->anomalous_neighbors);
345   return GNUNET_YES;
346 }
347
348
349 /**
350  * Callback to be called when peerstore service connect operation is completed
351  *
352  * @param cls the callback closure from functions generating an operation
353  * @param op the operation that has been finished
354  * @param ca_result the service handle returned from GNUNET_TESTBED_ConnectAdapter()
355  * @param emsg error message in case the operation has failed; will be NULL if
356  *          operation has executed successfully.
357  */
358 static void
359 peerstore_connect_cb (void *cls, struct GNUNET_TESTBED_Operation *op,
360                       void *ca_result, const char *emsg)
361 {
362   int i;
363   int j;
364   struct PeerInfo *peer;
365
366   if (NULL != emsg)
367   {
368     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
369     GNUNET_assert (0);
370   }
371   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected to peerstore service.\n");
372   /* Watch for anomaly reports from other peers */
373   for (i = 0; i < num_peers; i++)
374   {
375     peer = &all_peers_info[i];
376     for (j = 0; j < sensor_names_size; j++)
377     {
378       GNUNET_PEERSTORE_watch (peerstore, "sensordashboard-anomalies",
379                               &peer->peer_id, sensor_names[j],
380                               &peerstore_watch_cb, peer);
381     }
382   }
383 }
384
385
386 /**
387  * Adapter function called to establish a connection to peerstore service.
388  *
389  * @param cls closure
390  * @param cfg configuration of the peer to connect to; will be available until
391  *          GNUNET_TESTBED_operation_done() is called on the operation returned
392  *          from GNUNET_TESTBED_service_connect()
393  * @return service handle to return in 'op_result', NULL on error
394  */
395 static void *
396 peerstore_connect_adapter (void *cls,
397                            const struct GNUNET_CONFIGURATION_Handle *cfg)
398 {
399   peerstore = GNUNET_PEERSTORE_connect (cfg);
400   GNUNET_assert (NULL != peerstore);
401   return peerstore;
402 }
403
404
405 /**
406  * Adapter function called to destroy a connection to peerstore service.
407  *
408  * @param cls closure
409  * @param op_result service handle returned from the connect adapter
410  */
411 static void
412 peerstore_disconnect_adapter (void *cls, void *op_result)
413 {
414   GNUNET_PEERSTORE_disconnect (peerstore, GNUNET_NO);
415   peerstore = NULL;
416   peerstore_op = NULL;
417 }
418
419
420 /**
421  * Callback to be called when sensor service is started
422  *
423  * @param cls the callback closure from functions generating an operation
424  * @param op the operation that has been finished
425  * @param emsg error message in case the operation has failed; will be NULL if
426  *          operation has executed successfully.
427  */
428 static void
429 sensor_service_started (void *cls, struct GNUNET_TESTBED_Operation *op,
430                         const char *emsg)
431 {
432   struct PeerInfo *peer = cls;
433
434   if (NULL != emsg)
435   {
436     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
437     GNUNET_assert (0);
438   }
439   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sensor service started on peer `%s'.\n",
440               GNUNET_i2s (&peer->peer_id));
441   GNUNET_TESTBED_operation_done (op);
442   sensor_services_started++;
443   if (sensor_services_started == num_peers)     //TODO: remove
444     do_shutdown ();
445   //TODO
446 }
447
448
449 /**
450  * Callback to be called when the requested peer information is available
451  *
452  * @param cb_cls the closure from GNUNET_TETSBED_peer_get_information()
453  * @param op the operation this callback corresponds to
454  * @param pinfo the result; will be NULL if the operation has failed
455  * @param emsg error message if the operation has failed; will be NULL if the
456  *          operation is successfull
457  */
458 static void
459 peer_info_cb (void *cb_cls, struct GNUNET_TESTBED_Operation *op,
460               const struct GNUNET_TESTBED_PeerInformation *pinfo,
461               const char *emsg)
462 {
463   struct GNUNET_TESTBED_Peer *testbed_peer = cb_cls;
464   struct PeerInfo *peer = &all_peers_info[peers_known];
465
466   if (NULL != emsg)
467   {
468     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "ERROR: %s.\n", emsg);
469     GNUNET_assert (0);
470   }
471   peer->testbed_peer = testbed_peer;
472   GNUNET_CRYPTO_get_peer_identity (pinfo->result.cfg, &peer->peer_id);
473   peers_known++;
474   if (1 == peers_known)         /* First peer is collection point */
475   {
476     /* Rewrite sensors */
477     rewrite_sensors ();
478     /* Start dashboard */
479     GNUNET_TESTBED_peer_manage_service (NULL, testbed_peer, "sensordashboard",
480                                         &dashboard_started, NULL, 1);
481   }
482   /* Start sensor service on every peer */
483   GNUNET_TESTBED_peer_manage_service (NULL, testbed_peer, "sensor",
484                                       &sensor_service_started, peer, 1);
485   if (num_peers == peers_known) /* Last peer */
486   {
487     /* Connect to peerstore on first peer (collection point) */
488     peerstore_op =
489         GNUNET_TESTBED_service_connect (NULL, all_peers_info[0].testbed_peer,
490                                         "peerstore", &peerstore_connect_cb,
491                                         NULL, &peerstore_connect_adapter,
492                                         &peerstore_disconnect_adapter, NULL);
493   }
494   GNUNET_TESTBED_operation_done (op);
495 }
496
497
498 /**
499  * Signature of a main function for a testcase.
500  *
501  * @param cls closure
502  * @param h the run handle
503  * @param num number of peers in 'peers'
504  * @param peers handle to peers run in the testbed.  NULL upon timeout (see
505  *          GNUNET_TESTBED_test_run()).
506  * @param links_succeeded the number of overlay link connection attempts that
507  *          succeeded
508  * @param links_failed the number of overlay link connection attempts that
509  *          failed
510  * @see GNUNET_TESTBED_test_run()
511  */
512 static void
513 test_master (void *cls, struct GNUNET_TESTBED_RunHandle *h, unsigned int num,
514              struct GNUNET_TESTBED_Peer **peers, unsigned int links_succeeded,
515              unsigned int links_failed)
516 {
517   int i;
518
519   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
520               "%d peers started. %d links succeeded. %d links failed.\n",
521               num_peers, links_succeeded, links_failed);
522   GNUNET_assert (num == num_peers);
523   GNUNET_assert (0 == links_failed);
524   /* Collect peer information */
525   all_peers_info = GNUNET_new_array (num_peers, struct PeerInfo);
526
527   for (i = 0; i < num_peers; i++)
528   {
529     GNUNET_TESTBED_peer_get_information (peers[i],
530                                          GNUNET_TESTBED_PIT_CONFIGURATION,
531                                          &peer_info_cb, peers[i]);
532   }
533 }
534
535
536 /**
537  * Verify that the user passed correct CL args
538  *
539  * @return #GNUNET_OK if arguments are valid, #GNUNET_SYSERR otherwise
540  */
541 static int
542 verify_args ()
543 {
544   if (num_peers < 3)
545   {
546     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
547                 _
548                 ("Invalid or missing number of peers. Set at least 3 peers.\n"));
549     return GNUNET_SYSERR;
550   }
551   if (NULL == topology_file ||
552       GNUNET_YES != GNUNET_DISK_file_test (topology_file))
553   {
554     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
555                 _("Missing or invalid topology file.\n"));
556     return GNUNET_SYSERR;
557   }
558   return GNUNET_OK;
559 }
560
561
562 /**
563  * Actual main function.
564  *
565  * @param cls unused
566  * @param args remaining args, unused
567  * @param cfgfile name of the configuration
568  * @param cfg configuration handle
569  */
570 static void
571 run (void *cls, char *const *args, const char *cf,
572      const struct GNUNET_CONFIGURATION_Handle *c)
573 {
574   struct GNUNET_CONFIGURATION_Handle *cfg;
575
576   if (GNUNET_OK != verify_args ())
577   {
578     do_shutdown ();
579     return;
580   }
581   cfg = GNUNET_CONFIGURATION_create ();
582   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (cfg, cfg_filename));
583   GNUNET_CONFIGURATION_set_value_string ((struct GNUNET_CONFIGURATION_Handle *)
584                                          cfg, "TESTBED",
585                                          "OVERLAY_TOPOLOGY_FILE",
586                                          topology_file);
587   GNUNET_TESTBED_run (NULL, cfg, num_peers, 0, NULL, NULL, &test_master, NULL);
588   GNUNET_CONFIGURATION_destroy (cfg);
589 }
590
591
592 /**
593  * Main function.
594  *
595  * @return 0 on success
596  */
597 int
598 main (int argc, char *const *argv)
599 {
600   static struct GNUNET_GETOPT_CommandLineOption options[] = {
601     {'p', "peers", "COUNT", gettext_noop ("Number of peers to run"), GNUNET_YES,
602      &GNUNET_GETOPT_set_uint, &num_peers},
603     {'t', "topology-file", "FILEPATH", gettext_noop ("Path to topology file"),
604      GNUNET_YES, &GNUNET_GETOPT_set_filename, &topology_file},
605     {'i', "sensors-interval", "INTERVAL",
606      gettext_noop ("Change the interval or running sensors to given value"),
607      GNUNET_YES, &GNUNET_GETOPT_set_uint, &sensors_interval},
608     GNUNET_GETOPT_OPTION_END
609   };
610
611   return (GNUNET_OK ==
612           GNUNET_PROGRAM_run (argc, argv, "gnunet-sensor-profiler",
613                               gettext_noop ("Profiler for sensor service"),
614                               options, &run, NULL)) ? ok : 1;
615 }
616
617 /* end of gnunet-sensor-profiler.c */