/* This file is part of GNUnet. 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 Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @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 "gnunet_util_lib.h" #include "gnunet_protocols.h" #define LOG(...) GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__) #define LOG_BACKOFF GNUNET_NO #define TIMEOUT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 10) #define SERVICE_TEST_TIMEOUT GNUNET_TIME_UNIT_FOREVER_REL #define FIVE_MILLISECONDS GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_MILLISECONDS, 5) #define SERVICE "do-nothing" #define BINARY "mockup-service" #define CFGFILENAME "test_arm_api_data2.conf" static const struct GNUNET_CONFIGURATION_Handle *cfg; static struct GNUNET_ARM_Handle *arm; static struct GNUNET_ARM_MonitorHandle *mon; static struct GNUNET_SCHEDULER_Task *kt; 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 char *killLogFileName; #endif /** * Context for handling the shutdown of a service. */ struct ShutdownContext { /** * 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 kill_task(void *cbData); /** * Shutting down took too long, cancel receive and return error. * * @param cls closure */ static void service_shutdown_timeout(void *cls) { 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 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 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 trigger_disconnect(void *cls) { GNUNET_ARM_disconnect(arm); GNUNET_ARM_monitor_stop(mon); if (NULL != kt) { GNUNET_SCHEDULER_cancel(kt); kt = NULL; } } static void 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 srv_status(void *cls, const char *service, enum GNUNET_ARM_ServiceMonitorStatus status) { 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; } 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, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { cfg = c; 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[] = { "test-exponential-backoff", "-c", CFGFILENAME, NULL }; struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; /* Running ARM and running the do_nothing task */ GNUNET_assert(GNUNET_OK == GNUNET_PROGRAM_run((sizeof(argv) / sizeof(char *)) - 1, argv, "test-exponential-backoff", "nohelp", options, &task, NULL)); return ok; } #ifndef PATH_MAX /** * Assumed maximum path length (for the log file name). */ #define PATH_MAX 4096 #endif static int init() { 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")) { GNUNET_CONFIGURATION_destroy(cfg); return GNUNET_SYSERR; } if (NULL == getcwd(pwd, PATH_MAX)) 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 houseKeep() { #if LOG_BACKOFF GNUNET_assert(0 == fclose(killLogFilePtr)); GNUNET_free(killLogFileName); #endif (void)unlink(CFGFILENAME); } int main(int argc, char *argv[]) { int ret; GNUNET_log_setup("test-exponential-backoff", "WARNING", NULL); if (GNUNET_OK != init()) return 1; ret = check(); houseKeep(); return ret; } /* end of test_exponential_backoff.c */