refactoring ARM api to use new MQ
[oweals/gnunet.git] / src / arm / test_exponential_backoff.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2016 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20 /**
21  * @file arm/test_exponential_backoff.c
22  * @brief testcase for gnunet-service-arm.c
23  */
24 #include "platform.h"
25 #include "gnunet_arm_service.h"
26 #include "gnunet_util_lib.h"
27 #include "gnunet_protocols.h"
28
29 #define LOG(...) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
30
31 #define START_ARM GNUNET_YES
32
33 #define LOG_BACKOFF GNUNET_NO
34
35 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
36
37 #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL
38
39 #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 5)
40
41 #define SERVICE "do-nothing"
42
43 #define BINARY "mockup-service"
44
45 #define CFGFILENAME "test_arm_api_data2.conf"
46
47
48 static const struct GNUNET_CONFIGURATION_Handle *cfg;
49
50 static struct GNUNET_ARM_Handle *arm;
51
52 static struct GNUNET_ARM_MonitorHandle *mon;
53
54 static int ok = 1;
55
56 static int phase = 0;
57
58 static int trialCount;
59
60 static struct GNUNET_TIME_Absolute startedWaitingAt;
61
62 struct GNUNET_TIME_Relative waitedFor;
63
64 struct GNUNET_TIME_Relative waitedFor_prev;
65
66 #if LOG_BACKOFF
67 static FILE *killLogFilePtr;
68
69 static char *killLogFileName;
70 #endif
71
72
73 typedef void
74 (*GNUNET_CLIENT_ShutdownTask) (void *cls, int reason);
75
76
77 /**
78  * Context for handling the shutdown of a service.
79  */
80 struct ShutdownContext
81 {
82   /**
83    * Connection to the service that is being shutdown.
84    */
85   struct GNUNET_CLIENT_Connection *sock;
86
87   /**
88    * Time allowed for shutdown to happen.
89    */
90   struct GNUNET_TIME_Absolute timeout;
91
92   /**
93    * Task set up to cancel the shutdown request on timeout.
94    */
95   struct GNUNET_SCHEDULER_Task *cancel_task;
96
97   /**
98    * Task to call once shutdown complete
99    */
100   GNUNET_CLIENT_ShutdownTask cont;
101
102   /**
103    * Closure for shutdown continuation
104    */
105   void *cont_cls;
106
107   /**
108    * We received a confirmation that the service will shut down.
109    */
110   int confirmed;
111
112 };
113
114 /**
115  * Handler receiving response to service shutdown requests.
116  * We expect it to be called with NULL, since the service that
117  * we are shutting down will just die without replying.
118  *
119  * @param cls closure
120  * @param msg NULL, indicating socket closure.
121  */
122 static void
123 service_shutdown_handler (void *cls,
124                           const struct GNUNET_MessageHeader *msg)
125 {
126   struct ShutdownContext *shutdown_ctx = cls;
127
128   if (NULL == msg)
129   {
130     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Service shutdown complete.\n");
131     if (shutdown_ctx->cont != NULL)
132       shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_NO);
133
134     GNUNET_SCHEDULER_cancel (shutdown_ctx->cancel_task);
135     GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
136     GNUNET_free (shutdown_ctx);
137     return;
138   }
139   GNUNET_assert (0);
140 }
141
142
143 /**
144  * Shutting down took too long, cancel receive and return error.
145  *
146  * @param cls closure
147  */
148 static void
149 service_shutdown_cancel (void *cls)
150 {
151   struct ShutdownContext *shutdown_ctx = cls;
152
153   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
154               "service_shutdown_cancel called!\n");
155   shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_SYSERR);
156   GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
157   GNUNET_free (shutdown_ctx);
158 }
159
160
161 /**
162  * If possible, write a shutdown message to the target
163  * buffer and destroy the client connection.
164  *
165  * @param cls the "struct GNUNET_CLIENT_Connection" to destroy
166  * @param size number of bytes available in buf
167  * @param buf NULL on error, otherwise target buffer
168  * @return number of bytes written to buf
169  */
170 static size_t
171 write_shutdown (void *cls, size_t size, void *buf)
172 {
173   struct GNUNET_MessageHeader *msg;
174   struct ShutdownContext *shutdown_ctx = cls;
175
176   if (size < sizeof (struct GNUNET_MessageHeader))
177   {
178     LOG ("Failed to send a shutdown request\n");
179     shutdown_ctx->cont (shutdown_ctx->cont_cls, GNUNET_SYSERR);
180     GNUNET_CLIENT_disconnect (shutdown_ctx->sock);
181     GNUNET_free (shutdown_ctx);
182     return 0;                   /* client disconnected */
183   }
184
185   GNUNET_CLIENT_receive (shutdown_ctx->sock, &service_shutdown_handler,
186                          shutdown_ctx, GNUNET_TIME_UNIT_FOREVER_REL);
187   shutdown_ctx->cancel_task = GNUNET_SCHEDULER_add_delayed (
188       GNUNET_TIME_absolute_get_remaining (shutdown_ctx->timeout),
189       &service_shutdown_cancel, shutdown_ctx);
190   msg = (struct GNUNET_MessageHeader *) buf;
191   msg->type = htons (GNUNET_MESSAGE_TYPE_ARM_STOP);
192   msg->size = htons (sizeof (struct GNUNET_MessageHeader));
193   strcpy ((char *) &msg[1], SERVICE);
194   LOG ("Sent a shutdown request\n");
195   return sizeof (struct GNUNET_MessageHeader) + strlen (SERVICE) + 1;
196 }
197
198
199 /**
200  * Request that the service should shutdown.
201  * Afterwards, the connection will automatically be
202  * disconnected.  Hence the "sock" should not
203  * be used by the caller after this call
204  * (calling this function frees "sock" after a while).
205  *
206  * @param sock the socket connected to the service
207  * @param timeout how long to wait before giving up on transmission
208  * @param cont continuation to call once the service is really down
209  * @param cont_cls closure for continuation
210  *
211  */
212 static void
213 do_nothing_service_shutdown (struct GNUNET_CLIENT_Connection *sock,
214                              struct GNUNET_TIME_Relative timeout,
215                              GNUNET_CLIENT_ShutdownTask cont,
216                              void *cont_cls)
217 {
218   struct ShutdownContext *shutdown_ctx;
219
220   shutdown_ctx = GNUNET_new (struct ShutdownContext);
221   shutdown_ctx->cont = cont;
222   shutdown_ctx->cont_cls = cont_cls;
223   shutdown_ctx->sock = sock;
224   shutdown_ctx->timeout = GNUNET_TIME_relative_to_absolute (timeout);
225   GNUNET_CLIENT_notify_transmit_ready (sock,
226                                        sizeof (struct GNUNET_MessageHeader) + strlen (SERVICE) + 1,
227                                        timeout, GNUNET_NO, &write_shutdown,
228                                        shutdown_ctx);
229 }
230
231
232 static void
233 kill_task (void *cbData);
234
235
236 static void
237 shutdown_cont (void *cls, int reason)
238 {
239   if (GNUNET_NO != reason)
240   {
241     /* Re-try shutdown */
242     LOG ("do-nothing didn't die, trying again\n");
243     GNUNET_SCHEDULER_add_now (&kill_task, NULL);
244     return;
245   }
246   startedWaitingAt = GNUNET_TIME_absolute_get ();
247   LOG ("do-nothing is dead, starting the countdown\n");
248 }
249
250
251 static void
252 kill_task (void *cbData)
253 {
254   static struct GNUNET_CLIENT_Connection *doNothingConnection = NULL;
255
256   if (NULL != cbData)
257   {
258     waitedFor = GNUNET_TIME_absolute_get_duration (startedWaitingAt);
259     LOG ("Waited for: %s\n",
260          GNUNET_STRINGS_relative_time_to_string (waitedFor, GNUNET_YES));
261   }
262   else
263   {
264     waitedFor.rel_value_us = 0;
265   }
266   /* Connect to the doNothing task */
267   doNothingConnection = GNUNET_CLIENT_connect (SERVICE, cfg);
268   GNUNET_assert (doNothingConnection != NULL);
269   if (trialCount == 12)
270     waitedFor_prev = waitedFor;
271   else if (trialCount == 13)
272   {
273     GNUNET_CLIENT_disconnect (doNothingConnection);
274     GNUNET_ARM_request_service_stop (arm,
275                                      SERVICE,
276                                      NULL,
277                                      NULL);
278     if (waitedFor_prev.rel_value_us >= waitedFor.rel_value_us)
279       ok = 9;
280     else
281       ok = 0;
282     trialCount += 1;
283     return;
284   }
285   trialCount += 1;
286   /* Use the created connection to kill the doNothingTask */
287   do_nothing_service_shutdown (doNothingConnection,
288       TIMEOUT, &shutdown_cont, NULL);
289 }
290
291
292 static void
293 trigger_disconnect (void *cls)
294 {
295   GNUNET_ARM_disconnect (arm);
296   GNUNET_ARM_monitor_stop (mon);
297 }
298
299
300 static void
301 arm_stop_cb (void *cls,
302              enum GNUNET_ARM_RequestStatus status,
303              enum GNUNET_ARM_Result result)
304 {
305   GNUNET_break (status == GNUNET_ARM_REQUEST_SENT_OK);
306   GNUNET_break (result == GNUNET_ARM_RESULT_STOPPED);
307   LOG ("ARM service stopped\n");
308   GNUNET_SCHEDULER_add_now (&trigger_disconnect, NULL);
309 }
310
311
312 static void
313 srv_status (void *cls,
314             const char *service,
315             enum GNUNET_ARM_ServiceStatus status)
316 {
317   LOG ("Service %s is %u, phase %u\n", service, status, phase);
318   if (status == GNUNET_ARM_SERVICE_MONITORING_STARTED)
319   {
320     phase++;
321     GNUNET_ARM_request_service_start (arm,
322                                       SERVICE,
323                                       GNUNET_OS_INHERIT_STD_OUT_AND_ERR,
324                                       NULL,
325                                       NULL);
326     return;
327   }
328   if (phase == 1)
329   {
330     GNUNET_break (status == GNUNET_ARM_SERVICE_STARTING);
331     GNUNET_break (0 == strcasecmp (service, SERVICE));
332     GNUNET_break (phase == 1);
333     LOG ("do-nothing is starting\n");
334     phase++;
335     ok = 1;
336     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
337                                   &kill_task,
338                                   NULL);
339   }
340   else if ((phase == 2) && (strcasecmp (SERVICE, service) == 0))
341   {
342     /* We passively monitor ARM for status updates. ARM should tell us
343      * when do-nothing dies (no need to run a service upness test ourselves).
344      */
345     if (status == GNUNET_ARM_SERVICE_STARTING)
346     {
347       LOG ("do-nothing is starting\n");
348       GNUNET_SCHEDULER_add_now (&kill_task, &ok);
349     }
350     else if ((status == GNUNET_ARM_SERVICE_STOPPED) && (trialCount == 14))
351     {
352       phase++;
353       GNUNET_ARM_request_service_stop (arm,
354                                        "arm",
355                                        &arm_stop_cb,
356                                        NULL);
357     }
358   }
359 }
360
361
362 static void
363 arm_start_cb (void *cls,
364               enum GNUNET_ARM_RequestStatus status,
365               enum GNUNET_ARM_Result result)
366 {
367   GNUNET_break (status == GNUNET_ARM_REQUEST_SENT_OK);
368   GNUNET_break (result == GNUNET_ARM_RESULT_STARTING);
369   GNUNET_break (phase == 0);
370   LOG ("Sent 'START' request for arm to ARM %s\n",
371        (status == GNUNET_ARM_REQUEST_SENT_OK) ? "successfully" : "unsuccessfully");
372 }
373
374
375 static void
376 task (void *cls,
377       char *const *args,
378       const char *cfgfile,
379       const struct GNUNET_CONFIGURATION_Handle *c)
380 {
381   cfg = c;
382   arm = GNUNET_ARM_connect (cfg, NULL, NULL);
383   if (NULL != arm)
384   {
385     mon = GNUNET_ARM_monitor_start (cfg, &srv_status, NULL);
386     if (NULL != mon)
387     {
388 #if START_ARM
389       GNUNET_ARM_request_service_start (arm,
390                                         "arm",
391                                         GNUNET_OS_INHERIT_STD_OUT_AND_ERR,
392                                         &arm_start_cb,
393                                         NULL);
394 #else
395       arm_start_cb (NULL,
396                     arm,
397                     GNUNET_ARM_REQUEST_SENT_OK,
398                     GNUNET_ARM_SERVICE_STARTING);
399 #endif
400     }
401     else
402     {
403       GNUNET_ARM_disconnect (arm);
404       arm = NULL;
405     }
406   }
407 }
408
409
410 static int
411 check ()
412 {
413   char *const argv[] = {
414     "test-exponential-backoff",
415     "-c", CFGFILENAME,
416     NULL
417   };
418   struct GNUNET_GETOPT_CommandLineOption options[] = {
419     GNUNET_GETOPT_OPTION_END
420   };
421
422   /* Running ARM  and running the do_nothing task */
423   GNUNET_assert (GNUNET_OK ==
424                  GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
425                                      argv, "test-exponential-backoff",
426                                      "nohelp", options, &task, NULL));
427
428
429   return ok;
430 }
431
432
433 #ifndef PATH_MAX
434 /**
435  * Assumed maximum path length (for the log file name).
436  */
437 #define PATH_MAX 4096
438 #endif
439
440
441 static int
442 init ()
443 {
444   struct GNUNET_CONFIGURATION_Handle *cfg;
445   char pwd[PATH_MAX];
446   char *binary;
447
448   cfg = GNUNET_CONFIGURATION_create ();
449   if (GNUNET_OK != GNUNET_CONFIGURATION_parse (cfg,
450                                                "test_arm_api_data.conf"))
451     return GNUNET_SYSERR;
452   if (NULL == getcwd (pwd, PATH_MAX))
453     return GNUNET_SYSERR;
454   GNUNET_assert (0 < GNUNET_asprintf (&binary, "%s/%s", pwd, BINARY));
455   GNUNET_CONFIGURATION_set_value_string (cfg, SERVICE, "BINARY", binary);
456   GNUNET_free (binary);
457   if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, CFGFILENAME))
458   {
459     GNUNET_CONFIGURATION_destroy (cfg);
460     return GNUNET_SYSERR;
461   }
462   GNUNET_CONFIGURATION_destroy (cfg);
463
464 #if LOG_BACKOFF
465   killLogFileName = GNUNET_DISK_mktemp ("exponential-backoff-waiting.log");
466   if (NULL == (killLogFilePtr = FOPEN (killLogFileName, "w")))
467     {
468       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "fopen",
469                                 killLogFileName);
470       GNUNET_free (killLogFileName);
471       return GNUNET_SYSERR;
472     }
473 #endif
474   return GNUNET_OK;
475 }
476
477
478 static void
479 houseKeep ()
480 {
481 #if LOG_BACKOFF
482   GNUNET_assert (0 == fclose (killLogFilePtr));
483   GNUNET_free (killLogFileName);
484 #endif
485   (void) unlink (CFGFILENAME);
486 }
487
488
489 int
490 main (int argc, char *argv[])
491 {
492   int ret;
493
494   GNUNET_log_setup ("test-exponential-backoff",
495                     "WARNING",
496                     NULL);
497
498   if (GNUNET_OK != init ())
499     return 1;
500   ret = check ();
501   houseKeep ();
502   return ret;
503 }
504
505 /* end of test_exponential_backoff.c */