diff --git a/fencing/admin.c b/fencing/admin.c index fcbb527890..28d1268089 100644 --- a/fencing/admin.c +++ b/fencing/admin.c @@ -1,598 +1,632 @@ /* * Copyright (C) 2009 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* *INDENT-OFF* */ static struct crm_option long_options[] = { {"help", 0, 0, '?', "\tThis text"}, {"version", 0, 0, '$', "\tVersion information" }, {"verbose", 0, 0, 'V', "\tIncrease debug output"}, {"quiet", 0, 0, 'q', "\tPrint only essential output"}, {"-spacer-", 0, 0, '-', "Commands:"}, {"list", 1, 0, 'l', "List devices that can terminate the specified host"}, {"list-registered", 0, 0, 'L', "List all registered devices"}, {"list-installed", 0, 0, 'I', "List all installed devices"}, + {"list-targets", 1, 0, 's', "List the device's target"}, {"-spacer-", 0, 0, '-', ""}, {"metadata", 0, 0, 'M', "Check the device's metadata"}, {"query", 1, 0, 'Q', "Check the device's status"}, {"fence", 1, 0, 'F', "Fence the named host"}, {"unfence", 1, 0, 'U', "Unfence the named host"}, {"reboot", 1, 0, 'B', "Reboot the named host"}, {"confirm", 1, 0, 'C', "Confirm the named host is now safely down"}, {"history", 1, 0, 'H', "Retrieve last fencing operation for specified node (or '*' for all)"}, {"last", 1, 0, 'h', "Indicate when the named node was last fenced. Optional: --as-node-id"}, {"-spacer-", 0, 0, '-', ""}, {"register", 1, 0, 'R', "Register the named stonith device. Requires: --agent, optional: --option"}, {"deregister", 1, 0, 'D', "De-register the named stonith device"}, {"register-level", 1, 0, 'r', "Register a stonith level for the named target, specified as one of:\n\t" "NAME, @PATTERN, or ATTR=VALUE. Requires: --index, one or more --device entries"}, {"deregister-level", 1, 0, 'd', "De-register a stonith level for the named target\n\t" "Target is specified as for --register-level. Requires: --index"}, {"-spacer-", 0, 0, '-', ""}, {"-spacer-", 0, 0, '-', "Options and modifiers:"}, {"agent", 1, 0, 'a', "The agent (eg. fence_xvm) to instantiate when calling with --register"}, {"env-option", 1, 0, 'e'}, {"option", 1, 0, 'o'}, {"tag", 1, 0, 'T', "Identify fencing operations with the specified tag.\n\t" "Useful when there are multiple entities that might be invoking stonith_admin(8)"}, {"device", 1, 0, 'v', "A device to associate with a given host and stonith level"}, {"index", 1, 0, 'i', "The stonith level (1-9)"}, {"timeout", 1, 0, 't', "Operation timeout in seconds"}, {"as-node-id", 0, 0, 'n', "(Advanced) The supplied node is the corosync nodeid (Only for use with --last)" }, {"tolerance", 1, 0, 0, "(Advanced) Do nothing if an equivalent --fence request succeeded less than N seconds earlier" }, {"list-all", 0, 0, 'L', "legacy alias for --list-registered"}, {0, 0, 0, 0} }; /* *INDENT-ON* */ int st_opts = st_opt_sync_call | st_opt_allow_suicide; GMainLoop *mainloop = NULL; struct { stonith_t *st; const char *target; const char *action; char *name; int timeout; int tolerance; int rc; } async_fence_data; static int try_mainloop_connect(void) { stonith_t *st = async_fence_data.st; int tries = 10; int i = 0; int rc = 0; for (i = 0; i < tries; i++) { crm_debug("Connecting as %s", async_fence_data.name); rc = st->cmds->connect(st, async_fence_data.name, NULL); if (!rc) { crm_debug("stonith client connection established"); return 0; } else { crm_debug("stonith client connection failed"); } sleep(1); } crm_err("Could not connect to stonithd."); return -1; } static void notify_callback(stonith_t * st, stonith_event_t * e) { if (e->result != pcmk_ok) { return; } if (safe_str_eq(async_fence_data.target, e->target) && safe_str_eq(async_fence_data.action, e->action)) { async_fence_data.rc = e->result; g_main_loop_quit(mainloop); } } static void fence_callback(stonith_t * stonith, stonith_callback_data_t * data) { async_fence_data.rc = data->rc; g_main_loop_quit(mainloop); } static gboolean async_fence_helper(gpointer user_data) { stonith_t *st = async_fence_data.st; int call_id = 0; if (try_mainloop_connect()) { g_main_loop_quit(mainloop); return TRUE; } st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, notify_callback); call_id = st->cmds->fence(st, st_opt_allow_suicide, async_fence_data.target, async_fence_data.action, async_fence_data.timeout, async_fence_data.tolerance); if (call_id < 0) { g_main_loop_quit(mainloop); return TRUE; } st->cmds->register_callback(st, call_id, async_fence_data.timeout, st_opt_timeout_updates, NULL, "callback", fence_callback); return TRUE; } static int mainloop_fencing(stonith_t * st, const char *target, const char *action, int timeout, int tolerance) { crm_trigger_t *trig; async_fence_data.st = st; async_fence_data.target = target; async_fence_data.action = action; async_fence_data.timeout = timeout; async_fence_data.tolerance = tolerance; async_fence_data.rc = -1; trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL); mainloop_set_trigger(trig); mainloop = g_main_new(FALSE); g_main_run(mainloop); return async_fence_data.rc; } static int handle_level(stonith_t *st, char *target, int fence_level, stonith_key_value_t *devices, bool added) { char *node = NULL; char *pattern = NULL; char *name = NULL; char *value = strchr(target, '='); /* Determine if targeting by attribute, node name pattern or node name */ if (value != NULL) { name = target; *value++ = '\0'; } else if (*target == '@') { pattern = target + 1; } else { node = target; } /* Register or unregister level as appropriate */ if (added) { return st->cmds->register_level_full(st, st_opts, node, pattern, name, value, fence_level, devices); } return st->cmds->remove_level_full(st, st_opts, node, pattern, name, value, fence_level); } static int show_history(stonith_t *st, const char *target, int timeout, int quiet, int verbose) { stonith_history_t *history, *hp, *latest = NULL; int rc = 0; rc = st->cmds->history(st, st_opts, (safe_str_eq(target, "*")? NULL : target), &history, timeout); for (hp = history; hp; hp = hp->next) { char *action_s = NULL; time_t complete = hp->completed; if (hp->state == st_done) { latest = hp; } if (quiet || !verbose) { continue; } else if (hp->action == NULL) { action_s = strdup("unknown"); } else if (hp->action[0] != 'r') { action_s = crm_concat("turn", hp->action, ' '); } else { action_s = strdup(hp->action); } if (hp->state == st_failed) { printf("%s failed to %s node %s on behalf of %s from %s at %s\n", hp->delegate ? hp->delegate : "We", action_s, hp->target, hp->client, hp->origin, ctime(&complete)); } else if (hp->state == st_done && hp->delegate) { printf("%s was able to %s node %s on behalf of %s from %s at %s\n", hp->delegate, action_s, hp->target, hp->client, hp->origin, ctime(&complete)); } else if (hp->state == st_done) { printf("We were able to %s node %s on behalf of %s from %s at %s\n", action_s, hp->target, hp->client, hp->origin, ctime(&complete)); } else { /* ocf:pacemaker:controld depends on "wishes to" being * in this output, when used with older versions of DLM * that don't report stateful_merge_wait */ printf("%s at %s wishes to %s node %s - %d %d\n", hp->client, hp->origin, action_s, hp->target, hp->state, hp->completed); } free(action_s); } if (latest) { if (quiet) { printf("%d\n", latest->completed); } else { char *action_s = NULL; time_t complete = latest->completed; if (latest->action == NULL) { action_s = strdup("unknown"); } else if (latest->action[0] != 'r') { action_s = crm_concat("turn", latest->action, ' '); } else { action_s = strdup(latest->action); } printf("%s was able to %s node %s on behalf of %s from %s at %s\n", latest->delegate ? latest->delegate : "We", action_s, latest->target, latest->client, latest->origin, ctime(&complete)); free(action_s); } } return rc; } int main(int argc, char **argv) { int flag; int rc = 0; int quiet = 0; int verbose = 0; int argerr = 0; int timeout = 120; int option_index = 0; int fence_level = 0; int no_connect = 0; int tolerance = 0; int as_nodeid = FALSE; char *name = NULL; char *value = NULL; char *target = NULL; + char *lists = NULL; const char *agent = NULL; const char *device = NULL; const char *longname = NULL; char action = 0; stonith_t *st = NULL; stonith_key_value_t *params = NULL; stonith_key_value_t *devices = NULL; stonith_key_value_t *dIter = NULL; crm_log_cli_init("stonith_admin"); crm_set_options(NULL, "mode [options]", long_options, "Provides access to the stonith-ng API.\n" "\nAllows the administrator to add/remove/list devices, check device and host status and fence hosts\n"); async_fence_data.name = strdup(crm_system_name); while (1) { flag = crm_get_option_long(argc, argv, &option_index, &longname); if (flag == -1) break; switch (flag) { case 'V': verbose = 1; crm_bump_log_level(argc, argv); break; case '$': case '?': crm_help(flag, EX_OK); break; case 'I': no_connect = 1; /* fall through */ case 'L': action = flag; break; case 'q': quiet = 1; break; case 'Q': case 'R': case 'D': + case 's': action = flag; device = optarg; break; case 'T': free(async_fence_data.name); async_fence_data.name = crm_strdup_printf("%s.%s", crm_system_name, optarg); break; case 'a': agent = optarg; break; case 'l': target = optarg; action = 'L'; break; case 'M': no_connect = 1; action = flag; break; case 't': timeout = crm_atoi(optarg, NULL); break; case 'B': case 'F': case 'U': /* using mainloop here */ no_connect = 1; /* fall through */ case 'C': /* Always log the input arguments */ crm_log_args(argc, argv); target = optarg; action = flag; break; case 'n': as_nodeid = TRUE; break; case 'h': case 'H': case 'r': case 'd': target = optarg; action = flag; break; case 'i': fence_level = crm_atoi(optarg, NULL); break; case 'v': devices = stonith_key_value_add(devices, NULL, optarg); break; case 'o': crm_info("Scanning: -o %s", optarg); rc = sscanf(optarg, "%m[^=]=%m[^=]", &name, &value); if (rc != 2) { crm_err("Invalid option: -o %s", optarg); ++argerr; } else { crm_info("Got: '%s'='%s'", name, value); params = stonith_key_value_add(params, name, value); } free(value); value = NULL; free(name); name = NULL; break; case 'e': { char *key = crm_concat("OCF_RESKEY", optarg, '_'); const char *env = getenv(key); if (env == NULL) { crm_err("Invalid option: -e %s", optarg); ++argerr; } else { crm_info("Got: '%s'='%s'", optarg, env); params = stonith_key_value_add(params, optarg, env); } } break; case 0: if (safe_str_eq("tolerance", longname)) { tolerance = crm_get_msec(optarg) / 1000; /* Send in seconds */ } break; default: ++argerr; break; } } if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', EX_USAGE); } crm_debug("Create"); st = stonith_api_new(); crm_debug("Created"); if (!no_connect) { crm_debug("Connecting as %s", async_fence_data.name); rc = st->cmds->connect(st, async_fence_data.name, NULL); crm_debug("Connect: %d", rc); if (rc < 0) { goto done; } } switch (action) { case 'I': rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout); for (dIter = devices; dIter; dIter = dIter->next) { fprintf(stdout, " %s\n", dIter->value); } if (rc == 0) { fprintf(stderr, "No devices found\n"); } else if (rc > 0) { fprintf(stderr, "%d devices found\n", rc); rc = 0; } stonith_key_value_freeall(devices, 1, 1); break; case 'L': rc = st->cmds->query(st, st_opts, target, &devices, timeout); for (dIter = devices; dIter; dIter = dIter->next) { fprintf(stdout, " %s\n", dIter->value); } if (rc == 0) { fprintf(stderr, "No devices found\n"); } else if (rc > 0) { fprintf(stderr, "%d devices found\n", rc); rc = 0; } stonith_key_value_freeall(devices, 1, 1); break; case 'Q': rc = st->cmds->monitor(st, st_opts, device, timeout); if (rc < 0) { rc = st->cmds->list(st, st_opts, device, NULL, timeout); } break; + case 's': + rc = st->cmds->list(st, st_opts, device, &lists, timeout); + if (rc == 0) { + if (lists) { + char *source = lists, *dest = lists; + + while (*dest) { + if ((*dest == '\\') && (*(dest+1) == 'n')) { + *source = '\n'; + dest++; + dest++; + source++; + } else if ((*dest == ',') || (*dest == ';')) { + dest++; + } else { + *source = *dest; + dest++; + source++; + } + + if (!(*dest)) { + *source = 0; + } + } + fprintf(stdout, "%s", lists); + free(lists); + } + } else { + fprintf(stderr, "List command returned error. rc : %d\n", rc); + } + break; case 'R': rc = st->cmds->register_device(st, st_opts, device, "stonith-ng", agent, params); break; case 'D': rc = st->cmds->remove_device(st, st_opts, device); break; case 'd': case 'r': rc = handle_level(st, target, fence_level, devices, action == 'r'); break; case 'M': if (agent == NULL) { printf("Please specify an agent to query using -a,--agent [value]\n"); return -1; } else { char *buffer = NULL; rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, timeout); if (rc == pcmk_ok) { printf("%s\n", buffer); } free(buffer); } break; case 'C': rc = st->cmds->confirm(st, st_opts, target); break; case 'B': rc = mainloop_fencing(st, target, "reboot", timeout, tolerance); break; case 'F': rc = mainloop_fencing(st, target, "off", timeout, tolerance); break; case 'U': rc = mainloop_fencing(st, target, "on", timeout, tolerance); break; case 'h': { time_t when = 0; if(as_nodeid) { uint32_t nodeid = atol(target); when = stonith_api_time(nodeid, NULL, FALSE); } else { when = stonith_api_time(0, target, FALSE); } if(when) { printf("Node %s last kicked at: %s\n", target, ctime(&when)); } else { printf("Node %s has never been kicked\n", target); } } break; case 'H': rc = show_history(st, target, timeout, quiet, verbose); break; } done: free(async_fence_data.name); crm_info("Command returned: %s (%d)", pcmk_strerror(rc), rc); if (rc < 0) { printf("Command failed: %s\n", pcmk_strerror(rc)); } stonith_key_value_freeall(params, 1, 1); st->cmds->disconnect(st); crm_debug("Disconnect: %d", rc); crm_debug("Destroy"); stonith_api_delete(st); return rc; }