implement exponential back-off cool down for ARM process restarts
[oweals/gnunet.git] / src / arm / test_exponential_backoff.c
index 92391ea40d0b0c61a982d380fce4fcade565551f..0905f145d593f44b018fe4c922ea7f549cb5386f 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet.
 /*
      This file is part of GNUnet.
-     (C) 2009 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2009, 2016 GNUnet e.V.
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
-     by the Free Software Foundation; either version 2, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
-     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-     Boston, MA 02111-1307, USA.
+     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+     Boston, MA 02110-1301, USA.
 */
 /**
  * @file arm/test_exponential_backoff.c
  * @brief testcase for gnunet-service-arm.c
 */
 /**
  * @file arm/test_exponential_backoff.c
  * @brief testcase for gnunet-service-arm.c
+ * @author Christian Grothoff
  */
 #include "platform.h"
 #include "gnunet_arm_service.h"
  */
 #include "platform.h"
 #include "gnunet_arm_service.h"
-#include "gnunet_client_lib.h"
-#include "gnunet_configuration_lib.h"
-#include "gnunet_program_lib.h"
+#include "gnunet_util_lib.h"
+#include "gnunet_protocols.h"
+
+#define LOG(...) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
+
+#define LOG_BACKOFF GNUNET_NO
 
 
-#define VERBOSE GNUNET_YES
-#define START_ARM GNUNET_YES
 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
+
 #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL
 #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL
+
 #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 5)
 
 #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 5)
 
-static struct GNUNET_SCHEDULER_Handle *sched;
+#define SERVICE "do-nothing"
+
+#define BINARY "mockup-service"
+
+#define CFGFILENAME "test_arm_api_data2.conf"
+
+
 static const struct GNUNET_CONFIGURATION_Handle *cfg;
 static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
 static struct GNUNET_ARM_Handle *arm;
 static struct GNUNET_ARM_Handle *arm;
+
+static struct GNUNET_ARM_MonitorHandle *mon;
+
+static struct GNUNET_SCHEDULER_Task *kt;
+
 static int ok = 1;
 static int ok = 1;
+
+static int phase = 0;
+
+static int trialCount;
+
+static struct GNUNET_TIME_Absolute startedWaitingAt;
+
+struct GNUNET_TIME_Relative waitedFor;
+
+struct GNUNET_TIME_Relative waitedFor_prev;
+
+#if LOG_BACKOFF
 static FILE *killLogFilePtr;
 static FILE *killLogFilePtr;
+
 static char *killLogFileName;
 static char *killLogFileName;
+#endif
 
 
 
 
-static void
-arm_notify_stop (void *cls, int success)
+/**
+ * Context for handling the shutdown of a service.
+ */
+struct ShutdownContext
 {
 {
-  GNUNET_assert (success == GNUNET_NO);
-#if START_ARM
-  GNUNET_ARM_stop_service (arm, "arm", TIMEOUT, NULL, NULL);
-#endif
-}
+  /**
+   * Connection to the service that is being shutdown.
+   */
+  struct GNUNET_MQ_Handle *mq;
+
+  /**
+   * Task set up to cancel the shutdown request on timeout.
+   */
+  struct GNUNET_SCHEDULER_Task *cancel_task;
+
+};
 
 
 static void
 
 
 static void
-kill_task (void *cbData,
-          const struct GNUNET_SCHEDULER_TaskContext *tc);
+kill_task (void *cbData);
 
 
 
 
+/**
+ * Shutting down took too long, cancel receive and return error.
+ *
+ * @param cls closure
+ */
 static void
 static void
-do_nothing_notify (void *cls, int success)
+service_shutdown_timeout (void *cls)
 {
 {
-  GNUNET_assert (success == GNUNET_YES);
-  ok = 1;
-  GNUNET_SCHEDULER_add_delayed (sched, GNUNET_TIME_UNIT_SECONDS, 
-                               &kill_task, NULL);
+  GNUNET_assert (0);
 }
 
 
 }
 
 
+/**
+ * Generic error handler, called with the appropriate error code and
+ * the same closure specified at the creation of the message queue.
+ * Not every message queue implementation supports an error handler.
+ *
+ * @param cls closure with the `struct ShutdownContext *`
+ * @param error error code
+ */
 static void
 static void
-arm_notify (void *cls, int success)
-{ 
-  GNUNET_assert (success == GNUNET_YES);
-  GNUNET_ARM_start_service (arm, 
-                           "do-nothing", TIMEOUT, 
-                           &do_nothing_notify, NULL);
+mq_error_handler (void *cls,
+                  enum GNUNET_MQ_Error error)
+{
+  struct ShutdownContext *shutdown_ctx = cls;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Service shutdown complete (MQ error).\n");
+  GNUNET_SCHEDULER_cancel (shutdown_ctx->cancel_task);
+  GNUNET_MQ_destroy (shutdown_ctx->mq);
+  GNUNET_free (shutdown_ctx);
 }
 
 
 static void
 }
 
 
 static void
