summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2017-09-13 13:27:13 +0200
committerChristian Grothoff <christian@grothoff.org>2017-09-13 13:27:13 +0200
commite33d72683c9ea445fdca81f3d33c948b650f482f (patch)
tree15a7306eef1001c046d0ed1963f1cff80c5da615
parent6d7645a99b523cb36d692de3e8d3bc80ddd8f21f (diff)
downloadexchange-e33d72683c9ea445fdca81f3d33c948b650f482f.tar.gz
exchange-e33d72683c9ea445fdca81f3d33c948b650f482f.tar.bz2
exchange-e33d72683c9ea445fdca81f3d33c948b650f482f.zip
skeleton for /keys cherry picking testcase
-rw-r--r--src/exchange-lib/Makefile.am14
-rw-r--r--src/exchange-lib/test_exchange_api_keys_cherry_picking.c687
2 files changed, 700 insertions, 1 deletions
diff --git a/src/exchange-lib/Makefile.am b/src/exchange-lib/Makefile.am
index d891d1d6..d6e068b7 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 00000000..02e62a3e
--- /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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+
+
+/**
+ * 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 */