| /* |
| * Copyright 2009-2020 the Pacemaker project contributors |
| * |
| * 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/wait.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <sys/utsname.h> |
| |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <ctype.h> |
| |
| #include <crm/crm.h> |
| #include <crm/msg_xml.h> |
| #include <crm/common/ipc.h> |
| #include <crm/common/ipcs.h> |
| #include <crm/cluster/internal.h> |
| #include <crm/common/mainloop.h> |
| |
| #include <crm/stonith-ng.h> |
| #include <crm/fencing/internal.h> |
| #include <crm/common/xml.h> |
| |
| #if SUPPORT_CIBSECRETS |
| # include <crm/common/cib_secrets.h> |
| #endif |
| |
| #include <internal.h> |
| |
| GHashTable *device_list = NULL; |
| GHashTable *topology = NULL; |
| GList *cmd_list = NULL; |
| |
| struct device_search_s { |
| /* target of fence action */ |
| char *host; |
| /* requested fence action */ |
| char *action; |
| /* timeout to use if a device is queried dynamically for possible targets */ |
| int per_device_timeout; |
| /* number of registered fencing devices at time of request */ |
| int replies_needed; |
| /* number of device replies received so far */ |
| int replies_received; |
| /* whether the target is eligible to perform requested action (or off) */ |
| bool allow_suicide; |
| |
| /* private data to pass to search callback function */ |
| void *user_data; |
| /* function to call when all replies have been received */ |
| void (*callback) (GList * devices, void *user_data); |
| /* devices capable of performing requested action (or off if remapping) */ |
| GListPtr capable; |
| }; |
| |
| static gboolean stonith_device_dispatch(gpointer user_data); |
| static void st_child_done(GPid pid, int rc, const char *output, gpointer user_data); |
| static void stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer, |
| const char *client_id); |
| |
| static void search_devices_record_result(struct device_search_s *search, const char *device, |
| gboolean can_fence); |
| |
| typedef struct async_command_s { |
| |
| int id; |
| int pid; |
| int fd_stdout; |
| int options; |
| int default_timeout; /* seconds */ |
| int timeout; /* seconds */ |
| |
| int start_delay; /* seconds */ |
| int delay_id; |
| |
| char *op; |
| char *origin; |
| char *client; |
| char *client_name; |
| char *remote_op_id; |
| |
| char *victim; |
| uint32_t victim_nodeid; |
| char *action; |
| char *device; |
| char *mode; |
| |
| GListPtr device_list; |
| GListPtr device_next; |
| |
| void *internal_user_data; |
| void (*done_cb) (GPid pid, int rc, const char *output, gpointer user_data); |
| guint timer_sigterm; |
| guint timer_sigkill; |
| /*! If the operation timed out, this is the last signal |
| * we sent to the process to get it to terminate */ |
| int last_timeout_signo; |
| |
| stonith_device_t *active_on; |
| stonith_device_t *activating_on; |
| } async_command_t; |
| |
| static xmlNode *stonith_construct_async_reply(async_command_t * cmd, const char *output, |
| xmlNode * data, int rc); |
| |
| static gboolean |
| is_action_required(const char *action, stonith_device_t *device) |
| { |
| return device && device->automatic_unfencing && safe_str_eq(action, "on"); |
| } |
| |
| static int |
| get_action_delay_max(stonith_device_t * device, const char * action) |
| { |
| const char *value = NULL; |
| int delay_max = 0; |
| |
| if (safe_str_neq(action, "off") && safe_str_neq(action, "reboot")) { |
| return 0; |
| } |
| |
| value = g_hash_table_lookup(device->params, STONITH_ATTR_DELAY_MAX); |
| if (value) { |
| delay_max = crm_get_msec(value) / 1000; |
| } |
| |
| return delay_max; |
| } |
| |
| static int |
| get_action_delay_base(stonith_device_t * device, const char * action) |
| { |
| const char *value = NULL; |
| int delay_base = 0; |
| |
| if (safe_str_neq(action, "off") && safe_str_neq(action, "reboot")) { |
| return 0; |
| } |
| |
| value = g_hash_table_lookup(device->params, STONITH_ATTR_DELAY_BASE); |
| if (value) { |
| delay_base = crm_get_msec(value) / 1000; |
| } |
| |
| return delay_base; |
| } |
| |
| /*! |
| * \internal |
| * \brief Override STONITH timeout with pcmk_*_timeout if available |
| * |
| * \param[in] device STONITH device to use |
| * \param[in] action STONITH action name |
| * \param[in] default_timeout Timeout to use if device does not have |
| * a pcmk_*_timeout parameter for action |
| * |
| * \return Value of pcmk_(action)_timeout if available, otherwise default_timeout |
| * \note For consistency, it would be nice if reboot/off/on timeouts could be |
| * set the same way as start/stop/monitor timeouts, i.e. with an |
| * <operation> entry in the fencing resource configuration. However that |
| * is insufficient because fencing devices may be registered directly via |
| * the STONITH register_device() API instead of going through the CIB |
| * (e.g. stonith_admin uses it for its -R option, and the LRMD uses it to |
| * ensure a device is registered when a command is issued). As device |
| * properties, pcmk_*_timeout parameters can be grabbed by stonithd when |
| * the device is registered, whether by CIB change or API call. |
| */ |
| static int |
| get_action_timeout(stonith_device_t * device, const char *action, int default_timeout) |
| { |
| if (action && device && device->params) { |
| char buffer[64] = { 0, }; |
| const char *value = NULL; |
| |
| /* If "reboot" was requested but the device does not support it, |
| * we will remap to "off", so check timeout for "off" instead |
| */ |
| if (safe_str_eq(action, "reboot") |
| && is_not_set(device->flags, st_device_supports_reboot)) { |
| crm_trace("%s doesn't support reboot, using timeout for off instead", |
| device->id); |
| action = "off"; |
| } |
| |
| /* If the device config specified an action-specific timeout, use it */ |
| snprintf(buffer, sizeof(buffer), "pcmk_%s_timeout", action); |
| value = g_hash_table_lookup(device->params, buffer); |
| if (value) { |
| return atoi(value); |
| } |
| } |
| return default_timeout; |
| } |
| |
| static void |
| free_async_command(async_command_t * cmd) |
| { |
| if (!cmd) { |
| return; |
| } |
| |
| if (cmd->delay_id) { |
| g_source_remove(cmd->delay_id); |
| } |
| |
| cmd_list = g_list_remove(cmd_list, cmd); |
| |
| g_list_free_full(cmd->device_list, free); |
| free(cmd->device); |
| free(cmd->action); |
| free(cmd->victim); |
| free(cmd->remote_op_id); |
| free(cmd->client); |
| free(cmd->client_name); |
| free(cmd->origin); |
| free(cmd->mode); |
| free(cmd->op); |
| free(cmd); |
| } |
| |
| static async_command_t * |
| create_async_command(xmlNode * msg) |
| { |
| async_command_t *cmd = NULL; |
| xmlNode *op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR); |
| const char *action = crm_element_value(op, F_STONITH_ACTION); |
| |
| CRM_CHECK(action != NULL, crm_log_xml_warn(msg, "NoAction"); return NULL); |
| |
| crm_log_xml_trace(msg, "Command"); |
| cmd = calloc(1, sizeof(async_command_t)); |
| crm_element_value_int(msg, F_STONITH_CALLID, &(cmd->id)); |
| crm_element_value_int(msg, F_STONITH_CALLOPTS, &(cmd->options)); |
| crm_element_value_int(msg, F_STONITH_TIMEOUT, &(cmd->default_timeout)); |
| cmd->timeout = cmd->default_timeout; |
| // Value -1 means disable any static/random fencing delays |
| crm_element_value_int(msg, F_STONITH_DELAY, &(cmd->start_delay)); |
| |
| cmd->origin = crm_element_value_copy(msg, F_ORIG); |
| cmd->remote_op_id = crm_element_value_copy(msg, F_STONITH_REMOTE_OP_ID); |
| cmd->client = crm_element_value_copy(msg, F_STONITH_CLIENTID); |
| cmd->client_name = crm_element_value_copy(msg, F_STONITH_CLIENTNAME); |
| cmd->op = crm_element_value_copy(msg, F_STONITH_OPERATION); |
| cmd->action = strdup(action); |
| cmd->victim = crm_element_value_copy(op, F_STONITH_TARGET); |
| cmd->mode = crm_element_value_copy(op, F_STONITH_MODE); |
| cmd->device = crm_element_value_copy(op, F_STONITH_DEVICE); |
| |
| CRM_CHECK(cmd->op != NULL, crm_log_xml_warn(msg, "NoOp"); free_async_command(cmd); return NULL); |
| CRM_CHECK(cmd->client != NULL, crm_log_xml_warn(msg, "NoClient")); |
| |
| cmd->done_cb = st_child_done; |
| cmd_list = g_list_append(cmd_list, cmd); |
| return cmd; |
| } |
| |
| static int |
| get_action_limit(stonith_device_t * device) |
| { |
| const char *value = NULL; |
| int action_limit = 1; |
| |
| value = g_hash_table_lookup(device->params, STONITH_ATTR_ACTION_LIMIT); |
| if (value) { |
| action_limit = crm_parse_int(value, "1"); |
| if (action_limit == 0) { |
| /* pcmk_action_limit should not be 0. Enforce it to be 1. */ |
| action_limit = 1; |
| } |
| } |
| |
| return action_limit; |
| } |
| |
| static int |
| get_active_cmds(stonith_device_t * device) |
| { |
| int counter = 0; |
| GListPtr gIter = NULL; |
| GListPtr gIterNext = NULL; |
| |
| CRM_CHECK(device != NULL, return 0); |
| |
| for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) { |
| async_command_t *cmd = gIter->data; |
| |
| gIterNext = gIter->next; |
| |
| if (cmd->active_on == device) { |
| counter++; |
| } |
| } |
| |
| return counter; |
| } |
| |
| static void |
| fork_cb(GPid pid, gpointer user_data) |
| { |
| async_command_t *cmd = (async_command_t *) user_data; |
| stonith_device_t * device = |
| /* in case of a retry we've done the move from |
| * activating_on to active_on already |
| */ |
| cmd->activating_on?cmd->activating_on:cmd->active_on; |
| |
| CRM_ASSERT(device); |
| crm_debug("Operation '%s'%s%s on %s now running with pid=%d, timeout=%ds", |
| cmd->action, cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "", |
| device->id, pid, cmd->timeout); |
| cmd->active_on = device; |
| cmd->activating_on = NULL; |
| } |
| |
| static gboolean |
| stonith_device_execute(stonith_device_t * device) |
| { |
| int exec_rc = 0; |
| const char *action_str = NULL; |
| const char *host_arg = NULL; |
| async_command_t *cmd = NULL; |
| stonith_action_t *action = NULL; |
| int active_cmds = 0; |
| int action_limit = 0; |
| GListPtr gIter = NULL; |
| GListPtr gIterNext = NULL; |
| |
| CRM_CHECK(device != NULL, return FALSE); |
| |
| active_cmds = get_active_cmds(device); |
| action_limit = get_action_limit(device); |
| if (action_limit > -1 && active_cmds >= action_limit) { |
| crm_trace("%s is over its action limit of %d (%u active action%s)", |
| device->id, action_limit, active_cmds, active_cmds > 1 ? "s" : ""); |
| return TRUE; |
| } |
| |
| for (gIter = device->pending_ops; gIter != NULL; gIter = gIterNext) { |
| async_command_t *pending_op = gIter->data; |
| |
| gIterNext = gIter->next; |
| |
| if (pending_op && pending_op->delay_id) { |
| crm_trace |
| ("Operation '%s'%s%s on %s was asked to run too early, waiting for start_delay timeout of %ds", |
| pending_op->action, pending_op->victim ? " targeting " : "", |
| pending_op->victim ? pending_op->victim : "", |
| device->id, pending_op->start_delay); |
| continue; |
| } |
| |
| device->pending_ops = g_list_remove_link(device->pending_ops, gIter); |
| g_list_free_1(gIter); |
| |
| cmd = pending_op; |
| break; |
| } |
| |
| if (cmd == NULL) { |
| crm_trace("Nothing further to do for %s for now", device->id); |
| return TRUE; |
| } |
| |
| if(safe_str_eq(device->agent, STONITH_WATCHDOG_AGENT)) { |
| if(safe_str_eq(cmd->action, "reboot")) { |
| pcmk_panic(__FUNCTION__); |
| goto done; |
| |
| } else if(safe_str_eq(cmd->action, "off")) { |
| pcmk_panic(__FUNCTION__); |
| goto done; |
| |
| } else { |
| crm_info("Faking success for %s watchdog operation", cmd->action); |
| cmd->done_cb(0, 0, NULL, cmd); |
| goto done; |
| } |
| } |
| |
| #if SUPPORT_CIBSECRETS |
| if (replace_secret_params(device->id, device->params) < 0) { |
| /* replacing secrets failed! */ |
| if (safe_str_eq(cmd->action,"stop")) { |
| /* don't fail on stop! */ |
| crm_info("proceeding with the stop operation for %s", device->id); |
| |
| } else { |
| crm_err("failed to get secrets for %s, " |
| "considering resource not configured", device->id); |
| exec_rc = PCMK_OCF_NOT_CONFIGURED; |
| cmd->done_cb(0, exec_rc, NULL, cmd); |
| goto done; |
| } |
| } |
| #endif |
| |
| action_str = cmd->action; |
| if (safe_str_eq(cmd->action, "reboot") && is_not_set(device->flags, st_device_supports_reboot)) { |
| crm_warn("Agent '%s' does not advertise support for 'reboot', performing 'off' action instead", device->agent); |
| action_str = "off"; |
| } |
| |
| if (is_set(device->flags, st_device_supports_parameter_port)) { |
| host_arg = "port"; |
| |
| } else if (is_set(device->flags, st_device_supports_parameter_plug)) { |
| host_arg = "plug"; |
| } |
| |
| action = stonith_action_create(device->agent, |
| action_str, |
| cmd->victim, |
| cmd->victim_nodeid, |
| cmd->timeout, device->params, |
| device->aliases, host_arg); |
| |
| /* for async exec, exec_rc is negative for early error exit |
| otherwise handling of success/errors is done via callbacks */ |
| cmd->activating_on = device; |
| exec_rc = stonith_action_execute_async(action, (void *)cmd, |
| cmd->done_cb, fork_cb); |
| |
| if (exec_rc < 0) { |
| crm_warn("Operation '%s'%s%s on %s failed: %s (%d)", |
| cmd->action, cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "", |
| device->id, pcmk_strerror(exec_rc), exec_rc); |
| cmd->activating_on = NULL; |
| cmd->done_cb(0, exec_rc, NULL, cmd); |
| } |
| |
| done: |
| /* Device might get triggered to work by multiple fencing commands |
| * simultaneously. Trigger the device again to make sure any |
| * remaining concurrent commands get executed. */ |
| if (device->pending_ops) { |
| mainloop_set_trigger(device->work); |
| } |
| return TRUE; |
| } |
| |
| static gboolean |
| stonith_device_dispatch(gpointer user_data) |
| { |
| return stonith_device_execute(user_data); |
| } |
| |
| static gboolean |
| start_delay_helper(gpointer data) |
| { |
| async_command_t *cmd = data; |
| stonith_device_t *device = NULL; |
| |
| cmd->delay_id = 0; |
| device = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL; |
| |
| if (device) { |
| mainloop_set_trigger(device->work); |
| } |
| |
| return FALSE; |
| } |
| |
| static void |
| schedule_stonith_command(async_command_t * cmd, stonith_device_t * device) |
| { |
| int delay_max = 0; |
| int delay_base = 0; |
| int requested_delay = cmd->start_delay; |
| |
| CRM_CHECK(cmd != NULL, return); |
| CRM_CHECK(device != NULL, return); |
| |
| if (cmd->device) { |
| free(cmd->device); |
| } |
| |
| if (device->include_nodeid && cmd->victim) { |
| crm_node_t *node = crm_get_peer(0, cmd->victim); |
| |
| cmd->victim_nodeid = node->id; |
| } |
| |
| cmd->device = strdup(device->id); |
| cmd->timeout = get_action_timeout(device, cmd->action, cmd->default_timeout); |
| |
| if (cmd->remote_op_id) { |
| crm_debug("Scheduling '%s' action%s%s on %s for remote peer %s with op id (%s) (timeout=%ds)", |
| cmd->action, |
| cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "", |
| device->id, cmd->origin, cmd->remote_op_id, cmd->timeout); |
| } else { |
| crm_debug("Scheduling '%s' action%s%s on %s for %s (timeout=%ds)", |
| cmd->action, |
| cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "", |
| device->id, cmd->client, cmd->timeout); |
| } |
| |
| device->pending_ops = g_list_append(device->pending_ops, cmd); |
| mainloop_set_trigger(device->work); |
| |
| // Value -1 means disable any static/random fencing delays |
| if (requested_delay < 0) { |
| return; |
| } |
| |
| delay_max = get_action_delay_max(device, cmd->action); |
| delay_base = get_action_delay_base(device, cmd->action); |
| if (delay_max == 0) { |
| delay_max = delay_base; |
| } |
| if (delay_max < delay_base) { |
| crm_warn("Base-delay (%ds) is larger than max-delay (%ds) " |
| "for %s on %s - limiting to max-delay", |
| delay_base, delay_max, cmd->action, device->id); |
| delay_base = delay_max; |
| } |
| if (delay_max > 0) { |
| // coverity[dont_call] We're not using rand() for security |
| cmd->start_delay += |
| ((delay_max != delay_base)?(rand() % (delay_max - delay_base)):0) |
| + delay_base; |
| } |
| |
| if (cmd->start_delay > 0) { |
| crm_notice("Delaying '%s' action%s%s on %s for %ds (timeout=%ds, " |
| "requested_delay=%ds, base=%ds, max=%ds)", |
| cmd->action, |
| cmd->victim ? " targeting " : "", cmd->victim ? cmd->victim : "", |
| device->id, cmd->start_delay, cmd->timeout, |
| requested_delay, delay_base, delay_max); |
| cmd->delay_id = |
| g_timeout_add_seconds(cmd->start_delay, start_delay_helper, cmd); |
| } |
| } |
| |
| void |
| free_device(gpointer data) |
| { |
| GListPtr gIter = NULL; |
| stonith_device_t *device = data; |
| |
| g_hash_table_destroy(device->params); |
| g_hash_table_destroy(device->aliases); |
| |
| for (gIter = device->pending_ops; gIter != NULL; gIter = gIter->next) { |
| async_command_t *cmd = gIter->data; |
| |
| crm_warn("Removal of device '%s' purged operation '%s'", device->id, cmd->action); |
| cmd->done_cb(0, -ENODEV, NULL, cmd); |
| } |
| g_list_free(device->pending_ops); |
| |
| g_list_free_full(device->targets, free); |
| |
| mainloop_destroy_trigger(device->work); |
| |
| free_xml(device->agent_metadata); |
| free(device->namespace); |
| free(device->on_target_actions); |
| free(device->agent); |
| free(device->id); |
| free(device); |
| } |
| |
| static GHashTable * |
| build_port_aliases(const char *hostmap, GListPtr * targets) |
| { |
| char *name = NULL; |
| int last = 0, lpc = 0, max = 0, added = 0; |
| GHashTable *aliases = crm_strcase_table_new(); |
| |
| if (hostmap == NULL) { |
| return aliases; |
| } |
| |
| max = strlen(hostmap); |
| for (; lpc <= max; lpc++) { |
| switch (hostmap[lpc]) { |
| /* Assignment chars */ |
| case '=': |
| case ':': |
| if (lpc > last) { |
| free(name); |
| name = calloc(1, 1 + lpc - last); |
| memcpy(name, hostmap + last, lpc - last); |
| } |
| last = lpc + 1; |
| break; |
| |
| /* Delimeter chars */ |
| /* case ',': Potentially used to specify multiple ports */ |
| case 0: |
| case ';': |
| case ' ': |
| case '\t': |
| if (name) { |
| char *value = NULL; |
| |
| value = calloc(1, 1 + lpc - last); |
| memcpy(value, hostmap + last, lpc - last); |
| |
| crm_debug("Adding alias '%s'='%s'", name, value); |
| g_hash_table_replace(aliases, name, value); |
| if (targets) { |
| *targets = g_list_append(*targets, strdup(value)); |
| } |
| value = NULL; |
| name = NULL; |
| added++; |
| |
| } else if (lpc > last) { |
| crm_debug("Parse error at offset %d near '%s'", lpc - last, hostmap + last); |
| } |
| |
| last = lpc + 1; |
| break; |
| } |
| |
| if (hostmap[lpc] == 0) { |
| break; |
| } |
| } |
| |
| if (added == 0) { |
| crm_info("No host mappings detected in '%s'", hostmap); |
| } |
| |
| free(name); |
| return aliases; |
| } |
| |
| static void |
| parse_host_line(const char *line, int max, GListPtr * output) |
| { |
| int lpc = 0; |
| int last = 0; |
| |
| if (max <= 0) { |
| return; |
| } |
| |
| /* Check for any complaints about additional parameters that the device doesn't understand */ |
| if (strstr(line, "invalid") || strstr(line, "variable")) { |
| crm_debug("Skipping: %s", line); |
| return; |
| } |
| |
| crm_trace("Processing %d bytes: [%s]", max, line); |
| /* Skip initial whitespace */ |
| for (lpc = 0; lpc <= max && isspace(line[lpc]); lpc++) { |
| last = lpc + 1; |
| } |
| |
| /* Now the actual content */ |
| for (lpc = 0; lpc <= max; lpc++) { |
| gboolean a_space = isspace(line[lpc]); |
| |
| if (a_space && lpc < max && isspace(line[lpc + 1])) { |
| /* fast-forward to the end of the spaces */ |
| |
| } else if (a_space || line[lpc] == ',' || line[lpc] == ';' || line[lpc] == 0) { |
| int rc = 1; |
| char *entry = NULL; |
| |
| if (lpc != last) { |
| entry = calloc(1, 1 + lpc - last); |
| rc = sscanf(line + last, "%[a-zA-Z0-9_-.]", entry); |
| } |
| |
| if (entry == NULL) { |
| /* Skip */ |
| } else if (rc != 1) { |
| crm_warn("Could not parse (%d %d): %s", last, lpc, line + last); |
| } else if (safe_str_neq(entry, "on") && safe_str_neq(entry, "off")) { |
| crm_trace("Adding '%s'", entry); |
| *output = g_list_append(*output, entry); |
| entry = NULL; |
| } |
| |
| free(entry); |
| last = lpc + 1; |
| } |
| } |
| } |
| |
| static GListPtr |
| parse_host_list(const char *hosts) |
| { |
| int lpc = 0; |
| int max = 0; |
| int last = 0; |
| GListPtr output = NULL; |
| |
| if (hosts == NULL) { |
| return output; |
| } |
| |
| max = strlen(hosts); |
| for (lpc = 0; lpc <= max; lpc++) { |
| if (hosts[lpc] == '\n' || hosts[lpc] == 0) { |
| char *line = NULL; |
| int len = lpc - last; |
| |
| if(len > 1) { |
| line = malloc(1 + len); |
| } |
| |
| if(line) { |
| snprintf(line, 1 + len, "%s", hosts + last); |
| line[len] = 0; /* Because it might be '\n' */ |
| parse_host_line(line, len, &output); |
| free(line); |
| } |
| |
| last = lpc + 1; |
| } |
| } |
| |
| crm_trace("Parsed %d entries from '%s'", g_list_length(output), hosts); |
| return output; |
| } |
| |
| GHashTable *metadata_cache = NULL; |
| |
| static xmlNode * |
| get_agent_metadata(const char *agent) |
| { |
| xmlNode *xml = NULL; |
| char *buffer = NULL; |
| |
| if(metadata_cache == NULL) { |
| metadata_cache = crm_str_table_new(); |
| } |
| |
| buffer = g_hash_table_lookup(metadata_cache, agent); |
| if(safe_str_eq(agent, STONITH_WATCHDOG_AGENT)) { |
| return NULL; |
| |
| } else if(buffer == NULL) { |
| stonith_t *st = stonith_api_new(); |
| int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, 10); |
| |
| stonith_api_delete(st); |
| if (rc || !buffer) { |
| crm_err("Could not retrieve metadata for fencing agent %s", agent); |
| return NULL; |
| } |
| g_hash_table_replace(metadata_cache, strdup(agent), buffer); |
| } |
| |
| xml = string2xml(buffer); |
| |
| return xml; |
| } |
| |
| static gboolean |
| is_nodeid_required(xmlNode * xml) |
| { |
| xmlXPathObjectPtr xpath = NULL; |
| |
| if (stand_alone) { |
| return FALSE; |
| } |
| |
| if (!xml) { |
| return FALSE; |
| } |
| |
| xpath = xpath_search(xml, "//parameter[@name='nodeid']"); |
| if (numXpathResults(xpath) <= 0) { |
| freeXpathObject(xpath); |
| return FALSE; |
| } |
| |
| freeXpathObject(xpath); |
| return TRUE; |
| } |
| |
| #define MAX_ACTION_LEN 256 |
| |
| static char * |
| add_action(char *actions, const char *action) |
| { |
| int offset = 0; |
| |
| if (actions == NULL) { |
| actions = calloc(1, MAX_ACTION_LEN); |
| } else { |
| offset = strlen(actions); |
| } |
| |
| if (offset > 0) { |
| offset += snprintf(actions+offset, MAX_ACTION_LEN - offset, " "); |
| } |
| offset += snprintf(actions+offset, MAX_ACTION_LEN - offset, "%s", action); |
| |
| return actions; |
| } |
| |
| static void |
| read_action_metadata(stonith_device_t *device) |
| { |
| xmlXPathObjectPtr xpath = NULL; |
| int max = 0; |
| int lpc = 0; |
| |
| if (device->agent_metadata == NULL) { |
| return; |
| } |
| |
| xpath = xpath_search(device->agent_metadata, "//action"); |
| max = numXpathResults(xpath); |
| |
| if (max <= 0) { |
| freeXpathObject(xpath); |
| return; |
| } |
| |
| for (lpc = 0; lpc < max; lpc++) { |
| const char *on_target = NULL; |
| const char *action = NULL; |
| xmlNode *match = getXpathResult(xpath, lpc); |
| |
| CRM_LOG_ASSERT(match != NULL); |
| if(match == NULL) { continue; }; |
| |
| on_target = crm_element_value(match, "on_target"); |
| action = crm_element_value(match, "name"); |
| |
| if(safe_str_eq(action, "list")) { |
| set_bit(device->flags, st_device_supports_list); |
| } else if(safe_str_eq(action, "status")) { |
| set_bit(device->flags, st_device_supports_status); |
| } else if(safe_str_eq(action, "reboot")) { |
| set_bit(device->flags, st_device_supports_reboot); |
| } else if (safe_str_eq(action, "on")) { |
| /* "automatic" means the cluster will unfence node when it joins */ |
| const char *automatic = crm_element_value(match, "automatic"); |
| |
| /* "required" is a deprecated synonym for "automatic" */ |
| const char *required = crm_element_value(match, "required"); |
| |
| if (crm_is_true(automatic) || crm_is_true(required)) { |
| device->automatic_unfencing = TRUE; |
| } |
| } |
| |
| if (action && crm_is_true(on_target)) { |
| device->on_target_actions = add_action(device->on_target_actions, action); |
| } |
| } |
| |
| freeXpathObject(xpath); |
| } |
| |
| /*! |
| * \internal |
| * \brief Set a pcmk_*_action parameter if not already set |
| * |
| * \param[in,out] params Device parameters |
| * \param[in] action Name of action |
| * \param[in] value Value to use if action is not already set |
| */ |
| static void |
| map_action(GHashTable *params, const char *action, const char *value) |
| { |
| char *key = crm_strdup_printf("pcmk_%s_action", action); |
| |
| if (g_hash_table_lookup(params, key)) { |
| crm_warn("Ignoring %s='%s', see %s instead", |
| STONITH_ATTR_ACTION_OP, value, key); |
| free(key); |
| } else { |
| crm_warn("Mapping %s='%s' to %s='%s'", |
| STONITH_ATTR_ACTION_OP, value, key, value); |
| g_hash_table_insert(params, key, strdup(value)); |
| } |
| } |
| |
| /*! |
| * \internal |
| * \brief Create device parameter table from XML |
| * |
| * \param[in] name Device name (used for logging only) |
| * \param[in,out] params Device parameters |
| */ |
| static GHashTable * |
| xml2device_params(const char *name, xmlNode *dev) |
| { |
| GHashTable *params = xml2list(dev); |
| const char *value; |
| |
| /* Action should never be specified in the device configuration, |
| * but we support it for users who are familiar with other software |
| * that worked that way. |
| */ |
| value = g_hash_table_lookup(params, STONITH_ATTR_ACTION_OP); |
| if (value != NULL) { |
| crm_warn("%s has '%s' parameter, which should never be specified in configuration", |
| name, STONITH_ATTR_ACTION_OP); |
| |
| if (*value == '\0') { |
| crm_warn("Ignoring empty '%s' parameter", STONITH_ATTR_ACTION_OP); |
| |
| } else if (strcmp(value, "reboot") == 0) { |
| crm_warn("Ignoring %s='reboot' (see stonith-action cluster property instead)", |
| STONITH_ATTR_ACTION_OP); |
| |
| } else if (strcmp(value, "off") == 0) { |
| map_action(params, "reboot", value); |
| |
| } else { |
| map_action(params, "off", value); |
| map_action(params, "reboot", value); |
| } |
| |
| g_hash_table_remove(params, STONITH_ATTR_ACTION_OP); |
| } |
| |
| return params; |
| } |
| |
| static stonith_device_t * |
| build_device_from_xml(xmlNode * msg) |
| { |
| const char *value = NULL; |
| xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR); |
| stonith_device_t *device = NULL; |
| |
| device = calloc(1, sizeof(stonith_device_t)); |
| device->id = crm_element_value_copy(dev, XML_ATTR_ID); |
| device->agent = crm_element_value_copy(dev, "agent"); |
| device->namespace = crm_element_value_copy(dev, "namespace"); |
| device->params = xml2device_params(device->id, dev); |
| |
| value = g_hash_table_lookup(device->params, STONITH_ATTR_HOSTLIST); |
| if (value) { |
| device->targets = parse_host_list(value); |
| } |
| |
| value = g_hash_table_lookup(device->params, STONITH_ATTR_HOSTMAP); |
| device->aliases = build_port_aliases(value, &(device->targets)); |
| |
| device->agent_metadata = get_agent_metadata(device->agent); |
| if (device->agent_metadata) { |
| read_action_metadata(device); |
| set_bit(device->flags, |
| stonith__device_parameter_flags(device->agent_metadata)); |
| } |
| |
| value = g_hash_table_lookup(device->params, "nodeid"); |
| if (!value) { |
| device->include_nodeid = is_nodeid_required(device->agent_metadata); |
| } |
| |
| value = crm_element_value(dev, "rsc_provides"); |
| if (safe_str_eq(value, "unfencing")) { |
| device->automatic_unfencing = TRUE; |
| } |
| |
| if (is_action_required("on", device)) { |
| crm_info("The fencing device '%s' requires unfencing", device->id); |
| } |
| |
| if (device->on_target_actions) { |
| crm_info("The fencing device '%s' requires actions (%s) to be executed on the target node", |
| device->id, device->on_target_actions); |
| } |
| |
| device->work = mainloop_add_trigger(G_PRIORITY_HIGH, stonith_device_dispatch, device); |
| /* TODO: Hook up priority */ |
| |
| return device; |
| } |
| |
| static const char * |
| target_list_type(stonith_device_t * dev) |
| { |
| const char *check_type = NULL; |
| |
| check_type = g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTCHECK); |
| |
| if (check_type == NULL) { |
| |
| if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTLIST)) { |
| check_type = "static-list"; |
| } else if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTMAP)) { |
| check_type = "static-list"; |
| } else if(is_set(dev->flags, st_device_supports_list)){ |
| check_type = "dynamic-list"; |
| } else if(is_set(dev->flags, st_device_supports_status)){ |
| check_type = "status"; |
| } else { |
| check_type = "none"; |
| } |
| } |
| |
| return check_type; |
| } |
| |
| void |
| schedule_internal_command(const char *origin, |
| stonith_device_t * device, |
| const char *action, |
| const char *victim, |
| int timeout, |
| void *internal_user_data, |
| void (*done_cb) (GPid pid, int rc, const char *output, |
| gpointer user_data)) |
| { |
| async_command_t *cmd = NULL; |
| |
| cmd = calloc(1, sizeof(async_command_t)); |
| |
| cmd->id = -1; |
| cmd->default_timeout = timeout ? timeout : 60; |
| cmd->timeout = cmd->default_timeout; |
| cmd->action = strdup(action); |
| cmd->victim = victim ? strdup(victim) : NULL; |
| cmd->device = strdup(device->id); |
| cmd->origin = strdup(origin); |
| cmd->client = strdup(crm_system_name); |
| cmd->client_name = strdup(crm_system_name); |
| |
| cmd->internal_user_data = internal_user_data; |
| cmd->done_cb = done_cb; /* cmd, not internal_user_data, is passed to 'done_cb' as the userdata */ |
| |
| schedule_stonith_command(cmd, device); |
| } |
| |
| gboolean |
| string_in_list(GListPtr list, const char *item) |
| { |
| int lpc = 0; |
| int max = g_list_length(list); |
| |
| for (lpc = 0; lpc < max; lpc++) { |
| const char *value = g_list_nth_data(list, lpc); |
| |
| if (safe_str_eq(item, value)) { |
| return TRUE; |
| } else { |
| crm_trace("%d: '%s' != '%s'", lpc, item, value); |
| } |
| } |
| return FALSE; |
| } |
| |
| static void |
| status_search_cb(GPid pid, int rc, const char *output, gpointer user_data) |
| { |
| async_command_t *cmd = user_data; |
| struct device_search_s *search = cmd->internal_user_data; |
| stonith_device_t *dev = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL; |
| gboolean can = FALSE; |
| |
| free_async_command(cmd); |
| |
| if (!dev) { |
| search_devices_record_result(search, NULL, FALSE); |
| return; |
| } |
| |
| mainloop_set_trigger(dev->work); |
| |
| if (rc == 1 /* unknown */ ) { |
| crm_trace("Host %s is not known by %s", search->host, dev->id); |
| |
| } else if (rc == 0 /* active */ || rc == 2 /* inactive */ ) { |
| crm_trace("Host %s is known by %s", search->host, dev->id); |
| can = TRUE; |
| |
| } else { |
| crm_notice("Unknown result when testing if %s can fence %s: rc=%d", dev->id, search->host, |
| rc); |
| } |
| search_devices_record_result(search, dev->id, can); |
| } |
| |
| static void |
| dynamic_list_search_cb(GPid pid, int rc, const char *output, gpointer user_data) |
| { |
| async_command_t *cmd = user_data; |
| struct device_search_s *search = cmd->internal_user_data; |
| stonith_device_t *dev = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL; |
| gboolean can_fence = FALSE; |
| |
| free_async_command(cmd); |
| |
| /* Host/alias must be in the list output to be eligible to be fenced |
| * |
| * Will cause problems if down'd nodes aren't listed or (for virtual nodes) |
| * if the guest is still listed despite being moved to another machine |
| */ |
| if (!dev) { |
| search_devices_record_result(search, NULL, FALSE); |
| return; |
| } |
| |
| mainloop_set_trigger(dev->work); |
| |
| /* If we successfully got the targets earlier, don't disable. */ |
| if (rc != 0 && !dev->targets) { |
| crm_notice("Disabling port list queries for %s (%d): %s", dev->id, rc, output); |
| /* Fall back to status */ |
| g_hash_table_replace(dev->params, strdup(STONITH_ATTR_HOSTCHECK), strdup("status")); |
| |
| g_list_free_full(dev->targets, free); |
| dev->targets = NULL; |
| } else if (!rc) { |
| crm_info("Refreshing port list for %s", dev->id); |
| g_list_free_full(dev->targets, free); |
| dev->targets = parse_host_list(output); |
| dev->targets_age = time(NULL); |
| } |
| |
| if (dev->targets) { |
| const char *alias = g_hash_table_lookup(dev->aliases, search->host); |
| |
| if (!alias) { |
| alias = search->host; |
| } |
| if (string_in_list(dev->targets, alias)) { |
| can_fence = TRUE; |
| } |
| } |
| search_devices_record_result(search, dev->id, can_fence); |
| } |
| |
| /*! |
| * \internal |
| * \brief Returns true if any key in first is not in second or second has a different value for key |
| */ |
| static int |
| device_params_diff(GHashTable *first, GHashTable *second) { |
| char *key = NULL; |
| char *value = NULL; |
| GHashTableIter gIter; |
| |
| g_hash_table_iter_init(&gIter, first); |
| while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&value)) { |
| |
| if(strstr(key, "CRM_meta") == key) { |
| continue; |
| } else if(strcmp(key, "crm_feature_set") == 0) { |
| continue; |
| } else { |
| char *other_value = g_hash_table_lookup(second, key); |
| |
| if (!other_value || safe_str_neq(other_value, value)) { |
| crm_trace("Different value for %s: %s != %s", key, other_value, value); |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /*! |
| * \internal |
| * \brief Checks to see if an identical device already exists in the device_list |
| */ |
| static stonith_device_t * |
| device_has_duplicate(stonith_device_t * device) |
| { |
| stonith_device_t *dup = g_hash_table_lookup(device_list, device->id); |
| |
| if (!dup) { |
| crm_trace("No match for %s", device->id); |
| return NULL; |
| |
| } else if (safe_str_neq(dup->agent, device->agent)) { |
| crm_trace("Different agent: %s != %s", dup->agent, device->agent); |
| return NULL; |
| } |
| |
| /* Use calculate_operation_digest() here? */ |
| if (device_params_diff(device->params, dup->params) || |
| device_params_diff(dup->params, device->params)) { |
| return NULL; |
| } |
| |
| crm_trace("Match"); |
| return dup; |
| } |
| |
| int |
| stonith_device_register(xmlNode * msg, const char **desc, gboolean from_cib) |
| { |
| stonith_device_t *dup = NULL; |
| stonith_device_t *device = build_device_from_xml(msg); |
| |
| dup = device_has_duplicate(device); |
| if (dup) { |
| crm_debug("Device '%s' already existed in device list (%d active devices)", device->id, |
| g_hash_table_size(device_list)); |
| free_device(device); |
| device = dup; |
| |
| } else { |
| stonith_device_t *old = g_hash_table_lookup(device_list, device->id); |
| |
| if (from_cib && old && old->api_registered) { |
| /* If the cib is writing over an entry that is shared with a stonith client, |
| * copy any pending ops that currently exist on the old entry to the new one. |
| * Otherwise the pending ops will be reported as failures |
| */ |
| crm_info("Overwriting an existing entry for %s from the cib", device->id); |
| device->pending_ops = old->pending_ops; |
| device->api_registered = TRUE; |
| old->pending_ops = NULL; |
| if (device->pending_ops) { |
| mainloop_set_trigger(device->work); |
| } |
| } |
| g_hash_table_replace(device_list, device->id, device); |
| |
| crm_notice("Added '%s' to the device list (%d active devices)", device->id, |
| g_hash_table_size(device_list)); |
| } |
| if (desc) { |
| *desc = device->id; |
| } |
| |
| if (from_cib) { |
| device->cib_registered = TRUE; |
| } else { |
| device->api_registered = TRUE; |
| } |
| |
| return pcmk_ok; |
| } |
| |
| int |
| stonith_device_remove(const char *id, gboolean from_cib) |
| { |
| stonith_device_t *device = g_hash_table_lookup(device_list, id); |
| |
| if (!device) { |
| crm_info("Device '%s' not found (%d active devices)", id, g_hash_table_size(device_list)); |
| return pcmk_ok; |
| } |
| |
| if (from_cib) { |
| device->cib_registered = FALSE; |
| } else { |
| device->verified = FALSE; |
| device->api_registered = FALSE; |
| } |
| |
| if (!device->cib_registered && !device->api_registered) { |
| g_hash_table_remove(device_list, id); |
| crm_info("Removed '%s' from the device list (%d active devices)", |
| id, g_hash_table_size(device_list)); |
| } else { |
| crm_trace("Not removing '%s' from the device list (%d active devices) " |
| "- still %s%s_registered", id, g_hash_table_size(device_list), |
| device->cib_registered?"cib":"", device->api_registered?"api":""); |
| } |
| return pcmk_ok; |
| } |
| |
| /*! |
| * \internal |
| * \brief Return the number of stonith levels registered for a node |
| * |
| * \param[in] tp Node's topology table entry |
| * |
| * \return Number of non-NULL levels in topology entry |
| * \note This function is used only for log messages. |
| */ |
| static int |
| count_active_levels(stonith_topology_t * tp) |
| { |
| int lpc = 0; |
| int count = 0; |
| |
| for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) { |
| if (tp->levels[lpc] != NULL) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| void |
| free_topology_entry(gpointer data) |
| { |
| stonith_topology_t *tp = data; |
| |
| int lpc = 0; |
| |
| for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) { |
| if (tp->levels[lpc] != NULL) { |
| g_list_free_full(tp->levels[lpc], free); |
| } |
| } |
| free(tp->target); |
| free(tp->target_value); |
| free(tp->target_pattern); |
| free(tp->target_attribute); |
| free(tp); |
| } |
| |
| char *stonith_level_key(xmlNode *level, int mode) |
| { |
| if(mode == -1) { |
| mode = stonith_level_kind(level); |
| } |
| |
| switch(mode) { |
| case 0: |
| return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET); |
| case 1: |
| return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN); |
| case 2: |
| { |
| const char *name = crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE); |
| const char *value = crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE); |
| |
| if(name && value) { |
| return crm_strdup_printf("%s=%s", name, value); |
| } |
| } |
| default: |
| return crm_strdup_printf("Unknown-%d-%s", mode, ID(level)); |
| } |
| } |
| |
| int stonith_level_kind(xmlNode * level) |
| { |
| int mode = 0; |
| const char *target = crm_element_value(level, XML_ATTR_STONITH_TARGET); |
| |
| if(target == NULL) { |
| mode++; |
| target = crm_element_value(level, XML_ATTR_STONITH_TARGET_PATTERN); |
| } |
| |
| if(stand_alone == FALSE && target == NULL) { |
| |
| mode++; |
| |
| if(crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE) == NULL) { |
| mode++; |
| |
| } else if(crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE) == NULL) { |
| mode++; |
| } |
| } |
| |
| return mode; |
| } |
| |
| static stonith_key_value_t * |
| parse_device_list(const char *devices) |
| { |
| int lpc = 0; |
| int max = 0; |
| int last = 0; |
| stonith_key_value_t *output = NULL; |
| |
| if (devices == NULL) { |
| return output; |
| } |
| |
| max = strlen(devices); |
| for (lpc = 0; lpc <= max; lpc++) { |
| if (devices[lpc] == ',' || devices[lpc] == 0) { |
| char *line = NULL; |
| |
| line = calloc(1, 2 + lpc - last); |
| snprintf(line, 1 + lpc - last, "%s", devices + last); |
| output = stonith_key_value_add(output, NULL, line); |
| free(line); |
| |
| last = lpc + 1; |
| } |
| } |
| |
| return output; |
| } |
| |
| /*! |
| * \internal |
| * \brief Register a STONITH level for a target |
| * |
| * Given an XML request specifying the target name, level index, and device IDs |
| * for the level, this will create an entry for the target in the global topology |
| * table if one does not already exist, then append the specified device IDs to |
| * the entry's device list for the specified level. |
| * |
| * \param[in] msg XML request for STONITH level registration |
| * \param[out] desc If not NULL, will be set to string representation ("TARGET[LEVEL]") |
| * |
| * \return pcmk_ok on success, -EINVAL if XML does not specify valid level index |
| */ |
| int |
| stonith_level_register(xmlNode *msg, char **desc) |
| { |
| int id = 0; |
| xmlNode *level; |
| int mode; |
| char *target; |
| |
| stonith_topology_t *tp; |
| stonith_key_value_t *dIter = NULL; |
| stonith_key_value_t *devices = NULL; |
| |
| /* Allow the XML here to point to the level tag directly, or wrapped in |
| * another tag. If directly, don't search by xpath, because it might give |
| * multiple hits (e.g. if the XML is the CIB). |
| */ |
| if (safe_str_eq(TYPE(msg), XML_TAG_FENCING_LEVEL)) { |
| level = msg; |
| } else { |
| level = get_xpath_object("//" XML_TAG_FENCING_LEVEL, msg, LOG_ERR); |
| } |
| CRM_CHECK(level != NULL, return -EINVAL); |
| |
| mode = stonith_level_kind(level); |
| target = stonith_level_key(level, mode); |
| crm_element_value_int(level, XML_ATTR_STONITH_INDEX, &id); |
| |
| if (desc) { |
| *desc = crm_strdup_printf("%s[%d]", target, id); |
| } |
| |
| /* Sanity-check arguments */ |
| if (mode >= 3 || (id <= 0) || (id >= ST_LEVEL_MAX)) { |
| crm_trace("Could not add %s[%d] (%d) to the topology (%d active entries)", target, id, mode, g_hash_table_size(topology)); |
| free(target); |
| crm_log_xml_err(level, "Bad topology"); |
| return -EINVAL; |
| } |
| |
| /* Find or create topology table entry */ |
| tp = g_hash_table_lookup(topology, target); |
| if (tp == NULL) { |
| tp = calloc(1, sizeof(stonith_topology_t)); |
| tp->kind = mode; |
| tp->target = target; |
| tp->target_value = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_VALUE); |
| tp->target_pattern = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN); |
| tp->target_attribute = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE); |
| |
| g_hash_table_replace(topology, tp->target, tp); |
| crm_trace("Added %s (%d) to the topology (%d active entries)", |
| target, mode, g_hash_table_size(topology)); |
| } else { |
| free(target); |
| } |
| |
| if (tp->levels[id] != NULL) { |
| crm_info("Adding to the existing %s[%d] topology entry", |
| tp->target, id); |
| } |
| |
| devices = parse_device_list(crm_element_value(level, XML_ATTR_STONITH_DEVICES)); |
| for (dIter = devices; dIter; dIter = dIter->next) { |
| const char *device = dIter->value; |
| |
| crm_trace("Adding device '%s' for %s[%d]", device, tp->target, id); |
| tp->levels[id] = g_list_append(tp->levels[id], strdup(device)); |
| } |
| stonith_key_value_freeall(devices, 1, 1); |
| |
| crm_info("Target %s has %d active fencing levels", |
| tp->target, count_active_levels(tp)); |
| return pcmk_ok; |
| } |
| |
| int |
| stonith_level_remove(xmlNode *msg, char **desc) |
| { |
| int id = 0; |
| stonith_topology_t *tp; |
| char *target; |
| |
| /* Unlike additions, removal requests should always have one level tag */ |
| xmlNode *level = get_xpath_object("//" XML_TAG_FENCING_LEVEL, msg, LOG_ERR); |
| |
| CRM_CHECK(level != NULL, return -EINVAL); |
| |
| target = stonith_level_key(level, -1); |
| crm_element_value_int(level, XML_ATTR_STONITH_INDEX, &id); |
| if (desc) { |
| *desc = crm_strdup_printf("%s[%d]", target, id); |
| } |
| |
| /* Sanity-check arguments */ |
| if (id >= ST_LEVEL_MAX) { |
| free(target); |
| return -EINVAL; |
| } |
| |
| tp = g_hash_table_lookup(topology, target); |
| if (tp == NULL) { |
| crm_info("Topology for %s not found (%d active entries)", |
| target, g_hash_table_size(topology)); |
| |
| } else if (id == 0 && g_hash_table_remove(topology, target)) { |
| crm_info("Removed all %s related entries from the topology (%d active entries)", |
| target, g_hash_table_size(topology)); |
| |
| } else if (id > 0 && tp->levels[id] != NULL) { |
| g_list_free_full(tp->levels[id], free); |
| tp->levels[id] = NULL; |
| |
| crm_info("Removed level '%d' from topology for %s (%d active levels remaining)", |
| id, target, count_active_levels(tp)); |
| } |
| |
| free(target); |
| return pcmk_ok; |
| } |
| |
| /*! |
| * \internal |
| * \brief Schedule an (asynchronous) action directly on a stonith device |
| * |
| * Handle a STONITH_OP_EXEC API message by scheduling a requested agent action |
| * directly on a specified device. Only list, monitor, and status actions are |
| * expected to use this call, though it should work with any agent command. |
| * |
| * \param[in] msg API message XML with desired action |
| * \param[out] output Unused |
| * |
| * \return -EINPROGRESS on success, -errno otherwise |
| * \note If the action is monitor, the device must be registered via the API |
| * (CIB registration is not sufficient), because monitor should not be |
| * possible unless the device is "started" (API registered). |
| */ |
| static int |
| stonith_device_action(xmlNode * msg, char **output) |
| { |
| xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR); |
| xmlNode *op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR); |
| const char *id = crm_element_value(dev, F_STONITH_DEVICE); |
| const char *action = crm_element_value(op, F_STONITH_ACTION); |
| async_command_t *cmd = NULL; |
| stonith_device_t *device = NULL; |
| |
| if ((id == NULL) || (action == NULL)) { |
| crm_info("Malformed API action request: device %s, action %s", |
| (id? id : "not specified"), |
| (action? action : "not specified")); |
| return -EPROTO; |
| } |
| |
| device = g_hash_table_lookup(device_list, id); |
| if ((device == NULL) |
| || (!device->api_registered && !strcmp(action, "monitor"))) { |
| |
| // Monitors may run only on "started" (API-registered) devices |
| crm_info("Ignoring API %s action request because device %s not found", |
| action, id); |
| return -ENODEV; |
| } |
| |
| cmd = create_async_command(msg); |
| if (cmd == NULL) { |
| return -EPROTO; |
| } |
| |
| schedule_stonith_command(cmd, device); |
| return -EINPROGRESS; |
| } |
| |
| static void |
| search_devices_record_result(struct device_search_s *search, const char *device, gboolean can_fence) |
| { |
| search->replies_received++; |
| |
| if (can_fence && device) { |
| search->capable = g_list_append(search->capable, strdup(device)); |
| } |
| |
| if (search->replies_needed == search->replies_received) { |
| |
| crm_debug("Finished Search. %d devices can perform action (%s) on node %s", |
| g_list_length(search->capable), |
| search->action ? search->action : "<unknown>", |
| search->host ? search->host : "<anyone>"); |
| |
| search->callback(search->capable, search->user_data); |
| free(search->host); |
| free(search->action); |
| free(search); |
| } |
| } |
| |
| /*! |
| * \internal |
| * \brief Check whether the local host is allowed to execute a fencing action |
| * |
| * \param[in] device Fence device to check |
| * \param[in] action Fence action to check |
| * \param[in] target Hostname of fence target |
| * \param[in] allow_suicide Whether self-fencing is allowed for this operation |
| * |
| * \return TRUE if local host is allowed to execute action, FALSE otherwise |
| */ |
| static gboolean |
| localhost_is_eligible(const stonith_device_t *device, const char *action, |
| const char *target, gboolean allow_suicide) |
| { |
| gboolean localhost_is_target = safe_str_eq(target, stonith_our_uname); |
| |
| if (device && action && device->on_target_actions |
| && strstr(device->on_target_actions, action)) { |
| if (!localhost_is_target) { |
| crm_trace("'%s' operation with %s can only be executed for localhost not %s", |
| action, device->id, target); |
| return FALSE; |
| } |
| |
| } else if (localhost_is_target && !allow_suicide) { |
| crm_trace("'%s' operation does not support self-fencing", action); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static void |
| can_fence_host_with_device(stonith_device_t * dev, struct device_search_s *search) |
| { |
| gboolean can = FALSE; |
| const char *check_type = NULL; |
| const char *host = search->host; |
| const char *alias = NULL; |
| |
| CRM_LOG_ASSERT(dev != NULL); |
| |
| if (dev == NULL) { |
| goto search_report_results; |
| } else if (host == NULL) { |
| can = TRUE; |
| goto search_report_results; |
| } |
| |
| /* Short-circuit query if this host is not allowed to perform the action */ |
| if (safe_str_eq(search->action, "reboot")) { |
| /* A "reboot" *might* get remapped to "off" then "on", so short-circuit |
| * only if all three are disallowed. If only one or two are disallowed, |
| * we'll report that with the results. We never allow suicide for |
| * remapped "on" operations because the host is off at that point. |
| */ |
| if (!localhost_is_eligible(dev, "reboot", host, search->allow_suicide) |
| && !localhost_is_eligible(dev, "off", host, search->allow_suicide) |
| && !localhost_is_eligible(dev, "on", host, FALSE)) { |
| goto search_report_results; |
| } |
| } else if (!localhost_is_eligible(dev, search->action, host, |
| search->allow_suicide)) { |
| goto search_report_results; |
| } |
| |
| alias = g_hash_table_lookup(dev->aliases, host); |
| if (alias == NULL) { |
| alias = host; |
| } |
| |
| check_type = target_list_type(dev); |
| |
| if (safe_str_eq(check_type, "none")) { |
| can = TRUE; |
| |
| } else if (safe_str_eq(check_type, "static-list")) { |
| |
| /* Presence in the hostmap is sufficient |
| * Only use if all hosts on which the device can be active can always fence all listed hosts |
| */ |
| |
| if (string_in_list(dev->targets, host)) { |
| can = TRUE; |
| } else if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTMAP) |
| && g_hash_table_lookup(dev->aliases, host)) { |
| can = TRUE; |
| } |
| |
| } else if (safe_str_eq(check_type, "dynamic-list")) { |
| time_t now = time(NULL); |
| |
| if (dev->targets == NULL || dev->targets_age + 60 < now) { |
| crm_trace("Running %s command to see if %s can fence %s (%s)", |
| check_type, dev->id, search->host, search->action); |
| |
| schedule_internal_command(__FUNCTION__, dev, "list", NULL, |
| search->per_device_timeout, search, dynamic_list_search_cb); |
| |
| /* we'll respond to this search request async in the cb */ |
| return; |
| } |
| |
| if (string_in_list(dev->targets, alias)) { |
| can = TRUE; |
| } |
| |
| } else if (safe_str_eq(check_type, "status")) { |
| crm_trace("Running %s command to see if %s can fence %s (%s)", |
| check_type, dev->id, search->host, search->action); |
| schedule_internal_command(__FUNCTION__, dev, "status", search->host, |
| search->per_device_timeout, search, status_search_cb); |
| /* we'll respond to this search request async in the cb */ |
| return; |
| } else { |
| crm_err("Unknown check type: %s", check_type); |
| } |
| |
| if (safe_str_eq(host, alias)) { |
| crm_notice("%s can%s fence (%s) %s: %s", dev->id, can ? "" : " not", search->action, host, check_type); |
| } else { |
| crm_notice("%s can%s fence (%s) %s (aka. '%s'): %s", dev->id, can ? "" : " not", search->action, host, alias, |
| check_type); |
| } |
| |
| search_report_results: |
| search_devices_record_result(search, dev ? dev->id : NULL, can); |
| } |
| |
| static void |
| search_devices(gpointer key, gpointer value, gpointer user_data) |
| { |
| stonith_device_t *dev = value; |
| struct device_search_s *search = user_data; |
| |
| can_fence_host_with_device(dev, search); |
| } |
| |
| #define DEFAULT_QUERY_TIMEOUT 20 |
| static void |
| get_capable_devices(const char *host, const char *action, int timeout, bool suicide, void *user_data, |
| void (*callback) (GList * devices, void *user_data)) |
| { |
| struct device_search_s *search; |
| int per_device_timeout = DEFAULT_QUERY_TIMEOUT; |
| int devices_needing_async_query = 0; |
| char *key = NULL; |
| const char *check_type = NULL; |
| GHashTableIter gIter; |
| stonith_device_t *device = NULL; |
| |
| if (!g_hash_table_size(device_list)) { |
| callback(NULL, user_data); |
| return; |
| } |
| |
| search = calloc(1, sizeof(struct device_search_s)); |
| if (!search) { |
| callback(NULL, user_data); |
| return; |
| } |
| |
| g_hash_table_iter_init(&gIter, device_list); |
| while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&device)) { |
| check_type = target_list_type(device); |
| if (safe_str_eq(check_type, "status") || safe_str_eq(check_type, "dynamic-list")) { |
| devices_needing_async_query++; |
| } |
| } |
| |
| /* If we have devices that require an async event in order to know what |
| * nodes they can fence, we have to give the events a timeout. The total |
| * query timeout is divided among those events. */ |
| if (devices_needing_async_query) { |
| per_device_timeout = timeout / devices_needing_async_query; |
| if (!per_device_timeout) { |
| crm_err("STONITH timeout %ds is too low; using %ds, but consider raising to at least %ds", |
| timeout, DEFAULT_QUERY_TIMEOUT, |
| DEFAULT_QUERY_TIMEOUT * devices_needing_async_query); |
| per_device_timeout = DEFAULT_QUERY_TIMEOUT; |
| } else if (per_device_timeout < DEFAULT_QUERY_TIMEOUT) { |
| crm_notice("STONITH timeout %ds is low for the current configuration;" |
| " consider raising to at least %ds", |
| timeout, DEFAULT_QUERY_TIMEOUT * devices_needing_async_query); |
| } |
| } |
| |
| search->host = host ? strdup(host) : NULL; |
| search->action = action ? strdup(action) : NULL; |
| search->per_device_timeout = per_device_timeout; |
| /* We are guaranteed this many replies. Even if a device gets |
| * unregistered some how during the async search, we will get |
| * the correct number of replies. */ |
| search->replies_needed = g_hash_table_size(device_list); |
| search->allow_suicide = suicide; |
| search->callback = callback; |
| search->user_data = user_data; |
| /* kick off the search */ |
| |
| crm_debug("Searching through %d devices to see what is capable of action (%s) for target %s", |
| search->replies_needed, |
| search->action ? search->action : "<unknown>", |
| search->host ? search->host : "<anyone>"); |
| g_hash_table_foreach(device_list, search_devices, search); |
| } |
| |
| struct st_query_data { |
| xmlNode *reply; |
| char *remote_peer; |
| char *client_id; |
| char *target; |
| char *action; |
| int call_options; |
| }; |
| |
| /*! |
| * \internal |
| * \brief Add action-specific attributes to query reply XML |
| * |
| * \param[in,out] xml XML to add attributes to |
| * \param[in] action Fence action |
| * \param[in] device Fence device |
| */ |
| static void |
| add_action_specific_attributes(xmlNode *xml, const char *action, |
| stonith_device_t *device) |
| { |
| int action_specific_timeout; |
| int delay_max; |
| int delay_base; |
| |
| CRM_CHECK(xml && action && device, return); |
| |
| if (is_action_required(action, device)) { |
| crm_trace("Action '%s' is required on %s", action, device->id); |
| crm_xml_add_int(xml, F_STONITH_DEVICE_REQUIRED, 1); |
| } |
| |
| action_specific_timeout = get_action_timeout(device, action, 0); |
| if (action_specific_timeout) { |
| crm_trace("Action '%s' has timeout %dms on %s", |
| action, action_specific_timeout, device->id); |
| crm_xml_add_int(xml, F_STONITH_ACTION_TIMEOUT, action_specific_timeout); |
| } |
| |
| delay_max = get_action_delay_max(device, action); |
| if (delay_max > 0) { |
| crm_trace("Action '%s' has maximum random delay %dms on %s", |
| action, delay_max, device->id); |
| crm_xml_add_int(xml, F_STONITH_DELAY_MAX, delay_max / 1000); |
| } |
| |
| delay_base = get_action_delay_base(device, action); |
| if (delay_base > 0) { |
| crm_xml_add_int(xml, F_STONITH_DELAY_BASE, delay_base / 1000); |
| } |
| |
| if ((delay_max > 0) && (delay_base == 0)) { |
| crm_trace("Action '%s' has maximum random delay %dms on %s", |
| action, delay_max, device->id); |
| } else if ((delay_max == 0) && (delay_base > 0)) { |
| crm_trace("Action '%s' has a static delay of %dms on %s", |
| action, delay_base, device->id); |
| } else if ((delay_max > 0) && (delay_base > 0)) { |
| crm_trace("Action '%s' has a minimum delay of %dms and a randomly chosen " |
| "maximum delay of %dms on %s", |
| action, delay_base, delay_max, device->id); |
| } |
| } |
| |
| /*! |
| * \internal |
| * \brief Add "disallowed" attribute to query reply XML if appropriate |
| * |
| * \param[in,out] xml XML to add attribute to |
| * \param[in] action Fence action |
| * \param[in] device Fence device |
| * \param[in] target Fence target |
| * \param[in] allow_suicide Whether self-fencing is allowed |
| */ |
| static void |
| add_disallowed(xmlNode *xml, const char *action, stonith_device_t *device, |
| const char *target, gboolean allow_suicide) |
| { |
| if (!localhost_is_eligible(device, action, target, allow_suicide)) { |
| crm_trace("Action '%s' on %s is disallowed for local host", |
| action, device->id); |
| crm_xml_add(xml, F_STONITH_ACTION_DISALLOWED, XML_BOOLEAN_TRUE); |
| } |
| } |
| |
| /*! |
| * \internal |
| * \brief Add child element with action-specific values to query reply XML |
| * |
| * \param[in,out] xml XML to add attribute to |
| * \param[in] action Fence action |
| * \param[in] device Fence device |
| * \param[in] target Fence target |
| * \param[in] allow_suicide Whether self-fencing is allowed |
| */ |
| static void |
| add_action_reply(xmlNode *xml, const char *action, stonith_device_t *device, |
| const char *target, gboolean allow_suicide) |
| { |
| xmlNode *child = create_xml_node(xml, F_STONITH_ACTION); |
| |
| crm_xml_add(child, XML_ATTR_ID, action); |
| add_action_specific_attributes(child, action, device); |
| add_disallowed(child, action, device, target, allow_suicide); |
| } |
| |
| static void |
| stonith_query_capable_device_cb(GList * devices, void *user_data) |
| { |
| struct st_query_data *query = user_data; |
| int available_devices = 0; |
| xmlNode *dev = NULL; |
| xmlNode *list = NULL; |
| GListPtr lpc = NULL; |
| |
| /* Pack the results into XML */ |
| list = create_xml_node(NULL, __FUNCTION__); |
| crm_xml_add(list, F_STONITH_TARGET, query->target); |
| for (lpc = devices; lpc != NULL; lpc = lpc->next) { |
| stonith_device_t *device = g_hash_table_lookup(device_list, lpc->data); |
| const char *action = query->action; |
| |
| if (!device) { |
| /* It is possible the device got unregistered while |
| * determining who can fence the target */ |
| continue; |
| } |
| |
| available_devices++; |
| |
| dev = create_xml_node(list, F_STONITH_DEVICE); |
| crm_xml_add(dev, XML_ATTR_ID, device->id); |
| crm_xml_add(dev, "namespace", device->namespace); |
| crm_xml_add(dev, "agent", device->agent); |
| crm_xml_add_int(dev, F_STONITH_DEVICE_VERIFIED, device->verified); |
| |
| /* If the originating stonithd wants to reboot the node, and we have a |
| * capable device that doesn't support "reboot", remap to "off" instead. |
| */ |
| if (is_not_set(device->flags, st_device_supports_reboot) |
| && safe_str_eq(query->action, "reboot")) { |
| crm_trace("%s doesn't support reboot, using values for off instead", |
| device->id); |
| action = "off"; |
| } |
| |
| /* Add action-specific values if available */ |
| add_action_specific_attributes(dev, action, device); |
| if (safe_str_eq(query->action, "reboot")) { |
| /* A "reboot" *might* get remapped to "off" then "on", so after |
| * sending the "reboot"-specific values in the main element, we add |
| * sub-elements for "off" and "on" values. |
| * |
| * We short-circuited earlier if "reboot", "off" and "on" are all |
| * disallowed for the local host. However if only one or two are |
| * disallowed, we send back the results and mark which ones are |
| * disallowed. If "reboot" is disallowed, this might cause problems |
| * with older stonithd versions, which won't check for it. Older |
| * versions will ignore "off" and "on", so they are not a problem. |
| */ |
| add_disallowed(dev, action, device, query->target, |
| is_set(query->call_options, st_opt_allow_suicide)); |
| add_action_reply(dev, "off", device, query->target, |
| is_set(query->call_options, st_opt_allow_suicide)); |
| add_action_reply(dev, "on", device, query->target, FALSE); |
| } |
| |
| /* A query without a target wants device parameters */ |
| if (query->target == NULL) { |
| xmlNode *attrs = create_xml_node(dev, XML_TAG_ATTRS); |
| |
| g_hash_table_foreach(device->params, hash2field, attrs); |
| } |
| } |
| |
| crm_xml_add_int(list, F_STONITH_AVAILABLE_DEVICES, available_devices); |
| if (query->target) { |
| crm_debug("Found %d matching devices for '%s'", available_devices, query->target); |
| } else { |
| crm_debug("%d devices installed", available_devices); |
| } |
| |
| if (list != NULL) { |
| crm_log_xml_trace(list, "Add query results"); |
| add_message_xml(query->reply, F_STONITH_CALLDATA, list); |
| } |
| stonith_send_reply(query->reply, query->call_options, query->remote_peer, query->client_id); |
| |
| free_xml(query->reply); |
| free(query->remote_peer); |
| free(query->client_id); |
| free(query->target); |
| free(query->action); |
| free(query); |
| free_xml(list); |
| g_list_free_full(devices, free); |
| } |
| |
| static void |
| stonith_query(xmlNode * msg, const char *remote_peer, const char *client_id, int call_options) |
| { |
| struct st_query_data *query = NULL; |
| const char *action = NULL; |
| const char *target = NULL; |
| int timeout = 0; |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_DEBUG_3); |
| |
| crm_element_value_int(msg, F_STONITH_TIMEOUT, &timeout); |
| if (dev) { |
| const char *device = crm_element_value(dev, F_STONITH_DEVICE); |
| |
| target = crm_element_value(dev, F_STONITH_TARGET); |
| action = crm_element_value(dev, F_STONITH_ACTION); |
| if (device && safe_str_eq(device, "manual_ack")) { |
| /* No query or reply necessary */ |
| return; |
| } |
| } |
| |
| crm_log_xml_debug(msg, "Query"); |
| query = calloc(1, sizeof(struct st_query_data)); |
| |
| query->reply = stonith_construct_reply(msg, NULL, NULL, pcmk_ok); |
| query->remote_peer = remote_peer ? strdup(remote_peer) : NULL; |
| query->client_id = client_id ? strdup(client_id) : NULL; |
| query->target = target ? strdup(target) : NULL; |
| query->action = action ? strdup(action) : NULL; |
| query->call_options = call_options; |
| |
| get_capable_devices(target, action, timeout, |
| is_set(call_options, st_opt_allow_suicide), |
| query, stonith_query_capable_device_cb); |
| } |
| |
| #define ST_LOG_OUTPUT_MAX 512 |
| static void |
| log_operation(async_command_t * cmd, int rc, int pid, const char *next, const char *output) |
| { |
| if (rc == 0) { |
| next = NULL; |
| } |
| |
| if (cmd->victim != NULL) { |
| do_crm_log(rc == 0 ? LOG_NOTICE : LOG_ERR, |
| "Operation '%s' [%d] (call %d from %s) for host '%s' with device '%s' returned: %d (%s)%s%s", |
| cmd->action, pid, cmd->id, cmd->client_name, cmd->victim, |
| cmd->device, rc, pcmk_strerror(rc), |
| (next? ", retrying with " : ""), (next ? next : "")); |
| } else { |
| do_crm_log_unlikely(rc == 0 ? LOG_DEBUG : LOG_NOTICE, |
| "Operation '%s' [%d] for device '%s' returned: %d (%s)%s%s", |
| cmd->action, pid, cmd->device, rc, pcmk_strerror(rc), |
| (next? ", retrying with " : ""), (next ? next : "")); |
| } |
| |
| if (output) { |
| /* Logging the whole string confuses syslog when the string is xml */ |
| char *prefix = crm_strdup_printf("%s:%d", cmd->device, pid); |
| |
| crm_log_output(rc == 0 ? LOG_DEBUG : LOG_WARNING, prefix, output); |
| free(prefix); |
| } |
| } |
| |
| static void |
| stonith_send_async_reply(async_command_t * cmd, const char *output, int rc, GPid pid) |
| { |
| xmlNode *reply = NULL; |
| gboolean bcast = FALSE; |
| |
| reply = stonith_construct_async_reply(cmd, output, NULL, rc); |
| |
| if (safe_str_eq(cmd->action, "metadata")) { |
| /* Too verbose to log */ |
| crm_trace("Metadata query for %s", cmd->device); |
| output = NULL; |
| |
| } else if (crm_str_eq(cmd->action, "monitor", TRUE) || |
| crm_str_eq(cmd->action, "list", TRUE) || crm_str_eq(cmd->action, "status", TRUE)) { |
| crm_trace("Never broadcast '%s' replies", cmd->action); |
| |
| } else if (!stand_alone && safe_str_eq(cmd->origin, cmd->victim) && safe_str_neq(cmd->action, "on")) { |
| crm_trace("Broadcast '%s' reply for %s", cmd->action, cmd->victim); |
| crm_xml_add(reply, F_SUBTYPE, "broadcast"); |
| bcast = TRUE; |
| } |
| |
| log_operation(cmd, rc, pid, NULL, output); |
| crm_log_xml_trace(reply, "Reply"); |
| |
| if (bcast) { |
| crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY); |
| send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE); |
| |
| } else if (cmd->origin) { |
| crm_trace("Directed reply to %s", cmd->origin); |
| send_cluster_message(crm_get_peer(0, cmd->origin), crm_msg_stonith_ng, reply, FALSE); |
| |
| } else { |
| crm_trace("Directed local %ssync reply to %s", |
| (cmd->options & st_opt_sync_call) ? "" : "a-", cmd->client_name); |
| do_local_reply(reply, cmd->client, cmd->options & st_opt_sync_call, FALSE); |
| } |
| |
| if (stand_alone) { |
| /* Do notification with a clean data object */ |
| xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE); |
| |
| crm_xml_add_int(notify_data, F_STONITH_RC, rc); |
| crm_xml_add(notify_data, F_STONITH_TARGET, cmd->victim); |
| crm_xml_add(notify_data, F_STONITH_OPERATION, cmd->op); |
| crm_xml_add(notify_data, F_STONITH_DELEGATE, "localhost"); |
| crm_xml_add(notify_data, F_STONITH_DEVICE, cmd->device); |
| crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id); |
| crm_xml_add(notify_data, F_STONITH_ORIGIN, cmd->client); |
| |
| do_stonith_notify(0, T_STONITH_NOTIFY_FENCE, rc, notify_data); |
| do_stonith_notify(0, T_STONITH_NOTIFY_HISTORY, 0, NULL); |
| } |
| |
| free_xml(reply); |
| } |
| |
| void |
| unfence_cb(GPid pid, int rc, const char *output, gpointer user_data) |
| { |
| async_command_t * cmd = user_data; |
| stonith_device_t *dev = g_hash_table_lookup(device_list, cmd->device); |
| |
| log_operation(cmd, rc, pid, NULL, output); |
| |
| cmd->active_on = NULL; |
| |
| if(dev) { |
| mainloop_set_trigger(dev->work); |
| } else { |
| crm_trace("Device %s does not exist", cmd->device); |
| } |
| |
| if(rc != 0) { |
| crm_exit(DAEMON_RESPAWN_STOP); |
| } |
| } |
| |
| static void |
| cancel_stonith_command(async_command_t * cmd) |
| { |
| stonith_device_t *device; |
| |
| CRM_CHECK(cmd != NULL, return); |
| |
| if (!cmd->device) { |
| return; |
| } |
| |
| device = g_hash_table_lookup(device_list, cmd->device); |
| |
| if (device) { |
| crm_trace("Cancel scheduled '%s' action on %s", cmd->action, device->id); |
| device->pending_ops = g_list_remove(device->pending_ops, cmd); |
| } |
| } |
| |
| static void |
| st_child_done(GPid pid, int rc, const char *output, gpointer user_data) |
| { |
| stonith_device_t *device = NULL; |
| stonith_device_t *next_device = NULL; |
| async_command_t *cmd = user_data; |
| |
| GListPtr gIter = NULL; |
| GListPtr gIterNext = NULL; |
| |
| CRM_CHECK(cmd != NULL, return); |
| |
| cmd->active_on = NULL; |
| |
| /* The device is ready to do something else now */ |
| device = g_hash_table_lookup(device_list, cmd->device); |
| if (device) { |
| if (!device->verified && (rc == pcmk_ok) && |
| (safe_str_eq(cmd->action, "list") || |
| safe_str_eq(cmd->action, "monitor") || safe_str_eq(cmd->action, "status"))) { |
| |
| device->verified = TRUE; |
| } |
| |
| mainloop_set_trigger(device->work); |
| } |
| |
| crm_debug("Operation '%s' on '%s' completed with rc=%d (%d remaining)", |
| cmd->action, cmd->device, rc, g_list_length(cmd->device_next)); |
| |
| if (rc == 0) { |
| GListPtr iter; |
| /* see if there are any required devices left to execute for this op */ |
| for (iter = cmd->device_next; iter != NULL; iter = iter->next) { |
| next_device = g_hash_table_lookup(device_list, iter->data); |
| |
| if (next_device != NULL && is_action_required(cmd->action, next_device)) { |
| cmd->device_next = iter->next; |
| break; |
| } |
| next_device = NULL; |
| } |
| |
| } else if (rc != 0 && cmd->device_next && (is_action_required(cmd->action, device) == FALSE)) { |
| /* if this device didn't work out, see if there are any others we can try. |
| * if the failed device was 'required', we can't pick another device. */ |
| next_device = g_hash_table_lookup(device_list, cmd->device_next->data); |
| cmd->device_next = cmd->device_next->next; |
| } |
| |
| /* this operation requires more fencing, hooray! */ |
| if (next_device) { |
| log_operation(cmd, rc, pid, next_device->id, output); |
| |
| schedule_stonith_command(cmd, next_device); |
| /* Prevent cmd from being freed */ |
| cmd = NULL; |
| goto done; |
| } |
| |
| stonith_send_async_reply(cmd, output, rc, pid); |
| |
| if (rc != 0) { |
| goto done; |
| } |
| |
| /* Check to see if any operations are scheduled to do the exact |
| * same thing that just completed. If so, rather than |
| * performing the same fencing operation twice, return the result |
| * of this operation for all pending commands it matches. */ |
| for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) { |
| async_command_t *cmd_other = gIter->data; |
| |
| gIterNext = gIter->next; |
| |
| if (cmd == cmd_other) { |
| continue; |
| } |
| |
| /* A pending scheduled command matches the command that just finished if. |
| * 1. The client connections are different. |
| * 2. The node victim is the same. |
| * 3. The fencing action is the same. |
| * 4. The device scheduled to execute the action is the same. |
| */ |
| if (safe_str_eq(cmd->client, cmd_other->client) || |
| safe_str_neq(cmd->victim, cmd_other->victim) || |
| safe_str_neq(cmd->action, cmd_other->action) || |
| safe_str_neq(cmd->device, cmd_other->device)) { |
| |
| continue; |
| } |
| |
| /* Duplicate merging will do the right thing for either type of remapped |
| * reboot. If the executing stonithd remapped an unsupported reboot to |
| * off, then cmd->action will be reboot and will be merged with any |
| * other reboot requests. If the originating stonithd remapped a |
| * topology reboot to off then on, we will get here once with |
| * cmd->action "off" and once with "on", and they will be merged |
| * separately with similar requests. |
| */ |
| crm_notice |
| ("Merging stonith action '%s' targeting %s originating from client %s with identical stonith request from client %s", |
| cmd_other->action, cmd_other->victim, cmd_other->client_name, cmd->client_name); |
| |
| cmd_list = g_list_remove_link(cmd_list, gIter); |
| |
| stonith_send_async_reply(cmd_other, output, rc, pid); |
| cancel_stonith_command(cmd_other); |
| |
| free_async_command(cmd_other); |
| g_list_free_1(gIter); |
| } |
| |
| done: |
| free_async_command(cmd); |
| } |
| |
| static gint |
| sort_device_priority(gconstpointer a, gconstpointer b) |
| { |
| const stonith_device_t *dev_a = a; |
| const stonith_device_t *dev_b = b; |
| |
| if (dev_a->priority > dev_b->priority) { |
| return -1; |
| } else if (dev_a->priority < dev_b->priority) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void |
| stonith_fence_get_devices_cb(GList * devices, void *user_data) |
| { |
| async_command_t *cmd = user_data; |
| stonith_device_t *device = NULL; |
| |
| crm_info("Found %d matching devices for '%s'", g_list_length(devices), cmd->victim); |
| |
| if (g_list_length(devices) > 0) { |
| /* Order based on priority */ |
| devices = g_list_sort(devices, sort_device_priority); |
| device = g_hash_table_lookup(device_list, devices->data); |
| |
| if (device) { |
| cmd->device_list = devices; |
| cmd->device_next = devices->next; |
| devices = NULL; /* list owned by cmd now */ |
| } |
| } |
| |
| /* we have a device, schedule it for fencing. */ |
| if (device) { |
| schedule_stonith_command(cmd, device); |
| /* in progress */ |
| return; |
| } |
| |
| /* no device found! */ |
| stonith_send_async_reply(cmd, NULL, -ENODEV, 0); |
| |
| free_async_command(cmd); |
| g_list_free_full(devices, free); |
| } |
| |
| static int |
| stonith_fence(xmlNode * msg) |
| { |
| const char *device_id = NULL; |
| stonith_device_t *device = NULL; |
| async_command_t *cmd = create_async_command(msg); |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR); |
| |
| if (cmd == NULL) { |
| return -EPROTO; |
| } |
| |
| device_id = crm_element_value(dev, F_STONITH_DEVICE); |
| if (device_id) { |
| device = g_hash_table_lookup(device_list, device_id); |
| if (device == NULL) { |
| crm_err("Requested device '%s' is not available", device_id); |
| return -ENODEV; |
| } |
| schedule_stonith_command(cmd, device); |
| |
| } else { |
| const char *host = crm_element_value(dev, F_STONITH_TARGET); |
| |
| if (cmd->options & st_opt_cs_nodeid) { |
| int nodeid = crm_atoi(host, NULL); |
| crm_node_t *node = crm_find_known_peer_full(nodeid, NULL, CRM_GET_PEER_ANY); |
| |
| if (node) { |
| host = node->uname; |
| } |
| } |
| |
| /* If we get to here, then self-fencing is implicitly allowed */ |
| get_capable_devices(host, cmd->action, cmd->default_timeout, |
| TRUE, cmd, stonith_fence_get_devices_cb); |
| } |
| |
| return -EINPROGRESS; |
| } |
| |
| xmlNode * |
| stonith_construct_reply(xmlNode * request, const char *output, xmlNode * data, int rc) |
| { |
| int lpc = 0; |
| xmlNode *reply = NULL; |
| |
| const char *name = NULL; |
| const char *value = NULL; |
| |
| const char *names[] = { |
| F_STONITH_OPERATION, |
| F_STONITH_CALLID, |
| F_STONITH_CLIENTID, |
| F_STONITH_CLIENTNAME, |
| F_STONITH_REMOTE_OP_ID, |
| F_STONITH_CALLOPTS |
| }; |
| |
| crm_trace("Creating a basic reply"); |
| reply = create_xml_node(NULL, T_STONITH_REPLY); |
| |
| crm_xml_add(reply, "st_origin", __FUNCTION__); |
| crm_xml_add(reply, F_TYPE, T_STONITH_NG); |
| crm_xml_add(reply, "st_output", output); |
| crm_xml_add_int(reply, F_STONITH_RC, rc); |
| |
| CRM_CHECK(request != NULL, crm_warn("Can't create a sane reply"); return reply); |
| for (lpc = 0; lpc < DIMOF(names); lpc++) { |
| name = names[lpc]; |
| value = crm_element_value(request, name); |
| crm_xml_add(reply, name, value); |
| } |
| |
| if (data != NULL) { |
| crm_trace("Attaching reply output"); |
| add_message_xml(reply, F_STONITH_CALLDATA, data); |
| } |
| return reply; |
| } |
| |
| static xmlNode * |
| stonith_construct_async_reply(async_command_t * cmd, const char *output, xmlNode * data, int rc) |
| { |
| xmlNode *reply = NULL; |
| |
| crm_trace("Creating a basic reply"); |
| reply = create_xml_node(NULL, T_STONITH_REPLY); |
| |
| crm_xml_add(reply, "st_origin", __FUNCTION__); |
| crm_xml_add(reply, F_TYPE, T_STONITH_NG); |
| |
| crm_xml_add(reply, F_STONITH_OPERATION, cmd->op); |
| crm_xml_add(reply, F_STONITH_DEVICE, cmd->device); |
| crm_xml_add(reply, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id); |
| crm_xml_add(reply, F_STONITH_CLIENTID, cmd->client); |
| crm_xml_add(reply, F_STONITH_CLIENTNAME, cmd->client_name); |
| crm_xml_add(reply, F_STONITH_TARGET, cmd->victim); |
| crm_xml_add(reply, F_STONITH_ACTION, cmd->op); |
| crm_xml_add(reply, F_STONITH_ORIGIN, cmd->origin); |
| crm_xml_add_int(reply, F_STONITH_CALLID, cmd->id); |
| crm_xml_add_int(reply, F_STONITH_CALLOPTS, cmd->options); |
| |
| crm_xml_add_int(reply, F_STONITH_RC, rc); |
| |
| crm_xml_add(reply, "st_output", output); |
| |
| if (data != NULL) { |
| crm_info("Attaching reply output"); |
| add_message_xml(reply, F_STONITH_CALLDATA, data); |
| } |
| return reply; |
| } |
| |
| bool fencing_peer_active(crm_node_t *peer) |
| { |
| if (peer == NULL) { |
| return FALSE; |
| } else if (peer->uname == NULL) { |
| return FALSE; |
| } else if (is_set(peer->processes, crm_get_cluster_proc())) { |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /*! |
| * \internal |
| * \brief Determine if we need to use an alternate node to |
| * fence the target. If so return that node's uname |
| * |
| * \retval NULL, no alternate host |
| * \retval uname, uname of alternate host to use |
| */ |
| static const char * |
| check_alternate_host(const char *target) |
| { |
| const char *alternate_host = NULL; |
| |
| crm_trace("Checking if we (%s) can fence %s", stonith_our_uname, target); |
| if (find_topology_for_host(target) && safe_str_eq(target, stonith_our_uname)) { |
| GHashTableIter gIter; |
| crm_node_t *entry = NULL; |
| |
| g_hash_table_iter_init(&gIter, crm_peer_cache); |
| while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) { |
| crm_trace("Checking for %s.%d != %s", entry->uname, entry->id, target); |
| if (fencing_peer_active(entry) |
| && safe_str_neq(entry->uname, target)) { |
| alternate_host = entry->uname; |
| break; |
| } |
| } |
| if (alternate_host == NULL) { |
| crm_err("No alternate host available to handle complex self fencing request"); |
| g_hash_table_iter_init(&gIter, crm_peer_cache); |
| while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) { |
| crm_notice("Peer[%d] %s", entry->id, entry->uname); |
| } |
| } |
| } |
| |
| return alternate_host; |
| } |
| |
| static void |
| stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer, |
| const char *client_id) |
| { |
| if (remote_peer) { |
| send_cluster_message(crm_get_peer(0, remote_peer), crm_msg_stonith_ng, reply, FALSE); |
| } else { |
| do_local_reply(reply, client_id, is_set(call_options, st_opt_sync_call), remote_peer != NULL); |
| } |
| } |
| |
| static void |
| remove_relay_op(xmlNode * request) |
| { |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_ACTION, request, LOG_TRACE); |
| const char *relay_op_id = NULL; |
| const char *op_id = NULL; |
| const char *client_name = NULL; |
| const char *target = NULL; |
| remote_fencing_op_t *relay_op = NULL; |
| |
| if (dev) { |
| target = crm_element_value(dev, F_STONITH_TARGET); |
| } |
| |
| relay_op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID_RELAY); |
| op_id = crm_element_value(request, F_STONITH_REMOTE_OP_ID); |
| client_name = crm_element_value(request, F_STONITH_CLIENTNAME); |
| |
| /* Delete RELAY operation. */ |
| if (relay_op_id && target && safe_str_eq(target, stonith_our_uname)) { |
| relay_op = g_hash_table_lookup(stonith_remote_op_list, relay_op_id); |
| |
| if (relay_op) { |
| GHashTableIter iter; |
| remote_fencing_op_t *list_op = NULL; |
| g_hash_table_iter_init(&iter, stonith_remote_op_list); |
| |
| /* If the operation to be deleted is registered as a duplicate, delete the registration. */ |
| while (g_hash_table_iter_next(&iter, NULL, (void **)&list_op)) { |
| GListPtr dup_iter = NULL; |
| if (list_op != relay_op) { |
| for (dup_iter = list_op->duplicates; dup_iter != NULL; dup_iter = dup_iter->next) { |
| remote_fencing_op_t *other = dup_iter->data; |
| if (other == relay_op) { |
| other->duplicates = g_list_remove(other->duplicates, relay_op); |
| break; |
| } |
| } |
| } |
| } |
| crm_info("Delete the relay op : %s - %s of %s for %s.(replaced by op : %s - %s of %s for %s)", |
| relay_op->id, relay_op->action, relay_op->target, relay_op->client_name, |
| op_id, relay_op->action, target, client_name); |
| |
| g_hash_table_remove(stonith_remote_op_list, relay_op_id); |
| } |
| } |
| } |
| |
| static int |
| handle_request(crm_client_t * client, uint32_t id, uint32_t flags, xmlNode * request, |
| const char *remote_peer) |
| { |
| int call_options = 0; |
| int rc = -EOPNOTSUPP; |
| |
| xmlNode *data = NULL; |
| xmlNode *reply = NULL; |
| |
| char *output = NULL; |
| const char *op = crm_element_value(request, F_STONITH_OPERATION); |
| const char *client_id = crm_element_value(request, F_STONITH_CLIENTID); |
| |
| #if ENABLE_ACL |
| /* IPC commands related to fencing configuration may be done only by |
| * privileged users (i.e. root or hacluster) when ACLs are supported, |
| * because all other users should go through the CIB to have ACLs applied. |
| * |
| * If no client was given, this is a peer request, which is always allowed. |
| */ |
| bool allowed = (client == NULL) |
| || is_set(client->flags, crm_client_flag_ipc_privileged); |
| #else |
| bool allowed = true; |
| #endif |
| |
| crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options); |
| |
| if (is_set(call_options, st_opt_sync_call)) { |
| CRM_ASSERT(client == NULL || client->request_id == id); |
| } |
| |
| if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) { |
| xmlNode *reply = create_xml_node(NULL, "reply"); |
| |
| CRM_ASSERT(client); |
| crm_xml_add(reply, F_STONITH_OPERATION, CRM_OP_REGISTER); |
| crm_xml_add(reply, F_STONITH_CLIENTID, client->id); |
| crm_ipcs_send(client, id, reply, flags); |
| client->request_id = 0; |
| free_xml(reply); |
| return 0; |
| |
| } else if (crm_str_eq(op, STONITH_OP_EXEC, TRUE)) { |
| rc = stonith_device_action(request, &output); |
| |
| } else if (crm_str_eq(op, STONITH_OP_TIMEOUT_UPDATE, TRUE)) { |
| const char *call_id = crm_element_value(request, F_STONITH_CALLID); |
| const char *client_id = crm_element_value(request, F_STONITH_CLIENTID); |
| int op_timeout = 0; |
| |
| crm_element_value_int(request, F_STONITH_TIMEOUT, &op_timeout); |
| do_stonith_async_timeout_update(client_id, call_id, op_timeout); |
| return 0; |
| |
| } else if (crm_str_eq(op, STONITH_OP_QUERY, TRUE)) { |
| if (remote_peer) { |
| create_remote_stonith_op(client_id, request, TRUE); /* Record it for the future notification */ |
| } |
| |
| /* Delete the DC node RELAY operation. */ |
| remove_relay_op(request); |
| |
| stonith_query(request, remote_peer, client_id, call_options); |
| return 0; |
| |
| } else if (crm_str_eq(op, T_STONITH_NOTIFY, TRUE)) { |
| const char *flag_name = NULL; |
| |
| CRM_ASSERT(client); |
| flag_name = crm_element_value(request, F_STONITH_NOTIFY_ACTIVATE); |
| if (flag_name) { |
| crm_debug("Setting %s callbacks for %s (%s): ON", flag_name, client->name, client->id); |
| client->options |= get_stonith_flag(flag_name); |
| } |
| |
| flag_name = crm_element_value(request, F_STONITH_NOTIFY_DEACTIVATE); |
| if (flag_name) { |
| crm_debug("Setting %s callbacks for %s (%s): off", flag_name, client->name, client->id); |
| client->options |= get_stonith_flag(flag_name); |
| } |
| |
| if (flags & crm_ipc_client_response) { |
| crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__); |
| } |
| return 0; |
| |
| } else if (crm_str_eq(op, STONITH_OP_RELAY, TRUE)) { |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE); |
| |
| crm_notice("Peer %s has received a forwarded fencing request from %s to fence (%s) peer %s", |
| stonith_our_uname, |
| client ? client->name : remote_peer, |
| crm_element_value(dev, F_STONITH_ACTION), |
| crm_element_value(dev, F_STONITH_TARGET)); |
| |
| if (initiate_remote_stonith_op(NULL, request, FALSE) != NULL) { |
| rc = -EINPROGRESS; |
| } |
| |
| } else if (crm_str_eq(op, STONITH_OP_FENCE, TRUE)) { |
| |
| if (remote_peer || stand_alone) { |
| rc = stonith_fence(request); |
| |
| } else if (call_options & st_opt_manual_ack) { |
| remote_fencing_op_t *rop = NULL; |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE); |
| const char *target = crm_element_value(dev, F_STONITH_TARGET); |
| |
| crm_notice("Received manual confirmation that %s is fenced", target); |
| rop = initiate_remote_stonith_op(client, request, TRUE); |
| rc = stonith_manual_ack(request, rop); |
| |
| } else { |
| const char *alternate_host = NULL; |
| xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE); |
| const char *target = crm_element_value(dev, F_STONITH_TARGET); |
| const char *action = crm_element_value(dev, F_STONITH_ACTION); |
| const char *device = crm_element_value(dev, F_STONITH_DEVICE); |
| |
| if (client) { |
| int tolerance = 0; |
| |
| crm_notice("Client %s.%.8s wants to fence (%s) '%s' with device '%s'", |
| client->name, client->id, action, target, device ? device : "(any)"); |
| |
| crm_element_value_int(dev, F_STONITH_TOLERANCE, &tolerance); |
| |
| if (stonith_check_fence_tolerance(tolerance, target, action)) { |
| rc = 0; |
| goto done; |
| } |
| |
| } else { |
| crm_notice("Peer %s wants to fence (%s) '%s' with device '%s'", |
| remote_peer, action, target, device ? device : "(any)"); |
| } |
| |
| alternate_host = check_alternate_host(target); |
| |
| if (alternate_host && client) { |
| const char *client_id = NULL; |
| remote_fencing_op_t *op = NULL; |
| |
| crm_notice("Forwarding complex self fencing request to peer %s", alternate_host); |
| |
| if (client->id) { |
| client_id = client->id; |
| } else { |
| client_id = crm_element_value(request, F_STONITH_CLIENTID); |
| } |
| |
| /* Create an operation for RELAY and send the ID in the RELAY message. */ |
| /* When a QUERY response is received, delete the RELAY operation to avoid the existence of duplicate operations. */ |
| op = create_remote_stonith_op(client_id, request, FALSE); |
| |
| crm_xml_add(request, F_STONITH_OPERATION, STONITH_OP_RELAY); |
| crm_xml_add(request, F_STONITH_CLIENTID, client->id); |
| crm_xml_add(request, F_STONITH_REMOTE_OP_ID, op->id); |
| send_cluster_message(crm_get_peer(0, alternate_host), crm_msg_stonith_ng, request, |
| FALSE); |
| rc = -EINPROGRESS; |
| |
| } else if (initiate_remote_stonith_op(client, request, FALSE) != NULL) { |
| rc = -EINPROGRESS; |
| } |
| } |
| |
| } else if (crm_str_eq(op, STONITH_OP_FENCE_HISTORY, TRUE)) { |
| rc = stonith_fence_history(request, &data, remote_peer, call_options); |
| if (call_options & st_opt_discard_reply) { |
| /* we don't expect answers to the broadcast |
| * we might have sent out |
| */ |
| free_xml(data); |
| return pcmk_ok; |
| } |
| |
| } else if (crm_str_eq(op, STONITH_OP_DEVICE_ADD, TRUE)) { |
| const char *device_id = NULL; |
| |
| if (allowed) { |
| rc = stonith_device_register(request, &device_id, FALSE); |
| } else { |
| rc = -EACCES; |
| } |
| do_stonith_notify_device(call_options, op, rc, device_id); |
| |
| } else if (crm_str_eq(op, STONITH_OP_DEVICE_DEL, TRUE)) { |
| xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request, LOG_ERR); |
| const char *device_id = crm_element_value(dev, XML_ATTR_ID); |
| |
| if (allowed) { |
| rc = stonith_device_remove(device_id, FALSE); |
| } else { |
| rc = -EACCES; |
| } |
| do_stonith_notify_device(call_options, op, rc, device_id); |
| |
| } else if (crm_str_eq(op, STONITH_OP_LEVEL_ADD, TRUE)) { |
| char *device_id = NULL; |
| |
| if (allowed) { |
| rc = stonith_level_register(request, &device_id); |
| } else { |
| rc = -EACCES; |
| } |
| do_stonith_notify_level(call_options, op, rc, device_id); |
| free(device_id); |
| |
| } else if (crm_str_eq(op, STONITH_OP_LEVEL_DEL, TRUE)) { |
| char *device_id = NULL; |
| |
| if (allowed) { |
| rc = stonith_level_remove(request, &device_id); |
| } else { |
| rc = -EACCES; |
| } |
| do_stonith_notify_level(call_options, op, rc, device_id); |
| |
| } else if (crm_str_eq(op, STONITH_OP_CONFIRM, TRUE)) { |
| async_command_t *cmd = create_async_command(request); |
| xmlNode *reply = stonith_construct_async_reply(cmd, NULL, NULL, 0); |
| |
| crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY); |
| crm_notice("Broadcasting manual fencing confirmation for node %s", cmd->victim); |
| send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE); |
| |
| free_async_command(cmd); |
| free_xml(reply); |
| |
| } else if(safe_str_eq(op, CRM_OP_RM_NODE_CACHE)) { |
| int node_id = 0; |
| const char *name = NULL; |
| |
| crm_element_value_int(request, XML_ATTR_ID, &node_id); |
| name = crm_element_value(request, XML_ATTR_UNAME); |
| reap_crm_member(node_id, name); |
| |
| return pcmk_ok; |
| |
| } else { |
| crm_err("Unknown %s from %s", op, client ? client->name : remote_peer); |
| crm_log_xml_warn(request, "UnknownOp"); |
| } |
| |
| done: |
| |
| if (rc == -EACCES) { |
| crm_warn("Rejecting IPC request '%s' from unprivileged client %s", |
| crm_str(op), crm_client_name(client)); |
| } |
| |
| /* Always reply unless the request is in process still. |
| * If in progress, a reply will happen async after the request |
| * processing is finished */ |
| if (rc != -EINPROGRESS) { |
| crm_trace("Reply handling: %p %u %u %d %d %s", client, client?client->request_id:0, |
| id, is_set(call_options, st_opt_sync_call), call_options, |
| crm_element_value(request, F_STONITH_CALLOPTS)); |
| |
| if (is_set(call_options, st_opt_sync_call)) { |
| CRM_ASSERT(client == NULL || client->request_id == id); |
| } |
| reply = stonith_construct_reply(request, output, data, rc); |
| stonith_send_reply(reply, call_options, remote_peer, client_id); |
| } |
| |
| free(output); |
| free_xml(data); |
| free_xml(reply); |
| |
| return rc; |
| } |
| |
| static void |
| handle_reply(crm_client_t * client, xmlNode * request, const char *remote_peer) |
| { |
| const char *op = crm_element_value(request, F_STONITH_OPERATION); |
| |
| if (crm_str_eq(op, STONITH_OP_QUERY, TRUE)) { |
| process_remote_stonith_query(request); |
| } else if (crm_str_eq(op, T_STONITH_NOTIFY, TRUE)) { |
| process_remote_stonith_exec(request); |
| } else if (crm_str_eq(op, STONITH_OP_FENCE, TRUE)) { |
| /* Reply to a complex fencing op */ |
| process_remote_stonith_exec(request); |
| } else { |
| crm_err("Unknown %s reply from %s", op, client ? client->name : remote_peer); |
| crm_log_xml_warn(request, "UnknownOp"); |
| } |
| } |
| |
| void |
| stonith_command(crm_client_t * client, uint32_t id, uint32_t flags, xmlNode * request, |
| const char *remote_peer) |
| { |
| int call_options = 0; |
| int rc = 0; |
| gboolean is_reply = FALSE; |
| |
| /* Copy op for reporting. The original might get freed by handle_reply() |
| * before we use it in crm_debug(): |
| * handle_reply() |
| * |- process_remote_stonith_exec() |
| * |-- remote_op_done() |
| * |--- handle_local_reply_and_notify() |
| * |---- crm_xml_add(...F_STONITH_OPERATION...) |
| * |--- free_xml(op->request) |
| */ |
| char *op = crm_element_value_copy(request, F_STONITH_OPERATION); |
| |
| if (get_xpath_object("//" T_STONITH_REPLY, request, LOG_DEBUG_3)) { |
| is_reply = TRUE; |
| } |
| |
| crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options); |
| crm_debug("Processing %s%s %u from %s (%16x)", op, is_reply ? " reply" : "", |
| id, client ? client->name : remote_peer, call_options); |
| |
| if (is_set(call_options, st_opt_sync_call)) { |
| CRM_ASSERT(client == NULL || client->request_id == id); |
| } |
| |
| if (is_reply) { |
| handle_reply(client, request, remote_peer); |
| } else { |
| rc = handle_request(client, id, flags, request, remote_peer); |
| } |
| |
| crm_debug("Processed %s%s from %s: %s (%d)", op, |
| is_reply ? " reply" : "", client ? client->name : remote_peer, |
| rc > 0 ? "" : pcmk_strerror(rc), rc); |
| |
| free(op); |
| } |