-kill_task (void *cbData,
-                  const struct GNUNET_SCHEDULER_TaskContext *tc);
+kill_task (void *cbData)
+{
+  struct ShutdownContext *shutdown_ctx
+    = GNUNET_new (struct ShutdownContext);
+  struct GNUNET_MQ_Envelope *env;
+  struct GNUNET_MessageHeader *msg;
+  struct GNUNET_MQ_MessageHandler handlers[] = {
+    GNUNET_MQ_handler_end ()
+  };
+
+  kt = NULL;
+  if (trialCount == 13)
+  {
+    LOG ("Saw enough kills, asking ARM to stop mock service for good\n");
+    GNUNET_ARM_request_service_stop (arm,
+                                     SERVICE,
+                                     NULL,
+                                     NULL);
+    ok = 0;
+    trialCount++;
+    GNUNET_free (shutdown_ctx);
+    return;
+  }
+  shutdown_ctx->mq = GNUNET_CLIENT_connect (cfg,
+                                            SERVICE,
+                                            handlers,
+                                            &mq_error_handler,
+                                            shutdown_ctx);
+  GNUNET_assert (NULL != shutdown_ctx->mq);
+  trialCount++;
+  LOG ("Sending a shutdown request to the mock service\n");
+  env = GNUNET_MQ_msg (msg,
+                       GNUNET_MESSAGE_TYPE_ARM_STOP); /* FIXME: abuse of message type */
+  GNUNET_MQ_send (shutdown_ctx->mq,
+                  env);
+  shutdown_ctx->cancel_task
+    = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+                                    &service_shutdown_timeout,
+                                    shutdown_ctx);
+}
 
 
 static void
 
 
 static void
