From e33d72683c9ea445fdca81f3d33c948b650f482f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 13 Sep 2017 13:27:13 +0200 Subject: [PATCH] skeleton for /keys cherry picking testcase --- src/exchange-lib/Makefile.am | 14 +- .../test_exchange_api_keys_cherry_picking.c | 687 ++++++++++++++++++ 2 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 src/exchange-lib/test_exchange_api_keys_cherry_picking.c diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am index d891d1d62..d6e068b73 100644 --- a/src/exchange-lib/Makefile.am +++ b/src/exchange-lib/Makefile.am @@ -45,7 +45,8 @@ endif endif check_PROGRAMS = \ - test_exchange_api + test_exchange_api \ + test_exchange_api_keys_cherry_picking AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; @@ -64,6 +65,17 @@ test_exchange_api_LDADD = \ -lgnunetutil \ -ljansson +test_exchange_api_keys_cherry_picking_SOURCES = \ + test_exchange_api_keys_cherry_picking.c +test_exchange_api_keys_cherry_picking_LDADD = \ + libtalerexchange.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetcurl \ + -lgnunetutil \ + -ljansson + EXTRA_DIST = \ test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ test_exchange_api_home/.config/taler/test.json \ diff --git a/src/exchange-lib/test_exchange_api_keys_cherry_picking.c b/src/exchange-lib/test_exchange_api_keys_cherry_picking.c new file mode 100644 index 000000000..02e62a3ef --- /dev/null +++ b/src/exchange-lib/test_exchange_api_keys_cherry_picking.c @@ -0,0 +1,687 @@ +/* + This file is part of TALER + Copyright (C) 2014-2017 GNUnet e.V. and Inria + + TALER 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 3, or (at your option) any later version. + + TALER 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file exchange/test_exchange_api_keys_cherry_picking.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include +#include + + +/** + * Main execution context for the main loop. + */ +static struct GNUNET_CURL_Context *ctx; + +/** + * Handle to access the exchange. + */ +static struct TALER_EXCHANGE_Handle *exchange; + +/** + * Context for running the CURL event loop. + */ +static struct GNUNET_CURL_RescheduleContext *rc; + +/** + * Handle to the exchange process. + */ +static struct GNUNET_OS_Process *exchanged; + +/** + * Task run on timeout. + */ +static struct GNUNET_SCHEDULER_Task *timeout_task; + +/** + * Result of the testcases, #GNUNET_OK on success + */ +static int result; + + +/** + * Opcodes for the interpreter. + */ +enum OpCode +{ + /** + * Termination code, stops the interpreter loop (with success). + */ + OC_END = 0, + + /** + * Run a process. + */ + OC_RUN_PROCESS, + + /** + * Signal the exchange to reload the keys. + */ + OC_SIGNAL_EXCHANGE, + + /** + * Check the /keys. + */ + OC_CHECK_KEYS + +}; + + +/** + * Details for a exchange operation to execute. + */ +struct Command +{ + /** + * Opcode of the command. + */ + enum OpCode oc; + + /** + * Label for the command, can be NULL. + */ + const char *label; + + /** + * Details about the command. + */ + union + { + + struct { + + /** + * Binary to execute. + */ + const char *binary; + + /** + * Command-line arguments for the process to be run. + */ + char *const*argv; + + /** + * Process handle. + */ + struct GNUNET_OS_Process *proc; + + /** + * ID of task called whenever we get a SIGCHILD. + */ + struct GNUNET_SCHEDULER_Task *child_death_task; + + } run_process; + + struct { + + /** + * Expected number of denomination keys. + */ + unsigned int num_denom_keys; + + } check_keys; + + } details; + +}; + + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ + /** + * Keys from the exchange. + */ + const struct TALER_EXCHANGE_Keys *keys; + + /** + * Commands the interpreter will run. + */ + struct Command *commands; + + /** + * Interpreter task (if one is scheduled). + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Instruction pointer. Tells #interpreter_run() which + * instruction to run next. + */ + unsigned int ip; + +}; + + +/** + * Pipe used to communicate child death via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + + +/** + * The testcase failed, return with an error code. + * + * @param is interpreter state to clean up + */ +static void +fail (struct InterpreterState *is) +{ + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls); + + +/** + * Run the next command with the interpreter. + * + * @param is current interpeter state. + */ +static void +next_command (struct InterpreterState *is) +{ + if (GNUNET_SYSERR == result) + return; /* ignore, we already failed! */ + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure, NULL if we need to self-restart + */ +static void +maint_child_death (void *cls) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct GNUNET_DISK_FileHandle *pr; + char c[16]; + + switch (cmd->oc) { + case OC_RUN_PROCESS: + cmd->details.run_process.child_death_task = NULL; + pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); + GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); + GNUNET_OS_process_wait (cmd->details.run_process.proc); + GNUNET_OS_process_destroy (cmd->details.run_process.proc); + cmd->details.run_process.proc = NULL; + break; + default: + GNUNET_break (0); + fail (is); + return; + } + next_command (is); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + is->task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + { + fprintf (stderr, + "Test aborted by shutdown request\n"); + fail (is); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Running command `%s'\n", + cmd->label); + switch (cmd->oc) + { + case OC_END: + result = GNUNET_OK; + GNUNET_SCHEDULER_shutdown (); + return; + case OC_RUN_PROCESS: + { + const struct GNUNET_DISK_FileHandle *pr; + + cmd->details.run_process.proc + = GNUNET_OS_start_process_vap (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + cmd->details.run_process.binary, + cmd->details.run_process.argv); + if (NULL == cmd->details.run_process.proc) + { + GNUNET_break (0); + fail (is); + return; + } + pr = GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ); + cmd->details.run_process.child_death_task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + pr, + &maint_child_death, + is); + return; + } + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown instruction %d at %u (%s)\n", + cmd->oc, + is->ip, + cmd->label); + fail (is); + return; + } +} + + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle + (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; /* restore errno */ +} + + +/** + * Function run when the test terminates (good or bad) with timeout. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ + timeout_task = NULL; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + */ +static void +do_shutdown (void *cls) +{ + struct InterpreterState *is = cls; + struct Command *cmd; + unsigned int i; + + fprintf (stderr, + "Executing shutdown at `%s'\n", + is->commands[is->ip].label); + + for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) + { + switch (cmd->oc) + { + case OC_END: + GNUNET_assert (0); + break; + case OC_RUN_PROCESS: + if (NULL != cmd->details.run_process.proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (cmd->details.run_process.proc, + SIGKILL)); + GNUNET_OS_process_wait (cmd->details.run_process.proc); + GNUNET_OS_process_destroy (cmd->details.run_process.proc); + cmd->details.run_process.proc = NULL; + } + if (NULL != cmd->details.run_process.child_death_task) + { + GNUNET_SCHEDULER_cancel (cmd->details.run_process.child_death_task); + cmd->details.run_process.child_death_task = NULL; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown instruction %d at %u (%s)\n", + cmd->oc, + i, + cmd->label); + break; + } + } + if (NULL != is->task) + { + GNUNET_SCHEDULER_cancel (is->task); + is->task = NULL; + } + GNUNET_free (is); + if (NULL != exchange) + { + TALER_EXCHANGE_disconnect (exchange); + exchange = NULL; + } + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } + if (NULL != timeout_task) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the exchange. No TALER_EXCHANGE_*() functions should be called + * in this callback. + * + * @param cls closure + * @param keys information about keys of the exchange + * @param vc version compatibility + */ +static void +cert_cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_EXCHANGE_VersionCompatibility vc) +{ + struct InterpreterState *is = cls; + + /* check that keys is OK */ +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0) + ERR (NULL == keys); + ERR (0 == keys->num_sign_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u signing keys\n", + keys->num_sign_keys); + ERR (0 == keys->num_denom_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u denomination keys\n", + keys->num_denom_keys); +#undef ERR + + /* run actual tests via interpreter-loop */ + is->keys = keys; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + */ +static void +run (void *cls) +{ + struct InterpreterState *is; + static struct Command commands[] = + { + { .oc = OC_END } + }; + + is = GNUNET_new (struct InterpreterState); + is->commands = commands; + + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &rc); + GNUNET_assert (NULL != ctx); + rc = GNUNET_CURL_gnunet_rc_create (ctx); + exchange = TALER_EXCHANGE_connect (ctx, + "http://localhost:8081", + &cert_cb, is, + TALER_EXCHANGE_OPTION_END); + GNUNET_assert (NULL != exchange); + timeout_task + = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 300), + &do_timeout, NULL); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + is); +} + + +/** + * Remove files from previous runs + */ +static void +cleanup_files () +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + char *dir; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + "test_exchange_api.conf")) + { + GNUNET_break (0); + GNUNET_CONFIGURATION_destroy (cfg); + exit (77); + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, + "exchange", + "keydir", + &dir)); + if (GNUNET_YES == + GNUNET_DISK_directory_test (dir, + GNUNET_NO)) + GNUNET_break (GNUNET_OK == + GNUNET_DISK_directory_remove (dir)); + GNUNET_free (dir); + GNUNET_CONFIGURATION_destroy (cfg); +} + + +/** + * Main function for the testcase for the exchange API. + * + * @param argc expected to be 1 + * @param argv expected to only contain the program name + */ +int +main (int argc, + char * const *argv) +{ + struct GNUNET_OS_Process *proc; + struct GNUNET_SIGNAL_Context *shc_chld; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + unsigned int iter; + + /* These might get in the way... */ + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-exchange-api-keys-cherry-picking", + "INFO", + NULL); + if (GNUNET_OK != + GNUNET_NETWORK_test_port_free (IPPROTO_TCP, + 8081)) + { + fprintf (stderr, + "Required port %u not available, skipping.\n", + 8081); + return 77; + } + cleanup_files (); + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-keyup", + "taler-exchange-keyup", + "-c", "test_exchange_api.conf", + "-o", "auditor.in", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-auditor-sign", + "taler-auditor-sign", + "-c", "test_exchange_api.conf", + "-u", "http://auditor/", + "-m", "98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG", + "-r", "auditor.in", + "-o", "test_exchange_api_home/.local/share/taler/auditors/auditor.out", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); + return 77; + } + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-dbinit", + "taler-exchange-dbinit", + "-c", "test_exchange_api.conf", + "-r", + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to run `taler-exchange-dbinit`, is your PATH correct?\n"); + return 77; + } + if (GNUNET_SYSERR == + GNUNET_OS_process_wait_status (proc, + &type, + &code)) + { + GNUNET_break (0); + GNUNET_OS_process_destroy (proc); + return 1; + } + GNUNET_OS_process_destroy (proc); + if ( (type == GNUNET_OS_PROCESS_EXITED) && + (0 != code) ) + { + fprintf (stderr, + "Failed to setup database\n"); + return 77; + } + if ( (type != GNUNET_OS_PROCESS_EXITED) || + (0 != code) ) + { + fprintf (stderr, + "Unexpected error running `taler-exchange-dbinit'!\n"); + return 1; + } + exchanged = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-httpd", + "taler-exchange-httpd", + "-c", "test_exchange_api.conf", + "-i", + NULL); + /* give child time to start and bind against the socket */ + fprintf (stderr, + "Waiting for `taler-exchange-httpd' to be ready"); + iter = 0; + do + { + if (10 == iter) + { + fprintf (stderr, + "Failed to launch `taler-exchange-httpd' (or `wget')\n"); + GNUNET_OS_process_kill (exchanged, + SIGTERM); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + return 77; + } + fprintf (stderr, "."); + sleep (1); + iter++; + } + while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null")); + fprintf (stderr, "\n"); + result = GNUNET_NO; + sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO); + GNUNET_assert (NULL != sigpipe); + shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, + &sighandler_child_death); + GNUNET_SCHEDULER_run (&run, NULL); + GNUNET_SIGNAL_handler_uninstall (shc_chld); + shc_chld = NULL; + GNUNET_DISK_pipe_close (sigpipe); + GNUNET_OS_process_kill (exchanged, + SIGTERM); + GNUNET_OS_process_wait (exchanged); + GNUNET_OS_process_destroy (exchanged); + return (GNUNET_OK == result) ? 0 : 1; +} + +/* end of test_exchange_api_keys_cherry_picking.c */