diff options
author | Amitay Isaacs <amitay@gmail.com> | 2017-02-23 18:40:48 +1100 |
---|---|---|
committer | Martin Schwenke <martins@samba.org> | 2017-05-30 03:58:06 +0200 |
commit | 98ee7e799171c270460d966a4d4700072eb41ed0 (patch) | |
tree | 1d57a1676e2a1b1887e38fafb24ae631759533ef | |
parent | 16c188c7f8e335e5c8c191148be2f8347020178a (diff) | |
download | samba-98ee7e799171c270460d966a4d4700072eb41ed0.tar.gz |
ctdb-common: Add run_event abstraction
Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
-rw-r--r-- | ctdb/common/run_event.c | 796 | ||||
-rw-r--r-- | ctdb/common/run_event.h | 148 | ||||
-rwxr-xr-x | ctdb/tests/cunit/run_event_001.sh | 134 | ||||
-rw-r--r-- | ctdb/tests/src/run_event_test.c | 205 | ||||
-rw-r--r-- | ctdb/wscript | 5 |
5 files changed, 1286 insertions, 2 deletions
diff --git a/ctdb/common/run_event.c b/ctdb/common/run_event.c new file mode 100644 index 00000000000..e5d562c96b3 --- /dev/null +++ b/ctdb/common/run_event.c @@ -0,0 +1,796 @@ +/* + Run scripts in a directory with specific event arguments + + Copyright (C) Amitay Isaacs 2017 + + This program 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 of the License, or + (at your option) any later version. + + This program 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 this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/dir.h" +#include "system/locale.h" +#include "system/wait.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" + +#include "common/logging.h" +#include "common/run_proc.h" +#include "common/run_event.h" + +/* + * Utility functions + */ + +static int script_filter(const struct dirent *de) +{ + size_t namelen = strlen(de->d_name); + char *ptr; + + /* Ignore . and .. */ + if (namelen < 3) { + return 0; + } + + /* Skip filenames with ~ */ + ptr = strchr(de->d_name, '~'); + if (ptr != NULL) { + return 0; + } + + /* Filename should start with [0-9][0-9]. */ + if ((! isdigit(de->d_name[0])) || + (! isdigit(de->d_name[1])) || + (de->d_name[2] != '.')) { + return 0; + } + + return 1; +} + +static int get_script_list(TALLOC_CTX *mem_ctx, + const char *script_dir, + struct run_event_script_list **out) +{ + struct dirent **namelist = NULL; + struct run_event_script_list *script_list; + int count, ret; + int i; + + count = scandir(script_dir, &namelist, script_filter, alphasort); + if (count == -1) { + ret = errno; + if (ret == ENOENT) { + D_WARNING("event script dir %s removed\n", script_dir); + } else { + D_WARNING("scandir() failed on %s, ret=%d\n", + script_dir, ret); + } + *out = NULL; + ret = 0; + goto done; + } + + if (count == 0) { + *out = NULL; + ret = 0; + goto done; + } + + script_list = talloc_zero(mem_ctx, struct run_event_script_list); + if (script_list == NULL) { + return ENOMEM; + } + + script_list->num_scripts = count; + script_list->script = talloc_zero_array(script_list, + struct run_event_script, + count); + if (script_list->script == NULL) { + ret = ENOMEM; + talloc_free(script_list); + goto done; + } + + for (i=0; i<count; i++) { + struct run_event_script *s = &script_list->script[i]; + + s->name = talloc_strdup(script_list, namelist[i]->d_name); + if (s->name == NULL) { + ret = ENOMEM; + talloc_free(script_list); + goto done; + } + } + + *out = script_list; + ret = 0; + +done: + if (namelist != NULL && count != -1) { + for (i=0; i<count; i++) { + free(namelist[i]); + } + free(namelist); + } + return ret; +} + +static int script_chmod(TALLOC_CTX *mem_ctx, const char *script_dir, + const char *script_name, bool enable) +{ + DIR *dirp; + struct dirent *de; + int ret, new_mode; + char *filename; + struct stat st; + bool found; + int fd = -1; + + dirp = opendir(script_dir); + if (dirp == NULL) { + return errno; + } + + found = false; + while ((de = readdir(dirp)) != NULL) { + if (strcmp(de->d_name, script_name) == 0) { + + /* check for valid script names */ + ret = script_filter(de); + if (ret == 0) { + closedir(dirp); + return EINVAL; + } + + found = true; + break; + } + } + closedir(dirp); + + if (! found) { + return ENOENT; + } + + filename = talloc_asprintf(mem_ctx, "%s/%s", script_dir, script_name); + if (filename == NULL) { + return ENOMEM; + } + + fd = open(filename, O_RDWR); + if (fd == -1) { + ret = errno; + goto done; + } + + ret = fstat(fd, &st); + if (ret != 0) { + ret = errno; + goto done; + } + + if (enable) { + new_mode = st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH); + } else { + new_mode = st.st_mode & ~(S_IXUSR | S_IXGRP | S_IXOTH); + } + + ret = fchmod(fd, new_mode); + if (ret != 0) { + ret = errno; + goto done; + } + +done: + if (fd != -1) { + close(fd); + } + talloc_free(filename); + return ret; +} + +static int script_args(TALLOC_CTX *mem_ctx, const char *event_str, + const char *arg_str, const char ***out) +{ + const char **argv; + int argc; + size_t len; + + /* Preallocate argv array to avoid reallocation. */ + len = 8; + argv = talloc_array(mem_ctx, const char *, len); + if (argv == NULL) { + return ENOMEM; + } + + argv[0] = NULL; /* script name */ + argv[1] = event_str; + argc = 2; + + if (arg_str != NULL) { + char *str, *t, *tok; + + str = talloc_strdup(argv, arg_str); + if (str == NULL) { + return ENOMEM; + } + + t = str; + while ((tok = strtok(t, " ")) != NULL) { + argv[argc] = talloc_strdup(argv, tok); + if (argv[argc] == NULL) { + talloc_free(argv); + return ENOMEM; + } + argc += 1; + if (argc >= len) { + argv = talloc_realloc(mem_ctx, argv, + const char *, len + 8); + if (argv == NULL) { + return ENOMEM; + } + len += 8; + } + t = NULL; + } + + talloc_free(str); + } + + argv[argc] = NULL; + argc += 1; + + *out = argv; + return 0; +} + +struct run_event_context { + struct run_proc_context *run_proc_ctx; + const char *script_dir; + const char *debug_prog; + bool debug_running; +}; + + +int run_event_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const char *script_dir, const char *debug_prog, + struct run_event_context **out) +{ + struct run_event_context *run_ctx; + struct stat st; + int ret; + + run_ctx = talloc_zero(mem_ctx, struct run_event_context); + if (run_ctx == NULL) { + return ENOMEM; + } + + ret = run_proc_init(run_ctx, ev, &run_ctx->run_proc_ctx); + if (ret != 0) { + talloc_free(run_ctx); + return ret; + } + + ret = stat(script_dir, &st); + if (ret != 0) { + ret = errno; + talloc_free(run_ctx); + return ret; + } + + if (! S_ISDIR(st.st_mode)) { + talloc_free(run_ctx); + return EINVAL; + } + + run_ctx->script_dir = talloc_strdup(run_ctx, script_dir); + if (run_ctx->script_dir == NULL) { + talloc_free(run_ctx); + return ENOMEM; + } + + if (debug_prog != NULL) { + run_ctx->debug_prog = talloc_strdup(run_ctx, debug_prog); + if (run_ctx->debug_prog == NULL) { + talloc_free(run_ctx); + return ENOMEM; + } + } + + run_ctx->debug_running = false; + + *out = run_ctx; + return 0; +} + +static struct run_proc_context * +run_event_run_proc_context(struct run_event_context *run_ctx) +{ + return run_ctx->run_proc_ctx; +} + +static const char *run_event_script_dir(struct run_event_context *run_ctx) +{ + return run_ctx->script_dir; +} + +static const char *run_event_debug_prog(struct run_event_context *run_ctx) +{ + return run_ctx->debug_prog; +} + +static int run_event_script_status(struct run_event_script *script) +{ + int ret; + + if (script->result.sig > 0) { + ret = -EINTR; + } else if (script->result.err > 0) { + if (script->result.err == EACCES) { + /* Map EACCESS to ENOEXEC */ + ret = -ENOEXEC; + } else { + ret = -script->result.err; + } + } else { + ret = script->result.status; + } + + return ret; +} + +int run_event_script_list(struct run_event_context *run_ctx, + TALLOC_CTX *mem_ctx, + struct run_event_script_list **output) +{ + struct run_event_script_list *script_list; + int ret, i; + + ret = get_script_list(mem_ctx, run_event_script_dir(run_ctx), + &script_list); + if (ret != 0) { + return ret; + } + + if (script_list == NULL) { + *output = NULL; + return 0; + } + + for (i=0; i<script_list->num_scripts; i++) { + struct run_event_script *script = &script_list->script[i]; + struct stat st; + char *path = NULL; + + path = talloc_asprintf(mem_ctx, "%s/%s", + run_event_script_dir(run_ctx), + script->name); + if (path == NULL) { + continue; + } + + ret = stat(path, &st); + if (ret != 0) { + TALLOC_FREE(path); + continue; + } + + if (! (st.st_mode & S_IXUSR)) { + script->summary = -ENOEXEC; + } + + TALLOC_FREE(path); + } + + *output = script_list; + return 0; +} + +int run_event_script_enable(struct run_event_context *run_ctx, + const char *script_name) +{ + return script_chmod(run_ctx, run_event_script_dir(run_ctx), + script_name, true); +} + +int run_event_script_disable(struct run_event_context *run_ctx, + const char *script_name) +{ + return script_chmod(run_ctx, run_event_script_dir(run_ctx), + script_name, false); +} + +/* + * Run debug program to diagnose hung scripts + */ + +static int debug_args(TALLOC_CTX *mem_ctx, const char *path, + const char *event_str, pid_t pid, const char ***out) +{ + const char **argv; + + argv = talloc_array(mem_ctx, const char *, 4); + if (argv == NULL) { + return ENOMEM; + } + + argv[0] = path; + argv[1] = talloc_asprintf(argv, "%d", pid); + argv[2] = event_str; + if (argv[1] == NULL) { + talloc_free(argv); + return ENOMEM; + } + argv[3] = NULL; + + *out = argv; + return 0; +} + +static void debug_log(int loglevel, const char *output, const char *log_prefix) +{ + char *line, *s; + + s = strdup(output); + if (s == NULL) { + DEBUG(loglevel, ("%s: %s\n", log_prefix, output)); + return; + } + + line = strtok(s, "\n"); + while (line != NULL) { + DEBUG(loglevel, ("%s: %s\n", log_prefix, line)); + line = strtok(NULL, "\n"); + } + free(s); +} + +struct run_debug_state { + struct run_event_context *run_ctx; + pid_t pid; +}; + +static void run_debug_done(struct tevent_req *subreq); + +static struct tevent_req *run_debug_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct run_event_context *run_ctx, + const char *event_str, pid_t pid) +{ + struct tevent_req *req, *subreq; + struct run_debug_state *state; + const char **argv; + const char *debug_prog; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct run_debug_state); + if (req == NULL) { + return NULL; + } + + state->run_ctx = run_ctx; + state->pid = pid; + + debug_prog = run_event_debug_prog(run_ctx); + if (debug_prog == NULL) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (run_ctx->debug_running) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (pid == -1) { + D_DEBUG("Event script terminated, nothing to debug\n"); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + ret = debug_args(state, debug_prog, event_str, pid, &argv); + if (ret != 0) { + D_ERR("debug_args() failed\n"); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + D_DEBUG("Running debug %s with args \"%s %s\"\n", + debug_prog, argv[1], argv[2]); + + subreq = run_proc_send(state, ev, run_event_run_proc_context(run_ctx), + debug_prog, argv, -1, tevent_timeval_zero()); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, run_debug_done, req); + + run_ctx->debug_running = true; + + talloc_free(argv); + return req; +} + +static void run_debug_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct run_debug_state *state = tevent_req_data( + req, struct run_debug_state); + char *output; + int ret; + bool status; + + state->run_ctx->debug_running = false; + + status = run_proc_recv(subreq, &ret, NULL, NULL, state, &output); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("Running debug failed, ret=%d\n", ret); + } + + /* Log output */ + if (output != NULL) { + debug_log(DEBUG_ERR, output, "event_debug"); + talloc_free(output); + } + + kill(-state->pid, SIGTERM); + tevent_req_done(req); +} + +static bool run_debug_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +/* + * Run a single event + */ + +struct run_event_state { + struct tevent_context *ev; + struct run_event_context *run_ctx; + const char *event_str; + struct timeval timeout; + + struct run_event_script_list *script_list; + const char **argv; + int index; + int status; +}; + +static struct tevent_req *run_event_run_script(struct tevent_req *req); +static void run_event_next_script(struct tevent_req *subreq); +static void run_event_debug(struct tevent_req *req, pid_t pid); +static void run_event_debug_done(struct tevent_req *subreq); + +struct tevent_req *run_event_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct run_event_context *run_ctx, + const char *event_str, + const char *arg_str, + struct timeval timeout) +{ + struct tevent_req *req, *subreq; + struct run_event_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct run_event_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->run_ctx = run_ctx; + state->event_str = talloc_strdup(state, event_str); + if (tevent_req_nomem(state->event_str, req)) { + return tevent_req_post(req, ev); + } + state->timeout = timeout; + + ret = get_script_list(state, run_event_script_dir(run_ctx), + &state->script_list); + if (ret != 0) { + D_ERR("get_script_list() failed, ret=%d\n", ret); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + /* No scripts */ + if (state->script_list == NULL || + state->script_list->num_scripts == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + ret = script_args(state, event_str, arg_str, &state->argv); + if (ret != 0) { + D_ERR("script_args() failed, ret=%d\n", ret); + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + state->index = 0; + + subreq = run_event_run_script(req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, run_event_next_script, req); + + return req; +} + +static struct tevent_req *run_event_run_script(struct tevent_req *req) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct run_event_script *script; + struct tevent_req *subreq; + char *path; + + script = &state->script_list->script[state->index]; + + path = talloc_asprintf(state, "%s/%s", + run_event_script_dir(state->run_ctx), + script->name); + if (path == NULL) { + return NULL; + } + + state->argv[0] = script->name; + script->begin = tevent_timeval_current(); + + D_DEBUG("Running %s with args \"%s %s\"\n", + path, state->argv[0], state->argv[1]); + + subreq = run_proc_send(state, state->ev, + run_event_run_proc_context(state->run_ctx), + path, state->argv, -1, state->timeout); + + talloc_free(path); + + return subreq; +} + +static void run_event_next_script(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct run_event_script *script; + pid_t pid; + int ret; + bool status; + + script = &state->script_list->script[state->index]; + script->end = tevent_timeval_current(); + + status = run_proc_recv(subreq, &ret, &script->result, &pid, + state->script_list, &script->output); + TALLOC_FREE(subreq); + if (! status) { + D_ERR("run_proc failed for %s, ret=%d\n", script->name, ret); + tevent_req_error(req, ret); + return; + } + + /* Log output */ + if (script->output != NULL) { + debug_log(DEBUG_ERR, script->output, script->name); + } + + D_DEBUG("Script %s finished sig=%d, err=%d, status=%d\n", + script->name, script->result.sig, script->result.err, + script->result.status); + + + /* If a script fails, stop running */ + script->summary = run_event_script_status(script); + if (script->summary != 0 && script->summary != -ENOEXEC) { + state->script_list->num_scripts = state->index + 1; + + if (script->summary == -ETIME && pid != -1) { + run_event_debug(req, pid); + } + + state->script_list->summary = script->summary; + D_NOTICE("%s event %s\n", state->event_str, + (script->summary == -ETIME) ? "timed out" : "failed"); + + tevent_req_done(req); + return; + } + + state->index += 1; + + /* All scripts executed */ + if (state->index >= state->script_list->num_scripts) { + tevent_req_done(req); + return; + } + + subreq = run_event_run_script(req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, run_event_next_script, req); +} + +static void run_event_debug(struct tevent_req *req, pid_t pid) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + struct tevent_req *subreq; + + /* Debug script is run with ectx as the memory context */ + subreq = run_debug_send(state->run_ctx, state->ev, state->run_ctx, + state->event_str, pid); + if (subreq == NULL) { + /* If run debug fails, it's not an error */ + D_NOTICE("Failed to run event debug\n"); + return; + } + tevent_req_set_callback(subreq, run_event_debug_done, NULL); +} + +static void run_event_debug_done(struct tevent_req *subreq) +{ + int ret = 0; + bool status; + + status = run_debug_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + D_NOTICE("run_debug() failed, ret=%d\n", ret); + } +} + +bool run_event_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct run_event_script_list **script_list) +{ + struct run_event_state *state = tevent_req_data( + req, struct run_event_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (script_list != NULL) { + *script_list = talloc_steal(mem_ctx, state->script_list); + } + return true; +} + diff --git a/ctdb/common/run_event.h b/ctdb/common/run_event.h new file mode 100644 index 00000000000..b7266875e9c --- /dev/null +++ b/ctdb/common/run_event.h @@ -0,0 +1,148 @@ +/* + Run scripts in a directory with specific event arguments + + Copyright (C) Amitay Isaacs 2017 + + This program 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 of the License, or + (at your option) any later version. + + This program 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 this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_RUN_EVENT_H__ +#define __CTDB_RUN_EVENT_H__ + +#include <talloc.h> +#include <tevent.h> + +#include "common/run_proc.h" + +/** + * @file run_event.h + * + * @brief Run scripts in a directory with specific event arguments. + * + * This abstraction allows to execute multiple scripts in a directory + * (specified by script_dir) with given event and arguments. + * + * At one time, only one event can be run. Multiple run_event calls + * will cause events to be queued up. They will be run sequentially. + * + * A "monitor" event is special and has special semantics. + * + * If a monitor event is running and another event is scheduled, the + * currently running monitor event is cancelled. + * + * If an event (not monitor) is running and monitor event is scheduled, + * then the monior event will be cancelled immediately. + */ + +/** + * @brief The run process context + */ +struct run_event_context; + +struct run_event_script { + char *name; + struct timeval begin, end; + struct run_proc_result result; + int summary; + char *output; +}; + +struct run_event_script_list { + uint32_t num_scripts; + struct run_event_script *script; + int summary; +}; + + +/** + * @brief Initialize the context for running events + * + * @param[in] mem_ctx Talloc memory context + * @param[in] ev Tevent context + * @param[in] script_dir Directory containing script to run + * @param[in] debug_prog Path of a program to run if a script hangs + * @param[out] result New run_event context + * @return 0 on success, errno on error + */ +int run_event_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const char *script_dir, const char *debug_prog, + struct run_event_context **result); + +/** + * @brief Get a list of scripts + * + * @param[in] run_ctx Run_event context + * @param[in] mem_ctx Talloc memory context + * @param[out] output List of valid scripts + * @return 0 on success, errno on failure + */ +int run_event_script_list(struct run_event_context *run_ctx, + TALLOC_CTX *mem_ctx, + struct run_event_script_list **output); + +/** + * @brief Enable a script + * + * @param[in] run_ctx Run_event context + * @param[in] script_name Name of the script to enable + * @return 0 on success, errno on failure + */ +int run_event_script_enable(struct run_event_context *run_ctx, + const char *script_name); + +/** + * @brief Disable a script + * + * @param[in] run_ctx Run_event context + * @param[in] script_name Name of the script to disable + * @return 0 on success, errno on failure + */ +int run_event_script_disable(struct run_event_context *run_ctx, + const char *script_name); + +/** + * @brief Async computation start to run an event + * + * @param[in] mem_ctx Talloc memory context + * @param[in] ev Tevent context + * @param[in] run_ctx Run_event context + * @param[in] event_str The event argument to the script + * @param[in] arg_str Event arguments to the script + * @param[in] timeout How long to wait for execution + * @return new tevent request, or NULL on failure + * + * arg_str contains optional arguments for an event. + */ +struct tevent_req *run_event_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct run_event_context *run_ctx, + const char *event_str, + const char *arg_str, + struct timeval timeout); + +/** + * @brief Async computation end to run an event + * + * @param[in] req Tevent request + * @param[out] perr errno in case of failure + * @param[in] mem_ctx Talloc memory context + * @param[out] output List of scripts executed and their status + * @return true on success, false on failure + */ +bool run_event_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, + struct run_event_script_list **output); + +#endif /* __CTDB_RUN_EVENT_H__ */ + diff --git a/ctdb/tests/cunit/run_event_001.sh b/ctdb/tests/cunit/run_event_001.sh new file mode 100755 index 00000000000..f0ffe4ff0c6 --- /dev/null +++ b/ctdb/tests/cunit/run_event_001.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +. "${TEST_SCRIPTS_DIR}/unit.sh" + +# Invalid path +required_result 1 <<EOF +run_event_init() failed, ret=2 +EOF +unit_test run_event_test /a/b/c list + +scriptdir=$(mktemp -d --tmpdir="$TEST_VAR_DIR") + +# Empty directory +ok <<EOF +No event scripts found +EOF +unit_test run_event_test "$scriptdir" list + +cat > "$scriptdir/prog" <<EOF +#!/bin/sh + +echo hello +EOF + +# Invalid script +ok <<EOF +No event scripts found +EOF +unit_test run_event_test "$scriptdir" list + +ok <<EOF +Script enable prog completed with result=22 +EOF +unit_test run_event_test "$scriptdir" enable prog + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/prog" + +cat > "$scriptdir/11.foo" <<EOF +#!/bin/sh + +echo hello +EOF + +# Valid script +ok <<EOF +11.foo +EOF +unit_test run_event_test "$scriptdir" list + +ok <<EOF +Script enable 11.foo completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 11.foo + +ok <<EOF +EOF +unit_test test -x "${scriptdir}/11.foo" + +ok <<EOF +11.foo: hello +Event monitor completed with result=0 +11.foo result=0 +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +cat > "$scriptdir/22.bar" <<EOF +#!/bin/sh + +exit 1 +EOF + +# Multiple scripts +ok <<EOF +11.foo +22.bar +EOF +unit_test run_event_test "$scriptdir" list + +ok <<EOF +Script enable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 22.bar + +ok <<EOF +11.foo: hello +Event monitor completed with result=1 +11.foo result=0 +22.bar result=1 +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +# Disable script +ok <<EOF +Script disable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" disable 22.bar + +required_result 1 <<EOF +EOF +unit_test test -x "${scriptdir}/22.bar" + +ok <<EOF +11.foo: hello +Event monitor completed with result=0 +11.foo result=0 +22.bar result=-8 +EOF +unit_test run_event_test "$scriptdir" run 10 monitor + +cat > "$scriptdir/22.bar" <<EOF +#!/bin/sh + +sleep 10 +EOF + +# Timed out script +ok <<EOF +Script enable 22.bar completed with result=0 +EOF +unit_test run_event_test "$scriptdir" enable 22.bar + +ok <<EOF +11.foo: hello +Event monitor completed with result=-62 +11.foo result=0 +22.bar result=-62 +EOF +unit_test run_event_test "$scriptdir" run 5 monitor + +rm -rf "$scriptdir" +exit 0 + diff --git a/ctdb/tests/src/run_event_test.c b/ctdb/tests/src/run_event_test.c new file mode 100644 index 00000000000..5539ceb57c7 --- /dev/null +++ b/ctdb/tests/src/run_event_test.c @@ -0,0 +1,205 @@ +/* + run_event test wrapper + + Copyright (C) Amitay Isaacs 2017 + + This program 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 of the License, or + (at your option) any later version. + + This program 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 this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "common/db_hash.c" +#include "common/run_proc.c" +#include "common/run_event.c" + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s <scriptdir> run|list|enable|disable <options>\n", prog); + fprintf(stderr, " %s <scriptdir> run <timeout> <event> [<args>]\n", prog); + fprintf(stderr, " %s <scriptdir> list\n", prog); + fprintf(stderr, " %s <scriptdir> enable <scriptname>\n", prog); + fprintf(stderr, " %s <scriptdir> disable <scriptname>\n", prog); +} + +static char *compact_args(const char **argv, int argc, int from) +{ + char *arg_str = NULL; + int i; + + for (i = from; i < argc; i++) { + arg_str = talloc_asprintf_append(arg_str, "%s ", argv[i]); + if (arg_str == NULL) { + fprintf(stderr, "talloc_asprintf_append() failed\n"); + exit(1); + } + } + + return arg_str; +} + +static void do_run(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct tevent_req *req; + struct timeval timeout; + struct run_event_script_list *script_list = NULL; + char *arg_str; + int ret, t, i; + bool status; + + if (argc < 5) { + usage(argv[0]); + exit(1); + } + + t = atoi(argv[3]); + if (t > 0) { + timeout = tevent_timeval_current_ofs(t, 0); + } else { + timeout = tevent_timeval_zero(); + } + + arg_str = compact_args(argv, argc, 5); + + req = run_event_send(mem_ctx, ev, run_ctx, argv[4], arg_str, timeout); + if (req == NULL) { + fprintf(stderr, "run_proc_send() failed\n"); + return; + } + + tevent_req_poll(req, ev); + + status = run_event_recv(req, &ret, mem_ctx, &script_list); + if (! status) { + fprintf(stderr, "run_proc_recv() failed, ret=%d\n", ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + printf("Event %s completed with result=%d\n", + argv[4], script_list->summary); + for (i=0; i<script_list->num_scripts; i++) { + printf("%s result=%d\n", script_list->script[i].name, + script_list->script[i].summary); + } +} + +static void do_list(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct run_event_script_list *script_list = NULL; + int ret, i; + + ret = run_event_script_list(run_ctx, mem_ctx, &script_list); + if (ret != 0) { + printf("Script list failed with result=%d\n", ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + for (i=0; i<script_list->num_scripts; i++) { + printf("%s\n", script_list->script[i].name); + } +} + +static void do_enable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_enable(run_ctx, argv[3]); + printf("Script enable %s completed with result=%d\n", argv[3], ret); +} + +static void do_disable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_disable(run_ctx, argv[3]); + printf("Script disable %s completed with result=%d\n", argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct run_event_context *run_ctx; + int ret; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + ret = run_event_init(mem_ctx, ev, argv[1], NULL, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_event_init() failed, ret=%d\n", ret); + exit(1); + } + + if (strcmp(argv[2], "run") == 0) { + do_run(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "list") == 0) { + do_list(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "enable") == 0) { + do_enable(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "disable") == 0) { + do_disable(mem_ctx, ev, run_ctx, argc, argv); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} + diff --git a/ctdb/wscript b/ctdb/wscript index 817d48777c7..7197b2a7c9a 100644 --- a/ctdb/wscript +++ b/ctdb/wscript @@ -385,7 +385,7 @@ def build(bld): pkt_read.c pkt_write.c comm.c logging.c rb_tree.c tunable.c pidfile.c run_proc.c - hash_count.c'''), + hash_count.c run_event.c'''), deps='''samba-util sys_rw tevent-util replace talloc tevent tdb''') @@ -733,7 +733,8 @@ def build(bld): 'run_proc_test', 'sock_daemon_test', 'sock_io_test', - 'hash_count_test' + 'hash_count_test', + 'run_event_test', ] for target in ctdb_unit_tests: |