-do_nothing_restarted_notify_task (void *cls,
-                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
-{      
-  static char a;
-  static int trialCount = 0;
-  
-  trialCount++;
-  
-  if ((tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN) != 0) { 
-    fprintf(killLogFilePtr, 
-           "%d.Reason is shutdown!\n",
-           trialCount);
-  }
-  else if ((tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT) != 0) {
-    fprintf(killLogFilePtr, 
-           "%d.Reason is timeout!\n", 
-           trialCount);
+trigger_disconnect (void *cls)
+{
+  GNUNET_ARM_disconnect (arm);
+  GNUNET_ARM_monitor_stop (mon);
+  if (NULL != kt)
+  {
+    GNUNET_SCHEDULER_cancel (kt);
+    kt = NULL;
   }
   }
-  else if ((tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE) != 0) {
-    fprintf(killLogFilePtr, 
-           "%d.Service is running!\n", 
-           trialCount);
-  }  
-  GNUNET_SCHEDULER_add_now (sched, &kill_task, &a);
 }
 
 
 static void
 }
 
 
 static void
-do_test (void *cbData,
-        const struct GNUNET_SCHEDULER_TaskContext *tc)
-{                                    
-  GNUNET_CLIENT_service_test(sched, "do-nothing", 
-                            cfg, TIMEOUT,
-                            &do_nothing_restarted_notify_task, NULL);
+arm_stop_cb (void *cls,
+            enum GNUNET_ARM_RequestStatus status,
+            enum GNUNET_ARM_Result result)
+{
+  GNUNET_break (status == GNUNET_ARM_REQUEST_SENT_OK);
+  GNUNET_break (result == GNUNET_ARM_RESULT_STOPPED);
+  LOG ("ARM service stopped\n");
+  GNUNET_SCHEDULER_shutdown ();
 }
 
 
 static void
 }
 
 
 static void
-kill_task (void *cbData,
-                  const struct GNUNET_SCHEDULER_TaskContext *tc)
+srv_status (void *cls,
+            const char *service,
+            enum GNUNET_ARM_ServiceStatus status)
 {
 {
-  static struct GNUNET_CLIENT_Connection * doNothingConnection = NULL;
-  static struct GNUNET_TIME_Absolute startedWaitingAt;
-  struct GNUNET_TIME_Relative waitedFor;
-  static int trialCount = 0;
-  
-  if (NULL != cbData) {
-    waitedFor = GNUNET_TIME_absolute_get_duration (startedWaitingAt);
-    fprintf(killLogFilePtr, 
-           "Waited for: %llu ms\n\n", 
-           (unsigned long long) waitedFor.value);
-  }
-  /* Connect to the doNothing task */
-  doNothingConnection = GNUNET_CLIENT_connect (sched, "do-nothing", cfg);
-  if (NULL == doNothingConnection)
-    fprintf(killLogFilePtr, 
-           "Unable to connect to do-nothing process!\n");
-  
-  if (trialCount == 12) {
-    GNUNET_ARM_stop_service (arm, 
-                            "do-nothing", 
-                            TIMEOUT,
-                            &arm_notify_stop, NULL);
-    ok = 0;
+  if (status == GNUNET_ARM_SERVICE_MONITORING_STARTED)
+  {
+    LOG ("ARM monitor started, starting mock service\n");
+    phase++;
+    GNUNET_ARM_request_service_start (arm,
+                                      SERVICE,
+                                      GNUNET_OS_INHERIT_STD_OUT_AND_ERR,
+                                      NULL,
+                                      NULL);
     return;
   }
     return;
   }
-  
-  /* Use the created connection to kill the doNothingTask */
-  GNUNET_CLIENT_service_shutdown(doNothingConnection);
-  trialCount++;
-  if (startedWaitingAt.value == 0)
-    waitedFor.value = 0;       
-  startedWaitingAt = GNUNET_TIME_absolute_get();
-  GNUNET_SCHEDULER_add_delayed (sched,
-                               waitedFor,
-                               &do_test,
-                               NULL);
+  if (0 != strcasecmp (service, SERVICE))
+    return; /* not what we care about */
+  if (phase == 1)
+  {
+    GNUNET_break (status == GNUNET_ARM_SERVICE_STARTING);
+    GNUNET_break (phase == 1);
+    LOG ("do-nothing is starting\n");
+    phase++;
+    ok = 1;
+    GNUNET_assert (NULL == kt);
+    startedWaitingAt = GNUNET_TIME_absolute_get ();
+    kt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
+                                       &kill_task,
+                                       NULL);
+  }
+  else if (phase == 2)
+  {
+    /* We passively monitor ARM for status updates. ARM should tell us
+     * when do-nothing dies (no need to run a service upness test ourselves).
+     */
+    if (status == GNUNET_ARM_SERVICE_STARTING)
+    {
+      waitedFor = GNUNET_TIME_absolute_get_duration (startedWaitingAt);
+      LOG ("Waited for: %s\n",
+           GNUNET_STRINGS_relative_time_to_string (waitedFor,
+                                                   GNUNET_YES));
+
+      LOG ("do-nothing is starting, killing it...\n");
+      GNUNET_assert (NULL == kt);
+      kt = GNUNET_SCHEDULER_add_now (&kill_task, &ok);
+    }
+    else if ((status == GNUNET_ARM_SERVICE_STOPPED) && (trialCount == 14))
+    {
+      phase++;
+      LOG ("do-nothing stopped working %u times, we are done here\n",
+           (unsigned int) trialCount);
+      GNUNET_ARM_request_service_stop (arm,
+                                       "arm",
+                                       &arm_stop_cb,
+                                       NULL);
+    }
+  }
+}
+
+
+static void
+arm_start_cb (void *cls,
+              enum GNUNET_ARM_RequestStatus status,
+              enum GNUNET_ARM_Result result)
+{
+  GNUNET_break (status == GNUNET_ARM_REQUEST_SENT_OK);
+  GNUNET_break (result == GNUNET_ARM_RESULT_STARTING);
+  GNUNET_break (phase == 0);
+  LOG ("Sent 'START' request for arm to ARM %s\n",
+       (status == GNUNET_ARM_REQUEST_SENT_OK) ? "successfully" : "unsuccessfully");
 }
 
 }
 
-       
+
 static void
 task (void *cls,
 static void
 task (void *cls,
-      struct GNUNET_SCHEDULER_Handle *s,
       char *const *args,
       const char *cfgfile,
       const struct GNUNET_CONFIGURATION_Handle *c)
 {
   cfg = c;
       char *const *args,
       const char *cfgfile,
       const struct GNUNET_CONFIGURATION_Handle *c)
 {
   cfg = c;
-  sched = s;
-  
-  arm = GNUNET_ARM_connect (cfg, sched, NULL);
-#if START_ARM
-  GNUNET_ARM_start_service (arm, "arm", GNUNET_TIME_UNIT_ZERO, &arm_notify, NULL);
-#else
-  arm_do_nothing (NULL, GNUNET_YES);
-#endif
+  arm = GNUNET_ARM_connect (cfg, NULL, NULL);
+  if (NULL == arm)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  mon = GNUNET_ARM_monitor_start (cfg,
+                                  &srv_status,
+                                  NULL);
+  if (NULL == mon)
+  {
+    GNUNET_break (0);
+    GNUNET_ARM_disconnect (arm);
+    arm = NULL;
+    return;
+  }
+  GNUNET_ARM_request_service_start (arm,
+                                    "arm",
+                                    GNUNET_OS_INHERIT_STD_OUT_AND_ERR,
+                                    &arm_start_cb,
+                                    NULL);
+  GNUNET_SCHEDULER_add_shutdown (&trigger_disconnect,
+                                 NULL);
 }
 
 }
 
