diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 3e9e9592e1..4688458988 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,541 +1,473 @@ /* * Copyright 2004-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include -#include - #include -#include -#include +#include +#include // atoi() -#include -#include -#include -#include #include // gboolean, GMainLoop, etc. +#include // xmlNode #include +#include #include #include - +#include #include -#include - #define DEFAULT_MESSAGE_TIMEOUT_MS 30000 static guint message_timer_id = 0; static guint message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; static GMainLoop *mainloop = NULL; -static crm_ipc_t *crmd_channel = NULL; -static char *admin_uuid = NULL; - -gboolean do_init(void); -int do_work(void); -void crmadmin_ipc_connection_destroy(gpointer user_data); -int admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata); -int do_find_node_list(xmlNode * xml_node); + +bool do_work(pcmk_ipc_api_t *api); +void do_find_node_list(xmlNode *xml_node); gboolean admin_message_timeout(gpointer data); +static enum { + cmd_none, + cmd_shutdown, + cmd_health, + cmd_elect_dc, + cmd_whois_dc, + cmd_list_nodes, +} command = cmd_none; + static gboolean BE_VERBOSE = FALSE; -static int expected_responses = 1; static gboolean BASH_EXPORT = FALSE; -static gboolean DO_HEALTH = FALSE; -static gboolean DO_RESET = FALSE; -static gboolean DO_RESOURCE = FALSE; -static gboolean DO_ELECT_DC = FALSE; -static gboolean DO_WHOIS_DC = FALSE; -static gboolean DO_NODE_LIST = FALSE; static gboolean BE_SILENT = FALSE; -static gboolean DO_RESOURCE_LIST = FALSE; -static const char *crmd_operation = NULL; static char *dest_node = NULL; static crm_exit_t exit_code = CRM_EX_OK; -static const char *sys_to = NULL; static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, NULL, '?', "\tThis text", pcmk__option_default }, { "version", no_argument, NULL, '$', "\tVersion information", pcmk__option_default }, { "quiet", no_argument, NULL, 'q', "\tDisplay only the essential query information", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\tIncrease debug output", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\nCommands:", pcmk__option_default }, /* daemon options */ { "status", required_argument, NULL, 'S', "Display the status of the specified node.", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\n\tResult is state of node's internal finite state machine, which " "can be useful for debugging\n", pcmk__option_default }, { "dc_lookup", no_argument, NULL, 'D', "Display the uname of the node co-ordinating the cluster.", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "\n\tThis is an internal detail rarely useful to administrators " "except when deciding on which node to examine the logs.\n", pcmk__option_default }, { "nodes", no_argument, NULL, 'N', "\tDisplay the uname of all member nodes", pcmk__option_default }, { "election", no_argument, NULL, 'E', "(Advanced) Start an election for the cluster co-ordinator", pcmk__option_default }, { "kill", required_argument, NULL, 'K', "(Advanced) Stop controller (not rest of cluster stack) on " "specified node", pcmk__option_default }, { "health", no_argument, NULL, 'H', NULL, pcmk__option_hidden }, { "-spacer-", no_argument, NULL, '-', "\nAdditional Options:", pcmk__option_default }, { XML_ATTR_TIMEOUT, required_argument, NULL, 't', "Time (in milliseconds) to wait before declaring the operation failed", pcmk__option_default }, { "bash-export", no_argument, NULL, 'B', - "Create Bash export entries of the form 'export uname=uuid'\n", + "Display nodes as shell commands of the form 'export uname=uuid' " + "(valid with -N/--nodes)'\n", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', "Notes:", pcmk__option_default }, { "-spacer-", no_argument, NULL, '-', - " The -K and -E commands are rarely used and may be removed in " - "future versions.", + "The -K and -E commands do not work and may be removed in a future " + "version.", pcmk__option_default }, { 0, 0, 0, 0 } }; +static void +quit_main_loop(crm_exit_t ec) +{ + exit_code = ec; + if (mainloop != NULL) { + GMainLoop *mloop = mainloop; + + mainloop = NULL; // Don't re-enter this block + pcmk_quit_main_loop(mloop, 10); + g_main_loop_unref(mloop); + } +} + +static void +controller_event_cb(pcmk_ipc_api_t *controld_api, + enum pcmk_ipc_event event_type, crm_exit_t status, + void *event_data, void *user_data) +{ + pcmk_controld_api_reply_t *reply = event_data; + + switch (event_type) { + case pcmk_ipc_event_disconnect: + if (exit_code == CRM_EX_DISCONNECT) { // Unexpected + fprintf(stderr, "error: Lost connection to controller\n"); + } + goto done; + break; + + case pcmk_ipc_event_reply: + break; + + default: + return; + } + + if (message_timer_id != 0) { + g_source_remove(message_timer_id); + message_timer_id = 0; + } + + if (status != CRM_EX_OK) { + fprintf(stderr, "error: Bad reply from controller: %s", + crm_exit_str(status)); + exit_code = status; + goto done; + } + + if (reply->reply_type != pcmk_controld_reply_ping) { + fprintf(stderr, "error: Unknown reply type %d from controller\n", + reply->reply_type); + goto done; + } + + // Parse desired information from reply + switch (command) { + case cmd_health: + printf("Status of %s@%s: %s (%s)\n", + reply->data.ping.sys_from, + reply->host_from, + reply->data.ping.fsa_state, + reply->data.ping.result); + if (BE_SILENT && (reply->data.ping.fsa_state != NULL)) { + fprintf(stderr, "%s\n", reply->data.ping.fsa_state); + } + exit_code = CRM_EX_OK; + break; + + case cmd_whois_dc: + printf("Designated Controller is: %s\n", reply->host_from); + if (BE_SILENT && (reply->host_from != NULL)) { + fprintf(stderr, "%s\n", reply->host_from); + } + exit_code = CRM_EX_OK; + break; + + default: // Not really possible here + exit_code = CRM_EX_SOFTWARE; + break; + } + +done: + pcmk_disconnect_ipc(controld_api); + quit_main_loop(exit_code); +} + // \return Standard Pacemaker return code static int list_nodes() { cib_t *the_cib = cib_new(); xmlNode *output = NULL; int rc; if (the_cib == NULL) { return ENOMEM; } rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command); if (rc != pcmk_ok) { return pcmk_legacy2rc(rc); } rc = the_cib->cmds->query(the_cib, NULL, &output, cib_scope_local | cib_sync_call); if (rc == pcmk_ok) { do_find_node_list(output); free_xml(output); } the_cib->cmds->signoff(the_cib); return pcmk_legacy2rc(rc); } int main(int argc, char **argv) { int option_index = 0; int argerr = 0; int flag; + int rc; + pcmk_ipc_api_t *controld_api = NULL; + bool need_controld_api = true; crm_log_cli_init("crmadmin"); pcmk__set_cli_options(NULL, " [options]", long_options, "query and manage the Pacemaker controller"); if (argc < 2) { pcmk__cli_help('?', CRM_EX_USAGE); } while (1) { flag = pcmk__next_cli_option(argc, argv, &option_index, NULL); if (flag == -1) break; switch (flag) { case 'V': BE_VERBOSE = TRUE; crm_bump_log_level(argc, argv); break; case 't': message_timeout_ms = (guint) atoi(optarg); if (message_timeout_ms < 1) { message_timeout_ms = DEFAULT_MESSAGE_TIMEOUT_MS; } break; case '$': case '?': pcmk__cli_help(flag, CRM_EX_OK); break; case 'D': - DO_WHOIS_DC = TRUE; + command = cmd_whois_dc; break; case 'B': BASH_EXPORT = TRUE; break; case 'K': - DO_RESET = TRUE; + command = cmd_shutdown; crm_trace("Option %c => %s", flag, optarg); + if (dest_node != NULL) { + free(dest_node); + } dest_node = strdup(optarg); - crmd_operation = CRM_OP_LOCAL_SHUTDOWN; break; case 'q': BE_SILENT = TRUE; break; case 'S': - DO_HEALTH = TRUE; + command = cmd_health; crm_trace("Option %c => %s", flag, optarg); + if (dest_node != NULL) { + free(dest_node); + } dest_node = strdup(optarg); break; case 'E': - DO_ELECT_DC = TRUE; + command = cmd_elect_dc; break; case 'N': - DO_NODE_LIST = TRUE; + command = cmd_list_nodes; + need_controld_api = false; break; case 'H': - DO_HEALTH = TRUE; + fprintf(stderr, "Cluster-wide health option not supported\n"); + ++argerr; break; default: printf("Argument code 0%o (%c) is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } if (optind > argc) { ++argerr; } + if (command == cmd_none) { + fprintf(stderr, "error: Must specify a command option\n\n"); + ++argerr; + } + if (argerr) { pcmk__cli_help('?', CRM_EX_USAGE); } - if (do_init()) { - int res = 0; - - res = do_work(); - if (res > 0) { - /* wait for the reply by creating a mainloop and running it until - * the callbacks are invoked... - */ - mainloop = g_main_loop_new(NULL, FALSE); - crm_trace("Waiting for %d replies from the local CRM", expected_responses); - - message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); - - g_main_loop_run(mainloop); - - } else if (res < 0) { - crm_err("No message to send"); - exit_code = CRM_EX_ERROR; + // Connect to the controller if needed + if (need_controld_api) { + rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); + if (controld_api == NULL) { + fprintf(stderr, "error: Could not connect to controller: %s\n", + pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); + goto done; } - } else { - crm_warn("Init failed, could not perform requested operations"); - exit_code = CRM_EX_UNAVAILABLE; - } - - crm_trace("%s exiting normally", crm_system_name); - return exit_code; -} - -int -do_work(void) -{ - int ret = 1; - - /* construct the request */ - xmlNode *msg_data = NULL; - gboolean all_is_good = TRUE; - - if (DO_HEALTH == TRUE) { - crm_trace("Querying the system"); - - sys_to = CRM_SYSTEM_DC; - - if (dest_node != NULL) { - sys_to = CRM_SYSTEM_CRMD; - crmd_operation = CRM_OP_PING; - - if (BE_VERBOSE) { - expected_responses = 1; - } - - } else { - crm_info("Cluster-wide health not available yet"); - all_is_good = FALSE; + pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); + rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main); + if (rc != pcmk_rc_ok) { + fprintf(stderr, "error: Could not connect to controller: %s\n", + pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); + goto done; } - - } else if (DO_ELECT_DC) { - /* tell the local node to initiate an election */ - - dest_node = NULL; - sys_to = CRM_SYSTEM_CRMD; - crmd_operation = CRM_OP_VOTE; - ret = 0; /* no return message */ - - } else if (DO_WHOIS_DC) { - dest_node = NULL; - sys_to = CRM_SYSTEM_DC; - crmd_operation = CRM_OP_PING; - - } else if (DO_NODE_LIST) { - crm_exit(pcmk_rc2exitc(list_nodes())); - - } else if (DO_RESET) { - /* tell dest_node to initiate the shutdown procedure - * - * if dest_node is NULL, the request will be sent to the - * local node - */ - sys_to = CRM_SYSTEM_CRMD; - ret = 0; /* no return message */ - - } else { - crm_err("Unknown options"); - all_is_good = FALSE; } - if (all_is_good == FALSE) { - crm_err("Creation of request failed. No message to send"); - return -1; + if (do_work(controld_api)) { + // A reply is needed from controller, so run main loop to get it + exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects + mainloop = g_main_loop_new(NULL, FALSE); + message_timer_id = g_timeout_add(message_timeout_ms, + admin_message_timeout, NULL); + g_main_loop_run(mainloop); } -/* send it */ - if (crmd_channel == NULL) { - crm_err("The IPC connection is not valid, cannot send anything"); - return -1; - } - - if (sys_to == NULL) { - if (dest_node != NULL) { - sys_to = CRM_SYSTEM_CRMD; - } else { - sys_to = CRM_SYSTEM_DC; - } - } - - { - xmlNode *cmd = create_request(crmd_operation, msg_data, dest_node, sys_to, - crm_system_name, admin_uuid); - - crm_ipc_send(crmd_channel, cmd, 0, 0, NULL); - free_xml(cmd); - } - - return ret; -} +done: + if (controld_api != NULL) { + pcmk_ipc_api_t *capi = controld_api; -void -crmadmin_ipc_connection_destroy(gpointer user_data) -{ - crm_err("Connection to controller was terminated"); - if (mainloop) { - g_main_loop_quit(mainloop); - } else { - crm_exit(CRM_EX_DISCONNECT); + controld_api = NULL; // Ensure we can't free this twice + pcmk_free_ipc_api(capi); } -} - -struct ipc_client_callbacks crm_callbacks = { - .dispatch = admin_msg_callback, - .destroy = crmadmin_ipc_connection_destroy -}; - -gboolean -do_init(void) -{ - mainloop_io_t *source = - mainloop_add_ipc_client(CRM_SYSTEM_CRMD, G_PRIORITY_DEFAULT, 0, NULL, &crm_callbacks); - - admin_uuid = pcmk__getpid_s(); - - crmd_channel = mainloop_get_ipc_client(source); - - if (DO_RESOURCE || DO_RESOURCE_LIST || DO_NODE_LIST) { - return TRUE; - - } else if (crmd_channel != NULL) { - xmlNode *xml = create_hello_message(admin_uuid, crm_system_name, "0", "1"); - - crm_ipc_send(crmd_channel, xml, 0, 0, NULL); - return TRUE; + if (mainloop != NULL) { + g_main_loop_unref(mainloop); + mainloop = NULL; } - return FALSE; + return crm_exit(exit_code); } -static bool -validate_crm_message(xmlNode * msg, const char *sys, const char *uuid, const char *msg_type) +// \return True if reply from controller is needed +bool +do_work(pcmk_ipc_api_t *controld_api) { - const char *type = NULL; - const char *crm_msg_reference = NULL; - - if (msg == NULL) { - return FALSE; - } + bool need_reply = false; + int rc = pcmk_rc_ok; - type = crm_element_value(msg, F_CRM_MSG_TYPE); - crm_msg_reference = crm_element_value(msg, XML_ATTR_REFERENCE); - - if (type == NULL) { - crm_info("No message type defined."); - return FALSE; - - } else if (msg_type != NULL && strcasecmp(msg_type, type) != 0) { - crm_info("Expecting a (%s) message but received a (%s).", msg_type, type); - return FALSE; - } - - if (crm_msg_reference == NULL) { - crm_info("No message crm_msg_reference defined."); - return FALSE; - } - - return TRUE; -} - -int -admin_msg_callback(const char *buffer, ssize_t length, gpointer userdata) -{ - static int received_responses = 0; - xmlNode *xml = string2xml(buffer); - - received_responses++; - g_source_remove(message_timer_id); - - crm_log_xml_trace(xml, "ipc"); - - if (xml == NULL) { - crm_info("XML in IPC message was not valid... " "discarding."); - - } else if (validate_crm_message(xml, crm_system_name, admin_uuid, XML_ATTR_RESPONSE) == FALSE) { - crm_trace("Message was not a CRM response. Discarding."); - - } else if (DO_HEALTH) { - xmlNode *data = get_message_xml(xml, F_CRM_DATA); - const char *state = crm_element_value(data, XML_PING_ATTR_CRMDSTATE); + switch (command) { + case cmd_shutdown: + rc = pcmk_controld_api_shutdown(controld_api, dest_node); + break; - printf("Status of %s@%s: %s (%s)\n", - crm_element_value(data, XML_PING_ATTR_SYSFROM), - crm_element_value(xml, F_CRM_HOST_FROM), - state, crm_element_value(data, XML_PING_ATTR_STATUS)); + case cmd_health: // dest_node != NULL + case cmd_whois_dc: // dest_node == NULL + rc = pcmk_controld_api_ping(controld_api, dest_node); + need_reply = true; + break; - if (BE_SILENT && state != NULL) { - fprintf(stderr, "%s\n", state); - } + case cmd_elect_dc: + rc = pcmk_controld_api_start_election(controld_api); + break; - } else if (DO_WHOIS_DC) { - const char *dc = crm_element_value(xml, F_CRM_HOST_FROM); + case cmd_list_nodes: + rc = list_nodes(); + break; - printf("Designated Controller is: %s\n", dc); - if (BE_SILENT && dc != NULL) { - fprintf(stderr, "%s\n", dc); - } - crm_exit(CRM_EX_OK); + case cmd_none: // not actually possible here + break; } - - free_xml(xml); - - if (received_responses >= expected_responses) { - crm_trace("Received expected number (%d) of replies, exiting normally", - expected_responses); - crm_exit(CRM_EX_OK); + if (rc != pcmk_rc_ok) { + fprintf(stderr, "error: Command failed: %s", pcmk_rc_str(rc)); + exit_code = pcmk_rc2exitc(rc); } - - message_timer_id = g_timeout_add(message_timeout_ms, admin_message_timeout, NULL); - return 0; + return need_reply; } gboolean admin_message_timeout(gpointer data) { - fprintf(stderr, "No messages received in %d seconds.. aborting\n", - (int)message_timeout_ms / 1000); - crm_err("No messages received in %d seconds", (int)message_timeout_ms / 1000); - exit_code = CRM_EX_TIMEOUT; - g_main_loop_quit(mainloop); - return FALSE; + fprintf(stderr, + "error: No reply received from controller before timeout (%dms)\n", + message_timeout_ms); + message_timer_id = 0; + quit_main_loop(CRM_EX_TIMEOUT); + return FALSE; // Tells glib to remove source } -int +void do_find_node_list(xmlNode * xml_node) { int found = 0; xmlNode *node = NULL; xmlNode *nodes = get_object_root(XML_CIB_TAG_NODES, xml_node); - for (node = __xml_first_child_element(nodes); node != NULL; - node = __xml_next_element(node)) { + for (node = first_named_child(nodes, XML_CIB_TAG_NODE); node != NULL; + node = crm_next_same_xml(node)) { - if (crm_str_eq((const char *)node->name, XML_CIB_TAG_NODE, TRUE)) { + if (BASH_EXPORT) { + printf("export %s=%s\n", + crm_element_value(node, XML_ATTR_UNAME), + crm_element_value(node, XML_ATTR_ID)); + } else { + const char *node_type = crm_element_value(node, XML_ATTR_TYPE); - if (BASH_EXPORT) { - printf("export %s=%s\n", - crm_element_value(node, XML_ATTR_UNAME), - crm_element_value(node, XML_ATTR_ID)); - } else { - printf("%s node: %s (%s)\n", - crm_element_value(node, XML_ATTR_TYPE), - crm_element_value(node, XML_ATTR_UNAME), - crm_element_value(node, XML_ATTR_ID)); + if (node_type == NULL) { + node_type = "member"; } - found++; + printf("%s node: %s (%s)\n", node_type, + crm_element_value(node, XML_ATTR_UNAME), + crm_element_value(node, XML_ATTR_ID)); } + found++; } + // @TODO List Pacemaker Remote nodes that don't have a entry if (found == 0) { - printf("NO nodes configured\n"); + printf("No nodes configured\n"); } - - return found; }