shutdown callback
[oweals/gnunet.git] / src / fs / fs_test_lib.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010 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 2, 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 fs/fs_test_lib.c
23  * @brief library routines for testing FS publishing and downloading
24  *        with multiple peers; this code is limited to flat files
25  *        and no keywords (those functions can be tested with
26  *        single-peer setups; this is for testing routing).
27  * @author Christian Grothoff
28  */
29 #include "platform.h"
30 #include "fs_test_lib.h"
31 #include "gnunet_testing_lib.h"
32
33 #define CONNECT_ATTEMPTS 4
34
35 /**
36  * Handle for a daemon started for testing FS.
37  */
38 struct GNUNET_FS_TestDaemon
39 {
40
41   /**
42    * Handle to the file sharing context using this daemon.
43    */
44   struct GNUNET_FS_Handle *fs;
45
46   /**
47    * Handle to the daemon via testing.
48    */
49   struct GNUNET_TESTING_Daemon *daemon;
50
51   /**
52    * Note that 'group' will be the same value for all of the
53    * daemons started jointly.
54    */
55   struct GNUNET_TESTING_PeerGroup *group;
56
57   /**
58    * Configuration for accessing this peer.
59    */
60   struct GNUNET_CONFIGURATION_Handle *cfg;
61
62   /**
63    * ID of this peer.
64    */
65   struct GNUNET_PeerIdentity id;
66
67   /**
68    * Scheduler to use (for publish_cont).
69    */
70   struct GNUNET_SCHEDULER_Handle *publish_sched;
71
72   /**
73    * Function to call when upload is done.
74    */
75   GNUNET_FS_TEST_UriContinuation publish_cont;
76   
77   /**
78    * Closure for publish_cont.
79    */
80   void *publish_cont_cls;
81
82   /**
83    * Task to abort publishing (timeout).
84    */
85   GNUNET_SCHEDULER_TaskIdentifier publish_timeout_task;
86
87   /**
88    * Seed for file generation.
89    */
90   uint32_t publish_seed;
91
92   /**
93    * Context for current publishing operation.
94    */
95   struct GNUNET_FS_PublishContext *publish_context;
96
97   /**
98    * Result URI.
99    */
100   struct GNUNET_FS_Uri *publish_uri;
101
102   /**
103    * Scheduler to use (for download_cont).
104    */
105   struct GNUNET_SCHEDULER_Handle *download_sched;
106
107   /**
108    * Function to call when download is done.
109    */
110   GNUNET_SCHEDULER_Task download_cont;
111
112   /**
113    * Closure for download_cont.
114    */
115   void *download_cont_cls;
116
117   /**
118    * Seed for download verification.
119    */
120   uint32_t download_seed;
121
122   /**
123    * Task to abort downloading (timeout).
124    */
125   GNUNET_SCHEDULER_TaskIdentifier download_timeout_task;
126
127   /**
128    * Context for current download operation.
129    */  
130   struct GNUNET_FS_DownloadContext *download_context;
131
132   /**
133    * Verbosity level of the current operation.
134    */
135   int verbose;
136
137                 
138 };
139
140 /**
141  * Check whether peers successfully shut down.
142  */
143 void shutdown_callback (void *cls,
144                         const char *emsg)
145 {
146   if (emsg != NULL)
147     {
148 #if VERBOSE
149       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
150                   "Shutdown of peers failed!\n");
151 #endif
152     }
153   else
154     {
155 #if VERBOSE
156       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
157                   "All peers successfully shut down!\n");
158 #endif
159     }
160 }
161
162 static void
163 report_uri (void *cls,
164             const struct GNUNET_SCHEDULER_TaskContext *tc)
165 {
166   struct GNUNET_FS_TestDaemon *daemon = cls;
167   GNUNET_FS_TEST_UriContinuation cont;
168   struct GNUNET_FS_Uri *uri;
169
170   GNUNET_FS_publish_stop (daemon->publish_context);
171   daemon->publish_context = NULL;
172   daemon->publish_sched = NULL;
173   cont = daemon->publish_cont;
174   daemon->publish_cont = NULL;
175   uri = daemon->publish_uri;
176   cont (daemon->publish_cont_cls,
177         uri);
178   GNUNET_FS_uri_destroy (uri);
179 }            
180
181
182 static void
183 report_success (void *cls,
184                 const struct GNUNET_SCHEDULER_TaskContext *tc)
185 {
186   struct GNUNET_FS_TestDaemon *daemon = cls;
187
188   GNUNET_FS_download_stop (daemon->download_context, GNUNET_YES);
189   daemon->download_context = NULL;
190   GNUNET_SCHEDULER_add_continuation (daemon->download_sched,
191                                      daemon->download_cont,
192                                      daemon->download_cont_cls,
193                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);      
194   daemon->download_cont = NULL;
195   daemon->download_sched = NULL;
196 }
197
198 static void*
199 progress_cb (void *cls,
200              const struct GNUNET_FS_ProgressInfo *info)
201 {
202   struct GNUNET_FS_TestDaemon *daemon = cls;
203
204   switch (info->status)
205     {
206     case GNUNET_FS_STATUS_PUBLISH_COMPLETED:      
207       GNUNET_SCHEDULER_cancel (daemon->publish_sched,
208                                daemon->publish_timeout_task);
209       daemon->publish_timeout_task = GNUNET_SCHEDULER_NO_TASK;
210       daemon->publish_uri = GNUNET_FS_uri_dup (info->value.publish.specifics.completed.chk_uri);
211       GNUNET_SCHEDULER_add_continuation (daemon->publish_sched,
212                                          &report_uri,
213                                          daemon,
214                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
215       break;
216     case GNUNET_FS_STATUS_PUBLISH_PROGRESS:
217       if (daemon->verbose)
218         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
219                     "Publishing at %llu/%llu bytes\n",
220                     (unsigned long long) info->value.publish.completed,
221                     (unsigned long long) info->value.publish.size);
222       break;
223     case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
224       if (daemon->verbose)
225         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
226                     "Download at %llu/%llu bytes\n",
227                     (unsigned long long) info->value.download.completed,
228                     (unsigned long long) info->value.download.size);
229       break;
230     case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
231       GNUNET_SCHEDULER_cancel (daemon->download_sched,
232                                daemon->download_timeout_task);
233       daemon->download_timeout_task = GNUNET_SCHEDULER_NO_TASK;
234       GNUNET_SCHEDULER_add_continuation (daemon->download_sched,
235                                          &report_success,
236                                          daemon,
237                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
238       break;
239     case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
240     case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
241       break;
242       /* FIXME: monitor data correctness during download progress */
243       /* FIXME: do performance reports given sufficient verbosity */
244       /* FIXME: advance timeout task to "immediate" on error */
245     default:
246       break;
247     }
248   return NULL;
249 }
250
251
252 struct StartContext
253 {
254   struct GNUNET_SCHEDULER_Handle *sched;
255   struct GNUNET_TIME_Relative timeout;
256   unsigned int total;
257   unsigned int have;
258   struct GNUNET_FS_TestDaemon **daemons;
259   GNUNET_SCHEDULER_Task cont;
260   void *cont_cls;
261   struct GNUNET_TESTING_PeerGroup *group;
262   struct GNUNET_CONFIGURATION_Handle *cfg;
263   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
264 };
265
266
267 static void 
268 notify_running (void *cls,
269                 const struct GNUNET_PeerIdentity *id,
270                 const struct GNUNET_CONFIGURATION_Handle *cfg,
271                 struct GNUNET_TESTING_Daemon *d,
272                 const char *emsg)
273 {
274   struct StartContext *sctx = cls;
275   unsigned int i;
276
277   if (emsg != NULL)
278     {
279       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
280                   _("Failed to start daemon: %s\n"),
281                   emsg);
282       return;
283     }
284   i = 0;
285   while (i < sctx->total)
286     {
287       if (GNUNET_TESTING_daemon_get (sctx->group,
288                                      i) == d)
289         break;
290       i++;
291     }
292   GNUNET_assert (i < sctx->total);
293   GNUNET_assert (sctx->have < sctx->total);
294   GNUNET_assert (sctx->daemons[i]->cfg == NULL);
295   sctx->daemons[i]->cfg = GNUNET_CONFIGURATION_dup (cfg);
296   sctx->daemons[i]->group = sctx->group;
297   sctx->daemons[i]->daemon = d;
298   sctx->daemons[i]->id = *id;
299   sctx->have++;
300   if (sctx->have == sctx->total)
301     {
302       GNUNET_SCHEDULER_add_continuation (sctx->sched,
303                                          sctx->cont,
304                                          sctx->cont_cls,
305                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
306       GNUNET_CONFIGURATION_destroy (sctx->cfg);
307       GNUNET_SCHEDULER_cancel (sctx->sched,
308                                sctx->timeout_task);
309       for (i=0;i<sctx->total;i++)
310         {
311           sctx->daemons[i]->fs = GNUNET_FS_start (sctx->sched,
312                                                   sctx->daemons[i]->cfg,
313                                                   "<tester>",
314                                                   &progress_cb,
315                                                   sctx->daemons[i],
316                                                   GNUNET_FS_FLAGS_NONE,
317                                                   GNUNET_FS_OPTIONS_END);
318         }
319       GNUNET_free (sctx);
320     }
321 }
322
323
324 static void
325 start_timeout (void *cls,
326                const struct GNUNET_SCHEDULER_TaskContext *tc)
327 {
328   struct StartContext *sctx = cls;
329   unsigned int i;
330
331   GNUNET_TESTING_daemons_stop (sctx->group, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 30), &shutdown_callback, NULL);
332   for (i=0;i<sctx->total;i++)
333     {
334       if (i < sctx->have)
335         GNUNET_CONFIGURATION_destroy (sctx->daemons[i]->cfg);
336       GNUNET_free (sctx->daemons[i]);
337     }
338   GNUNET_CONFIGURATION_destroy (sctx->cfg);
339   GNUNET_SCHEDULER_add_continuation (sctx->sched,
340                                      sctx->cont,
341                                      sctx->cont_cls,
342                                      GNUNET_SCHEDULER_REASON_TIMEOUT);
343   GNUNET_free (sctx);
344 }
345
346
347 /**
348  * Start daemons for testing.
349  *
350  * @param sched scheduler to use
351  * @param template_cfg_file configuration template to use
352  * @param timeout if this operation cannot be completed within the
353  *                given period, call the continuation with an error code
354  * @param total number of daemons to start
355  * @param daemons array of 'total' entries to be initialized
356  *                (array must already be allocated, will be filled)
357  * @param cont function to call when done
358  * @param cont_cls closure for cont
359  */
360 void
361 GNUNET_FS_TEST_daemons_start (struct GNUNET_SCHEDULER_Handle *sched,
362                               const char *template_cfg_file,
363                               struct GNUNET_TIME_Relative timeout,
364                               unsigned int total,
365                               struct GNUNET_FS_TestDaemon **daemons,
366                               GNUNET_SCHEDULER_Task cont,
367                               void *cont_cls)
368 {
369   struct StartContext *sctx;
370   unsigned int i;
371
372   GNUNET_assert (total > 0);
373   sctx = GNUNET_malloc (sizeof (struct StartContext));
374   sctx->sched = sched;
375   sctx->daemons = daemons;
376   sctx->total = total;
377   sctx->cont = cont;
378   sctx->cont_cls = cont_cls;
379   sctx->cfg = GNUNET_CONFIGURATION_create ();
380   if (GNUNET_OK !=
381       GNUNET_CONFIGURATION_load (sctx->cfg,
382                                  template_cfg_file))
383     {
384       GNUNET_break (0);
385       GNUNET_CONFIGURATION_destroy (sctx->cfg);
386       GNUNET_free (sctx);
387       GNUNET_SCHEDULER_add_continuation (sched,
388                                          cont,
389                                          cont_cls,
390                                          GNUNET_SCHEDULER_REASON_TIMEOUT);
391       return;
392     }
393   for (i=0;i<total;i++)
394     daemons[i] = GNUNET_malloc (sizeof (struct GNUNET_FS_TestDaemon));
395   sctx->group = GNUNET_TESTING_daemons_start (sched,
396                                               sctx->cfg,
397                                               total,
398                                               timeout,
399                                               NULL,
400                                               NULL,
401                                               &notify_running,
402                                               sctx,
403                                               NULL, NULL,
404                                               NULL);
405   sctx->timeout_task = GNUNET_SCHEDULER_add_delayed (sched,
406                                                      timeout,
407                                                      &start_timeout,
408                                                      sctx);
409 }
410
411
412 struct ConnectContext
413 {
414   struct GNUNET_SCHEDULER_Handle *sched;
415   GNUNET_SCHEDULER_Task cont;
416   void *cont_cls;
417 };
418
419 /**
420  * Prototype of a function that will be called whenever
421  * two daemons are connected by the testing library.
422  *
423  * @param cls closure
424  * @param first peer id for first daemon
425  * @param second peer id for the second daemon
426  * @param distance distance between the connected peers
427  * @param first_cfg config for the first daemon
428  * @param second_cfg config for the second daemon
429  * @param first_daemon handle for the first daemon
430  * @param second_daemon handle for the second daemon
431  * @param emsg error message (NULL on success)
432  */
433 static void
434 notify_connection (void *cls,
435                    const struct GNUNET_PeerIdentity *first,
436                    const struct GNUNET_PeerIdentity *second,
437                    uint32_t distance,
438                    const struct GNUNET_CONFIGURATION_Handle *first_cfg,
439                    const struct GNUNET_CONFIGURATION_Handle *second_cfg,
440                    struct GNUNET_TESTING_Daemon *first_daemon,
441                    struct GNUNET_TESTING_Daemon *second_daemon,
442                    const char *emsg)
443 {
444   struct ConnectContext *cc = cls;
445   
446   if (emsg != NULL)
447     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
448                 _("Failed to connect peers: %s\n"),
449                 emsg);
450   GNUNET_SCHEDULER_add_continuation (cc->sched,
451                                      cc->cont,
452                                      cc->cont_cls,
453                                      (emsg != NULL) 
454                                      ? GNUNET_SCHEDULER_REASON_TIMEOUT 
455                                      : GNUNET_SCHEDULER_REASON_PREREQ_DONE);
456   GNUNET_free (cc);
457 }
458
459
460 /**
461  * Connect two daemons for testing.
462  *
463  * @param sched scheduler to use
464  * @param daemon1 first daemon to connect
465  * @param daemon2 second first daemon to connect
466  * @param timeout if this operation cannot be completed within the
467  *                given period, call the continuation with an error code
468  * @param cont function to call when done
469  * @param cont_cls closure for cont
470  */
471 void
472 GNUNET_FS_TEST_daemons_connect (struct GNUNET_SCHEDULER_Handle *sched,
473                                 struct GNUNET_FS_TestDaemon *daemon1,
474                                 struct GNUNET_FS_TestDaemon *daemon2,
475                                 struct GNUNET_TIME_Relative timeout,
476                                 GNUNET_SCHEDULER_Task cont,
477                                 void *cont_cls)
478 {
479   struct ConnectContext *ncc;
480
481   ncc = GNUNET_malloc (sizeof (struct ConnectContext));
482   ncc->sched = sched;
483   ncc->cont = cont;
484   ncc->cont_cls = cont_cls;
485   GNUNET_TESTING_daemons_connect (daemon1->daemon,
486                                   daemon2->daemon,
487                                   timeout,
488                                   CONNECT_ATTEMPTS,
489                                   &notify_connection,
490                                   ncc);
491 }
492
493
494 /**
495  * Obtain peer group used for testing.
496  *
497  * @param daemons array with the daemons (must contain at least one)
498  * @return peer group
499  */
500 struct GNUNET_TESTING_PeerGroup *
501 GNUNET_FS_TEST_get_group (struct GNUNET_FS_TestDaemon **daemons)
502 {
503   return daemons[0]->group;  
504 }
505
506
507 /**
508  * Stop daemons used for testing.
509  *
510  * @param sched scheduler to use
511  * @param total number of daemons to stop
512  * @param daemons array with the daemons (values will be clobbered)
513  */
514 void
515 GNUNET_FS_TEST_daemons_stop (struct GNUNET_SCHEDULER_Handle *sched,
516                              unsigned int total,
517                              struct GNUNET_FS_TestDaemon **daemons)
518 {
519   unsigned int i;
520   struct GNUNET_TESTING_PeerGroup *pg;
521
522   GNUNET_assert (total > 0);
523   pg = daemons[0]->group;
524   for (i=0;i<total;i++)
525     {
526       GNUNET_FS_stop (daemons[i]->fs);
527       GNUNET_CONFIGURATION_destroy (daemons[i]->cfg);
528       GNUNET_free (daemons[i]);
529       daemons[i] = NULL;
530     }  
531   GNUNET_TESTING_daemons_stop (pg, GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 30), &shutdown_callback, NULL);
532 }
533
534
535 static void
536 publish_timeout (void *cls,
537                  const struct GNUNET_SCHEDULER_TaskContext *tc)
538 {
539   struct GNUNET_FS_TestDaemon *daemon = cls;
540   GNUNET_FS_TEST_UriContinuation cont;
541   
542   cont = daemon->publish_cont;
543   daemon->publish_timeout_task = GNUNET_SCHEDULER_NO_TASK;
544   daemon->publish_cont = NULL;
545   GNUNET_FS_publish_stop (daemon->publish_context);
546   daemon->publish_context = NULL;
547   cont (daemon->publish_cont_cls,
548         NULL);
549 }
550
551
552 static size_t
553 file_generator (void *cls, 
554                 uint64_t offset,
555                 size_t max, 
556                 void *buf,
557                 char **emsg)
558 {
559   struct GNUNET_FS_TestDaemon *daemon = cls;
560   uint64_t pos;
561   uint8_t *cbuf = buf;
562   int mod;
563
564   for (pos=0;pos<max;pos++)
565     {
566       mod = (255 - (offset / 1024 / 32));
567       if (mod == 0)
568         mod = 1;
569       cbuf[pos] = (uint8_t) ((offset * daemon->publish_seed) % mod);  
570     }
571   return max;
572 }
573
574
575
576 /**
577  * Publish a file at the given daemon.
578  *
579  * @param sched scheduler to use
580  * @param daemon where to publish
581  * @param timeout if this operation cannot be completed within the
582  *                given period, call the continuation with an error code
583  * @param anonymity option for publication
584  * @param do_index GNUNET_YES for index, GNUNET_NO for insertion,
585  *                GNUNET_SYSERR for simulation
586  * @param size size of the file to publish
587  * @param seed seed to use for file generation
588  * @param verbose how verbose to be in reporting
589  * @param cont function to call when done
590  * @param cont_cls closure for cont
591  */
592 void
593 GNUNET_FS_TEST_publish (struct GNUNET_SCHEDULER_Handle *sched,
594                         struct GNUNET_FS_TestDaemon *daemon,
595                         struct GNUNET_TIME_Relative timeout,
596                         uint32_t anonymity,
597                         int do_index,
598                         uint64_t size,
599                         uint32_t seed,
600                         unsigned int verbose,
601                         GNUNET_FS_TEST_UriContinuation cont,
602                         void *cont_cls)
603 {
604   GNUNET_assert (daemon->publish_cont == NULL);
605   struct GNUNET_FS_FileInformation *fi;
606
607   daemon->publish_cont = cont;
608   daemon->publish_cont_cls = cont_cls;
609   daemon->publish_seed = seed;
610   daemon->verbose = verbose;
611   daemon->publish_sched = sched;
612   fi = GNUNET_FS_file_information_create_from_reader (daemon->fs,
613                                                       daemon,                                                 
614                                                       size,
615                                                       &file_generator,
616                                                       daemon,
617                                                       NULL,
618                                                       NULL,
619                                                       do_index,
620                                                       anonymity,
621                                                       42 /* priority */,
622                                                       GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS));
623   daemon->publish_context = GNUNET_FS_publish_start (daemon->fs,
624                                                      fi,
625                                                      NULL, NULL, NULL,
626                                                      GNUNET_FS_PUBLISH_OPTION_NONE);
627   daemon->publish_timeout_task = GNUNET_SCHEDULER_add_delayed (sched,
628                                                                timeout,
629                                                                &publish_timeout,
630                                                                daemon);
631 }
632
633
634 static void
635 download_timeout (void *cls,
636                   const struct GNUNET_SCHEDULER_TaskContext *tc)
637 {
638   struct GNUNET_FS_TestDaemon *daemon = cls;
639
640   daemon->download_timeout_task = GNUNET_SCHEDULER_NO_TASK;
641   GNUNET_FS_download_stop (daemon->download_context, GNUNET_YES);
642   daemon->download_context = NULL;
643   GNUNET_SCHEDULER_add_continuation (daemon->download_sched,
644                                      daemon->download_cont,
645                                      daemon->download_cont_cls,
646                                      GNUNET_SCHEDULER_REASON_TIMEOUT);
647   daemon->download_cont = NULL;
648   daemon->download_sched = NULL;
649 }
650
651
652 /**
653  * Perform test download.
654  *
655  * @param sched scheduler to use
656  * @param daemon which peer to download from
657  * @param timeout if this operation cannot be completed within the
658  *                given period, call the continuation with an error code
659  * @param anonymity option for download
660  * @param seed used for file validation
661  * @param uri URI of file to download (CHK/LOC only)
662  * @param verbose how verbose to be in reporting
663  * @param cont function to call when done
664  * @param cont_cls closure for cont
665  */
666 void
667 GNUNET_FS_TEST_download (struct GNUNET_SCHEDULER_Handle *sched,
668                          struct GNUNET_FS_TestDaemon *daemon,
669                          struct GNUNET_TIME_Relative timeout,
670                          uint32_t anonymity,
671                          uint32_t seed,
672                          const struct GNUNET_FS_Uri *uri,
673                          unsigned int verbose,
674                          GNUNET_SCHEDULER_Task cont,
675                          void *cont_cls)
676 {
677   uint64_t size;
678  
679   GNUNET_assert (daemon->download_cont == NULL);
680   size = GNUNET_FS_uri_chk_get_file_size (uri);
681   daemon->verbose = verbose;
682   daemon->download_sched = sched;
683   daemon->download_cont = cont;
684   daemon->download_cont_cls = cont_cls;
685   daemon->download_seed = seed;  
686   daemon->download_context = GNUNET_FS_download_start (daemon->fs,
687                                                        uri,
688                                                        NULL, NULL,
689                                                        NULL,
690                                                        0,
691                                                        size,
692                                                        anonymity,
693                                                        GNUNET_FS_DOWNLOAD_OPTION_NONE,
694                                                        NULL,
695                                                        NULL);
696   daemon->download_timeout_task = GNUNET_SCHEDULER_add_delayed (sched,
697                                                                 timeout,
698                                                                 &download_timeout,
699                                                                 daemon);
700 }
701
702 /* end of test_fs_lib.c */