+
 static int
 check ()
 {
   char *const argv[] = {
 static int
 check ()
 {
   char *const argv[] = {
-    "test-arm-api",
-    "-c", "test_arm_api_data.conf",
-#if VERBOSE
-    "-L", "DEBUG",
-#endif
+    "test-exponential-backoff",
+    "-c", CFGFILENAME,
     NULL
   };
   struct GNUNET_GETOPT_CommandLineOption options[] = {
     GNUNET_GETOPT_OPTION_END
   };
     NULL
   };
   struct GNUNET_GETOPT_CommandLineOption options[] = {
     GNUNET_GETOPT_OPTION_END
   };
-  
+
   /* Running ARM  and running the do_nothing task */
   GNUNET_assert (GNUNET_OK ==
   /* Running ARM  and running the do_nothing task */
   GNUNET_assert (GNUNET_OK ==
-                 GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
-                                     argv,
+                GNUNET_PROGRAM_run ((sizeof (argv) / sizeof (char *)) - 1,
+                                    argv,
                                      "test-exponential-backoff",
                                      "test-exponential-backoff",
-                                     "nohelp", options, &task, NULL));
-  
-  
+                                    "nohelp",
+                                     options,
+                                     &task,
+                                     NULL));
   return ok;
 }
 
   return ok;
 }
 
+
+#ifndef PATH_MAX
+/**
+ * Assumed maximum path length (for the log file name).
+ */
+#define PATH_MAX 4096
+#endif
+
+
 static int
 static int
-init()
+init ()
 {
 {
-  killLogFileName = GNUNET_DISK_mktemp("exponential-backoff-waiting.log");
-  if (NULL == (killLogFilePtr = FOPEN(killLogFileName, "w"))) {
-    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "fopen", killLogFileName);
-    GNUNET_free (killLogFileName);
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+  char pwd[PATH_MAX];
+  char *binary;
+
+  cfg = GNUNET_CONFIGURATION_create ();
+  if (GNUNET_OK != GNUNET_CONFIGURATION_parse (cfg,
+                                               "test_arm_api_data.conf"))
+    return GNUNET_SYSERR;
+  if (NULL == getcwd (pwd, PATH_MAX))
     return GNUNET_SYSERR;
     return GNUNET_SYSERR;
-  }  
+  GNUNET_assert (0 < GNUNET_asprintf (&binary,
+                                      "%s/%s",
+                                      pwd,
+                                      BINARY));
+  GNUNET_CONFIGURATION_set_value_string (cfg,
+                                         SERVICE,
+                                         "BINARY",
+                                         binary);
+  GNUNET_free (binary);
+  if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg,
+                                               CFGFILENAME))
+  {
+    GNUNET_CONFIGURATION_destroy (cfg);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_CONFIGURATION_destroy (cfg);
+
+#if LOG_BACKOFF
+  killLogFileName = GNUNET_DISK_mktemp ("exponential-backoff-waiting.log");
+  if (NULL == (killLogFilePtr = FOPEN (killLogFileName,
+                                       "w")))
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                                "fopen",
+                               killLogFileName);
+      GNUNET_free (killLogFileName);
+      return GNUNET_SYSERR;
+    }
+#endif
   return GNUNET_OK;
 }
 
 
 static void
   return GNUNET_OK;
 }
 
 
 static void
-houseKeep()
+houseKeep ()
 {
 {
+#if LOG_BACKOFF
   GNUNET_assert (0 == fclose (killLogFilePtr));
   GNUNET_assert (0 == fclose (killLogFilePtr));
-  GNUNET_free(killLogFileName);
+  GNUNET_free (killLogFileName);
+#endif
+  (void) unlink (CFGFILENAME);
 }
 
 
 }
 
 
@@ -232,19 +397,14 @@ main (int argc, char *argv[])
 {
   int ret;
 
 {
   int ret;
 
-  fprintf (stdout,
-          "This test will print some warnings about 'do-nothing' being terminated.  That's expected!\n");
   GNUNET_log_setup ("test-exponential-backoff",
   GNUNET_log_setup ("test-exponential-backoff",
-#if VERBOSE
-                    "DEBUG",
-#else
-                    "WARNING",
-#endif
-                    NULL);
-  
-  init();
+                   "WARNING",
+                   NULL);
+
+  if (GNUNET_OK != init ())
+    return 1;
   ret = check ();
   ret = check ();
-  houseKeep();
+  houseKeep ();
   return ret;
 }
 
   return ret;
 }