diff options
Diffstat (limited to 'tools/stonith_admin.c')
-rw-r--r-- | tools/stonith_admin.c | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c new file mode 100644 index 0000000..1077de7 --- /dev/null +++ b/tools/stonith_admin.c @@ -0,0 +1,674 @@ +/* + * Copyright 2009-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <sys/param.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/utsname.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/common/ipc.h> +#include <crm/cluster/internal.h> +#include <crm/common/cmdline_internal.h> +#include <crm/common/output_internal.h> + +#include <crm/stonith-ng.h> +#include <crm/fencing/internal.h> +#include <crm/cib.h> +#include <crm/pengine/status.h> + +#include <crm/common/xml.h> +#include <pacemaker-internal.h> + +#define SUMMARY "stonith_admin - Access the Pacemaker fencing API" + +char action = 0; + +struct { + gboolean as_nodeid; + gboolean broadcast; + gboolean cleanup; + gboolean installed; + gboolean metadata; + gboolean registered; + gboolean validate_cfg; + stonith_key_value_t *devices; + stonith_key_value_t *params; + int fence_level; + int timeout ; + int tolerance; + int delay; + char *agent; + char *confirm_host; + char *fence_host; + char *history; + char *last_fenced; + char *query; + char *reboot_host; + char *register_dev; + char *register_level; + char *targets; + char *terminate; + char *unfence_host; + char *unregister_dev; + char *unregister_level; +} options = { + .timeout = 120, + .delay = 0 +}; + +gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); +gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); + +#define INDENT " " + +/* *INDENT-OFF* */ +static GOptionEntry defn_entries[] = { + { "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev, + "Register the named stonith device. Requires: --agent.\n" + INDENT "Optional: --option, --env-option.", + "DEVICE" }, + { "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev, + "De-register the named stonith device.", + "DEVICE" }, + { "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level, + "Register a stonith level for the named target,\n" + INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n" + INDENT "Requires: --index and one or more --device entries.", + "TARGET" }, + { "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level, + "Unregister a stonith level for the named target,\n" + INDENT "specified as for --register-level. Requires: --index", + "TARGET" }, + + { NULL } +}; + +static GOptionEntry query_entries[] = { + { "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate, + "List devices that can terminate the specified host.\n" + INDENT "Optional: --timeout", + "HOST" }, + { "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered, + "List all registered devices. Optional: --timeout.", + NULL }, + { "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed, + "List all installed devices. Optional: --timeout.", + NULL }, + { "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets, + "List the targets that can be fenced by the\n" + INDENT "named device. Optional: --timeout.", + "DEVICE" }, + { "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata, + "Show agent metadata. Requires: --agent.\n" + INDENT "Optional: --timeout.", + NULL }, + { "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query, + "Check the named device's status. Optional: --timeout.", + "DEVICE" }, + { "history", 'H', 0, G_OPTION_ARG_STRING, &options.history, + "Show last successful fencing operation for named node\n" + INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n" + INDENT "--quiet (show only the operation's epoch timestamp),\n" + INDENT "--verbose (show all recorded and pending operations),\n" + INDENT "--broadcast (update history from all nodes available).", + "NODE" }, + { "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced, + "Indicate when the named node was last fenced.\n" + INDENT "Optional: --as-node-id.", + "NODE" }, + { "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg, + "Validate a fence device configuration.\n" + INDENT "Requires: --agent. Optional: --option, --env-option,\n" + INDENT "--quiet (print no output, only return status).", + NULL }, + + { NULL } +}; + +static GOptionEntry fence_entries[] = { + { "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host, + "Fence named host. Optional: --timeout, --tolerance, --delay.", + "HOST" }, + { "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host, + "Unfence named host. Optional: --timeout, --tolerance, --delay.", + "HOST" }, + { "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host, + "Reboot named host. Optional: --timeout, --tolerance, --delay.", + "HOST" }, + { "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host, + "Tell cluster that named host is now safely down.", + "HOST", }, + + { NULL } +}; + +static GOptionEntry addl_entries[] = { + { "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup, + "Cleanup wherever appropriate. Requires --history.", + NULL }, + { "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast, + "Broadcast wherever appropriate.", + NULL }, + { "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent, + "The agent to use (for example, fence_xvm;\n" + INDENT "with --register, --metadata, --validate).", + "AGENT" }, + { "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params, + "Specify a device configuration parameter as NAME=VALUE\n" + INDENT "(may be specified multiple times; with --register,\n" + INDENT "--validate).", + "PARAM" }, + { "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params, + "Specify a device configuration parameter with the\n" + INDENT "specified name, using the value of the\n" + INDENT "environment variable of the same name prefixed with\n" + INDENT "OCF_RESKEY_ (may be specified multiple times;\n" + INDENT "with --register, --validate).", + "PARAM" }, + { "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag, + "Identify fencing operations in logs with the specified\n" + INDENT "tag; useful when multiple entities might invoke\n" + INDENT "stonith_admin (used with most commands).", + "TAG" }, + { "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device, + "Device ID (with --register-level, device to associate with\n" + INDENT "a given host and level; may be specified multiple times)" +#if SUPPORT_CIBSECRETS + "\n" INDENT "(with --validate, name to use to load CIB secrets)" +#endif + ".", + "DEVICE" }, + { "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level, + "The stonith level (1-9) (with --register-level,\n" + INDENT "--deregister-level).", + "LEVEL" }, + { "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout, + "Operation timeout in seconds (default 120;\n" + INDENT "used with most commands).", + "SECONDS" }, + { "delay", 'y', 0, G_OPTION_ARG_INT, &options.delay, + "Apply a fencing delay in seconds. Any static/random delays from\n" + INDENT "pcmk_delay_base/max will be added, otherwise all\n" + INDENT "disabled with the value -1\n" + INDENT "(default 0; with --fence, --reboot, --unfence).", + "SECONDS" }, + { "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid, + "(Advanced) The supplied node is the corosync node ID\n" + INDENT "(with --last).", + NULL }, + { "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance, + "(Advanced) Do nothing if an equivalent --fence request\n" + INDENT "succeeded less than this many seconds earlier\n" + INDENT "(with --fence, --unfence, --reboot).", + "SECONDS" }, + + { NULL } +}; +/* *INDENT-ON* */ + +static pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_HTML, + PCMK__SUPPORTED_FORMAT_NONE, + PCMK__SUPPORTED_FORMAT_TEXT, + PCMK__SUPPORTED_FORMAT_XML, + { NULL, NULL, NULL } +}; + +static const int st_opts = st_opt_sync_call | st_opt_allow_suicide; + +static char *name = NULL; + +gboolean +add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg); + const char *env = getenv(key); + gboolean retval = TRUE; + + if (env == NULL) { + g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg); + retval = FALSE; + } else { + crm_info("Got: '%s'='%s'", optarg, env); + options.params = stonith_key_value_add(options.params, optarg, env); + } + + free(key); + return retval; +} + +gboolean +add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.devices = stonith_key_value_add(options.devices, NULL, optarg); + return TRUE; +} + +gboolean +add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + options.tolerance = crm_get_msec(optarg) / 1000; + return TRUE; +} + +gboolean +add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + char *name = NULL; + char *value = NULL; + int rc = 0; + gboolean retval = TRUE; + + crm_info("Scanning: -o %s", optarg); + + rc = pcmk__scan_nvpair(optarg, &name, &value); + + if (rc != 2) { + rc = pcmk_legacy2rc(rc); + g_set_error(error, PCMK__RC_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_rc_str(rc)); + retval = FALSE; + } else { + crm_info("Got: '%s'='%s'", name, value); + options.params = stonith_key_value_add(options.params, name, value); + } + + free(name); + free(value); + return retval; +} + +gboolean +set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + free(name); + name = crm_strdup_printf("%s.%s", crm_system_name, optarg); + return TRUE; +} + +static GOptionContext * +build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { + GOptionContext *context = NULL; + + GOptionEntry extra_prog_entries[] = { + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet), + "Be less descriptive in output.", + NULL }, + + { NULL } + }; + + context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL); + + /* Add the -q option, which cannot be part of the globally supported options + * because some tools use that flag for something else. + */ + pcmk__add_main_args(context, extra_prog_entries); + + pcmk__add_arg_group(context, "definition", "Device Definition Commands:", + "Show device definition help", defn_entries); + pcmk__add_arg_group(context, "queries", "Queries:", + "Show query help", query_entries); + pcmk__add_arg_group(context, "fence", "Fencing Commands:", + "Show fence help", fence_entries); + pcmk__add_arg_group(context, "additional", "Additional Options:", + "Show additional options", addl_entries); + return context; +} + +// \return Standard Pacemaker return code +static int +request_fencing(stonith_t *st, const char *target, const char *command, + GError **error) +{ + char *reason = NULL; + int rc = pcmk__request_fencing(st, target, command, name, + options.timeout * 1000, + options.tolerance * 1000, + options.delay, &reason); + + if (rc != pcmk_rc_ok) { + const char *rc_str = pcmk_rc_str(rc); + const char *what = (strcmp(command, "on") == 0)? "unfence" : "fence"; + + // If reason is identical to return code string, don't display it twice + if (pcmk__str_eq(rc_str, reason, pcmk__str_none)) { + free(reason); + reason = NULL; + } + + g_set_error(error, PCMK__RC_ERROR, rc, + "Couldn't %s %s: %s%s%s%s", + what, target, rc_str, + ((reason == NULL)? "" : " ("), + ((reason == NULL)? "" : reason), + ((reason == NULL)? "" : ")")); + } + free(reason); + return rc; +} + +int +main(int argc, char **argv) +{ + int rc = 0; + crm_exit_t exit_code = CRM_EX_OK; + bool no_connect = false; + bool required_agent = false; + + char *target = NULL; + const char *device = NULL; + stonith_t *st = NULL; + + GError *error = NULL; + + pcmk__output_t *out = NULL; + + GOptionGroup *output_group = NULL; + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + gchar **processed_args = pcmk__cmdline_preproc(argv, "adehilorstvyBCDFHQRTU"); + GOptionContext *context = build_arg_context(args, &output_group); + + pcmk__register_formats(output_group, formats); + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; + } + + pcmk__cli_init_logging("stonith_admin", args->verbosity); + + if (name == NULL) { + name = strdup(crm_system_name); + } + + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); + if (rc != pcmk_rc_ok) { + exit_code = CRM_EX_ERROR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", + args->output_ty, pcmk_rc_str(rc)); + goto done; + } + + stonith__register_messages(out); + + if (args->version) { + out->version(out, false); + goto done; + } + + if (options.validate_cfg) { + required_agent = true; + no_connect = true; + action = 'K'; + } + + if (options.installed) { + no_connect = true; + action = 'I'; + } + + if (options.registered) { + action = 'L'; + } + + if (options.register_dev != NULL) { + required_agent = true; + action = 'R'; + device = options.register_dev; + } + + if (options.query != NULL) { + action = 'Q'; + device = options.query; + } + + if (options.unregister_dev != NULL) { + action = 'D'; + device = options.unregister_dev; + } + + if (options.targets != NULL) { + action = 's'; + device = options.targets; + } + + if (options.terminate != NULL) { + action = 'L'; + target = options.terminate; + } + + if (options.metadata) { + no_connect = true; + required_agent = true; + action = 'M'; + } + + if (options.reboot_host != NULL) { + no_connect = true; + action = 'B'; + target = options.reboot_host; + crm_log_args(argc, argv); + } + + if (options.fence_host != NULL) { + no_connect = true; + action = 'F'; + target = options.fence_host; + crm_log_args(argc, argv); + } + + if (options.unfence_host != NULL) { + no_connect = true; + action = 'U'; + target = options.unfence_host; + crm_log_args(argc, argv); + } + + if (options.confirm_host != NULL) { + action = 'C'; + target = options.confirm_host; + crm_log_args(argc, argv); + } + + if (options.last_fenced != NULL) { + action = 'h'; + target = options.last_fenced; + } + + if (options.history != NULL) { + action = 'H'; + target = options.history; + } + + if (options.register_level != NULL) { + action = 'r'; + target = options.register_level; + } + + if (options.unregister_level != NULL) { + action = 'd'; + target = options.unregister_level; + } + + if (action == 0) { + char *help = g_option_context_get_help(context, TRUE, NULL); + + out->err(out, "%s", help); + g_free(help); + exit_code = CRM_EX_USAGE; + goto done; + } + + if (required_agent && options.agent == NULL) { + char *help = g_option_context_get_help(context, TRUE, NULL); + + out->err(out, "Please specify an agent to query using -a,--agent [value]"); + out->err(out, "%s", help); + g_free(help); + exit_code = CRM_EX_USAGE; + goto done; + } + + out->quiet = args->quiet; + + st = stonith_api_new(); + if (st == NULL) { + rc = -ENOMEM; + } else if (!no_connect) { + rc = st->cmds->connect(st, name, NULL); + } + if (rc < 0) { + out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc)); + exit_code = CRM_EX_DISCONNECT; + goto done; + } + + switch (action) { + case 'I': + rc = pcmk__fence_installed(out, st, options.timeout*1000); + if (rc != pcmk_rc_ok) { + out->err(out, "Failed to list installed devices: %s", pcmk_strerror(rc)); + } + + break; + + case 'L': + rc = pcmk__fence_registered(out, st, target, options.timeout*1000); + if (rc != pcmk_rc_ok) { + out->err(out, "Failed to list registered devices: %s", pcmk_strerror(rc)); + } + + break; + + case 'Q': + rc = st->cmds->monitor(st, st_opts, device, options.timeout); + if (rc != pcmk_rc_ok) { + rc = st->cmds->list(st, st_opts, device, NULL, options.timeout); + } + rc = pcmk_legacy2rc(rc); + break; + + case 's': + rc = pcmk__fence_list_targets(out, st, device, options.timeout*1000); + if (rc != pcmk_rc_ok) { + out->err(out, "Couldn't list targets: %s", pcmk_strerror(rc)); + } + + break; + + case 'R': + rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent, + options.params); + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + out->err(out, "Can't register device %s using agent %s: %s", + device, options.agent, pcmk_rc_str(rc)); + } + break; + + case 'D': + rc = st->cmds->remove_device(st, st_opts, device); + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + out->err(out, "Can't unregister device %s: %s", + device, pcmk_rc_str(rc)); + } + break; + + case 'd': + rc = pcmk__fence_unregister_level(st, target, options.fence_level); + if (rc != pcmk_rc_ok) { + out->err(out, "Can't unregister topology level %d for %s: %s", + options.fence_level, target, pcmk_rc_str(rc)); + } + break; + + case 'r': + rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices); + if (rc != pcmk_rc_ok) { + out->err(out, "Can't register topology level %d for %s: %s", + options.fence_level, target, pcmk_rc_str(rc)); + } + break; + + case 'M': + rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000); + if (rc != pcmk_rc_ok) { + out->err(out, "Can't get fence agent meta-data: %s", + pcmk_rc_str(rc)); + } + + break; + + case 'C': + rc = st->cmds->confirm(st, st_opts, target); + rc = pcmk_legacy2rc(rc); + break; + + case 'B': + rc = request_fencing(st, target, "reboot", &error); + break; + + case 'F': + rc = request_fencing(st, target, "off", &error); + break; + + case 'U': + rc = request_fencing(st, target, "on", &error); + break; + + case 'h': + rc = pcmk__fence_last(out, target, options.as_nodeid); + break; + + case 'H': + rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->verbosity, + options.broadcast, options.cleanup); + break; + + case 'K': + device = options.devices ? options.devices->key : NULL; + rc = pcmk__fence_validate(out, st, options.agent, device, options.params, + options.timeout*1000); + break; + } + + crm_info("Command returned: %s (%d)", pcmk_rc_str(rc), rc); + exit_code = pcmk_rc2exitc(rc); + + done: + g_strfreev(processed_args); + pcmk__free_arg_context(context); + + pcmk__output_and_clear_error(&error, out); + + if (out != NULL) { + out->finish(out, exit_code, true, NULL); + pcmk__output_free(out); + } + pcmk__unregister_formats(); + free(name); + stonith_key_value_freeall(options.params, 1, 1); + + if (st != NULL) { + st->cmds->disconnect(st); + stonith_api_delete(st); + } + + return exit_code; +} |