| /* |
| * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> |
| * |
| * 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 <crm_internal.h> |
| |
| #include <sys/param.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| #include <libgen.h> /* for basename() */ |
| |
| #include <crm/crm.h> |
| #include <crm/cluster/internal.h> |
| #include <crm/common/mainloop.h> |
| #include <crm/msg_xml.h> |
| #include <crm/cib.h> |
| #include <crm/attrd.h> |
| |
| static int command = 0; |
| static char *pid_s = NULL; |
| static const char *target_uname = NULL; |
| static GMainLoop *mainloop = NULL; |
| static int exit_code = pcmk_ok; |
| |
| /* *INDENT-OFF* */ |
| static struct crm_option long_options[] = { |
| /* Top-level Options */ |
| {"help", 0, 0, '?', "\tThis text"}, |
| {"version", 0, 0, '$', "\tVersion information" }, |
| {"verbose", 0, 0, 'V', "\tIncrease debug output"}, |
| {"quiet", 0, 0, 'Q', "\tEssential output only"}, |
| |
| {"-spacer-", 1, 0, '-', "\nStack:"}, |
| #if SUPPORT_CMAN |
| {"cman", 0, 0, 'c', "\tOnly try connecting to a cman-based cluster"}, |
| #endif |
| #if SUPPORT_COROSYNC |
| {"openais", 0, 0, 'A', "\tOnly try connecting to an OpenAIS-based cluster"}, |
| #endif |
| #ifdef SUPPORT_CS_QUORUM |
| {"corosync", 0, 0, 'C', "\tOnly try connecting to an Corosync-based cluster"}, |
| #endif |
| #ifdef SUPPORT_HEARTBEAT |
| {"heartbeat", 0, 0, 'H', "Only try connecting to a Heartbeat-based cluster"}, |
| #endif |
| |
| {"-spacer-", 1, 0, '-', "\nCommands:"}, |
| {"name", 0, 0, 'n', "\tDisplay the name used by the cluster for this node"}, |
| {"name-for-id", 1, 0, 'N', "\tDisplay the name used by the cluster for the node with the specified id"}, |
| {"epoch", 0, 0, 'e', "\tDisplay the epoch during which this node joined the cluster"}, |
| {"quorum", 0, 0, 'q', "\tDisplay a 1 if our partition has quorum, 0 if not"}, |
| {"list", 0, 0, 'l', "\tDisplay all known members (past and present) of this cluster (Not available for heartbeat clusters)"}, |
| {"partition", 0, 0, 'p', "Display the members of this partition"}, |
| {"cluster-id", 0, 0, 'i', "Display this node's cluster id"}, |
| {"remove", 1, 0, 'R', "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's configuration and caches"}, |
| {"-spacer-", 1, 0, '-', "In the case of Heartbeat, CMAN and Corosync 2.0, requires that the node has already been removed from the underlying cluster"}, |
| |
| {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, |
| {"force", 0, 0, 'f'}, |
| // @TODO add timeout option for when IPC replies are needed |
| |
| {0, 0, 0, 0} |
| }; |
| /* *INDENT-ON* */ |
| |
| /*! |
| * \internal |
| * \brief Exit crm_node |
| * Clean up memory, and either quit mainloop (if running) or exit |
| * |
| * \param[in] value Exit status |
| */ |
| static void |
| crm_node_exit(int value) |
| { |
| if (pid_s) { |
| free(pid_s); |
| pid_s = NULL; |
| } |
| |
| exit_code = value; |
| |
| if (mainloop && g_main_loop_is_running(mainloop)) { |
| g_main_loop_quit(mainloop); |
| } else { |
| crm_exit(exit_code); |
| } |
| } |
| |
| static void |
| exit_disconnect(gpointer user_data) |
| { |
| fprintf(stderr, "error: Lost connection to cluster\n"); |
| crm_node_exit(ENOTCONN); |
| } |
| |
| typedef int (*ipc_dispatch_fn) (const char *buffer, ssize_t length, |
| gpointer userdata); |
| |
| static crm_ipc_t * |
| new_mainloop_for_ipc(const char *system, ipc_dispatch_fn dispatch) |
| { |
| mainloop_io_t *source = NULL; |
| crm_ipc_t *ipc = NULL; |
| |
| struct ipc_client_callbacks ipc_callbacks = { |
| .dispatch = dispatch, |
| .destroy = exit_disconnect |
| }; |
| |
| mainloop = g_main_loop_new(NULL, FALSE); |
| source = mainloop_add_ipc_client(system, G_PRIORITY_DEFAULT, 0, |
| NULL, &ipc_callbacks); |
| ipc = mainloop_get_ipc_client(source); |
| if (ipc == NULL) { |
| fprintf(stderr, |
| "error: Could not connect to cluster (is it running?)\n"); |
| crm_node_exit(ENOTCONN); |
| } |
| return ipc; |
| } |
| |
| static void |
| run_mainloop_and_exit() |
| { |
| g_main_loop_run(mainloop); |
| g_main_loop_unref(mainloop); |
| mainloop = NULL; |
| crm_node_exit(exit_code); |
| } |
| |
| static int |
| send_controller_hello(crm_ipc_t *controller) |
| { |
| xmlNode *hello = NULL; |
| int rc; |
| |
| pid_s = crm_getpid_s(); |
| hello = create_hello_message(pid_s, crm_system_name, "1", "0"); |
| rc = crm_ipc_send(controller, hello, 0, 0, NULL); |
| free_xml(hello); |
| return (rc < 0)? rc : 0; |
| } |
| |
| static int |
| send_node_info_request(crm_ipc_t *controller, uint32_t nodeid) |
| { |
| xmlNode *ping = NULL; |
| int rc; |
| |
| ping = create_request(CRM_OP_NODE_INFO, NULL, NULL, CRM_SYSTEM_CRMD, |
| crm_system_name, pid_s); |
| if (nodeid > 0) { |
| crm_xml_add_int(ping, XML_ATTR_ID, nodeid); |
| } |
| rc = crm_ipc_send(controller, ping, 0, 0, NULL); |
| free_xml(ping); |
| return (rc < 0)? rc : 0; |
| } |
| |
| static int |
| dispatch_controller(const char *buffer, ssize_t length, gpointer userdata) |
| { |
| xmlNode *message = string2xml(buffer); |
| xmlNode *data = NULL; |
| const char *value = NULL; |
| |
| if (message == NULL) { |
| fprintf(stderr, "error: Could not understand reply from controller\n"); |
| crm_node_exit(1); |
| return 0; |
| } |
| crm_log_xml_trace(message, "controller reply"); |
| |
| exit_code = 1; |
| |
| // Validate reply |
| value = crm_element_value(message, F_CRM_MSG_TYPE); |
| if (safe_str_neq(value, XML_ATTR_RESPONSE)) { |
| fprintf(stderr, "error: Message from controller was not a reply\n"); |
| goto done; |
| } |
| value = crm_element_value(message, XML_ATTR_REFERENCE); |
| if (value == NULL) { |
| fprintf(stderr, "error: Controller reply did not specify original message\n"); |
| goto done; |
| } |
| data = get_message_xml(message, F_CRM_DATA); |
| if (data == NULL) { |
| fprintf(stderr, "error: Controller reply did not contain any data\n"); |
| goto done; |
| } |
| |
| switch (command) { |
| case 'i': |
| value = crm_element_value(data, XML_ATTR_ID); |
| if (value == NULL) { |
| fprintf(stderr, "error: Controller reply did not contain node ID\n"); |
| } else { |
| printf("%s\n", value); |
| exit_code = pcmk_ok; |
| } |
| break; |
| case 'n': |
| case 'N': |
| value = crm_element_value(data, XML_ATTR_UNAME); |
| if (value == NULL) { |
| fprintf(stderr, "Node is not known to cluster\n"); |
| exit_code = 1; |
| } else { |
| printf("%s\n", value); |
| exit_code = pcmk_ok; |
| } |
| break; |
| default: |
| fprintf(stderr, "internal error: Controller reply not expected\n"); |
| exit_code = 1; |
| break; |
| } |
| |
| done: |
| free_xml(message); |
| crm_node_exit(exit_code); |
| return 0; |
| } |
| |
| static void |
| run_controller_mainloop(uint32_t nodeid) |
| { |
| crm_ipc_t *controller = NULL; |
| int rc; |
| |
| controller = new_mainloop_for_ipc(CRM_SYSTEM_CRMD, dispatch_controller); |
| |
| rc = send_controller_hello(controller); |
| if (rc < 0) { |
| fprintf(stderr, "error: Could not register with controller: %s\n", |
| pcmk_strerror(rc)); |
| crm_node_exit(1); |
| } |
| |
| rc = send_node_info_request(controller, nodeid); |
| if (rc < 0) { |
| fprintf(stderr, "error: Could not ping controller: %s\n", |
| pcmk_strerror(rc)); |
| crm_node_exit(1); |
| } |
| |
| // Run main loop to get controller reply via dispatch_controller() |
| run_mainloop_and_exit(); |
| } |
| |
| static void |
| print_node_name() |
| { |
| // Check environment first (i.e. when called by resource agent) |
| const char *name = getenv("OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET); |
| |
| if (name != NULL) { |
| printf("%s\n", name); |
| crm_node_exit(pcmk_ok); |
| |
| } else { |
| // Otherwise ask the controller |
| run_controller_mainloop(0); |
| } |
| } |
| |
| static int |
| cib_remove_node(uint32_t id, const char *name) |
| { |
| int rc; |
| cib_t *cib = NULL; |
| xmlNode *node = NULL; |
| xmlNode *node_state = NULL; |
| |
| crm_trace("Removing %s from the CIB", name); |
| |
| if(name == NULL && id == 0) { |
| return -ENOTUNIQ; |
| } |
| |
| node = create_xml_node(NULL, XML_CIB_TAG_NODE); |
| node_state = create_xml_node(NULL, XML_CIB_TAG_STATE); |
| |
| crm_xml_add(node, XML_ATTR_UNAME, name); |
| crm_xml_add(node_state, XML_ATTR_UNAME, name); |
| if(id) { |
| crm_xml_set_id(node, "%u", id); |
| crm_xml_add(node_state, XML_ATTR_ID, ID(node)); |
| } |
| |
| cib = cib_new(); |
| cib->cmds->signon(cib, crm_system_name, cib_command); |
| |
| rc = cib->cmds->delete(cib, XML_CIB_TAG_NODES, node, cib_sync_call); |
| if (rc != pcmk_ok) { |
| printf("Could not remove %s/%u from " XML_CIB_TAG_NODES ": %s", name, id, pcmk_strerror(rc)); |
| } |
| rc = cib->cmds->delete(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call); |
| if (rc != pcmk_ok) { |
| printf("Could not remove %s/%u from " XML_CIB_TAG_STATUS ": %s", name, id, pcmk_strerror(rc)); |
| } |
| |
| cib->cmds->signoff(cib); |
| cib_delete(cib); |
| return rc; |
| } |
| |
| static int |
| tools_remove_node_cache(const char *node, const char *target) |
| { |
| int n = 0; |
| int rc = -1; |
| char *name = NULL; |
| crm_ipc_t *conn = crm_ipc_new(target, 0); |
| xmlNode *cmd = NULL; |
| char *endptr = NULL; |
| |
| if (!conn) { |
| return -ENOTCONN; |
| } |
| |
| if (!crm_ipc_connect(conn)) { |
| crm_perror(LOG_ERR, "Connection to %s failed", target); |
| crm_ipc_destroy(conn); |
| return -ENOTCONN; |
| } |
| |
| if(safe_str_eq(target, CRM_SYSTEM_CRMD)) { |
| // The controller requires a hello message before sending a request |
| rc = send_controller_hello(conn); |
| if (rc < 0) { |
| fprintf(stderr, "error: Could not register with controller: %s\n", |
| pcmk_strerror(rc)); |
| return rc; |
| } |
| } |
| |
| errno = 0; |
| n = strtol(node, &endptr, 10); |
| if (errno != 0 || endptr == node || *endptr != '\0') { |
| /* Argument was not a nodeid */ |
| n = 0; |
| name = strdup(node); |
| } else { |
| name = get_node_name(n); |
| } |
| |
| crm_trace("Removing %s aka. %s (%u) from the membership cache", name, node, n); |
| |
| if(safe_str_eq(target, T_ATTRD)) { |
| cmd = create_xml_node(NULL, __FUNCTION__); |
| |
| crm_xml_add(cmd, F_TYPE, T_ATTRD); |
| crm_xml_add(cmd, F_ORIG, crm_system_name); |
| |
| crm_xml_add(cmd, F_ATTRD_TASK, ATTRD_OP_PEER_REMOVE); |
| crm_xml_add(cmd, F_ATTRD_HOST, name); |
| |
| if (n) { |
| char buffer[64]; |
| if(snprintf(buffer, 63, "%u", n) > 0) { |
| crm_xml_add(cmd, F_ATTRD_HOST_ID, buffer); |
| } |
| } |
| |
| } else { |
| cmd = create_request(CRM_OP_RM_NODE_CACHE, |
| NULL, NULL, target, crm_system_name, pid_s); |
| if (n) { |
| crm_xml_set_id(cmd, "%u", n); |
| } |
| crm_xml_add(cmd, XML_ATTR_UNAME, name); |
| } |
| |
| rc = crm_ipc_send(conn, cmd, 0, 0, NULL); |
| crm_debug("%s peer cache cleanup for %s (%u): %d", target, name, n, rc); |
| |
| if (rc > 0) { |
| rc = cib_remove_node(n, name); |
| } |
| |
| if (conn) { |
| crm_ipc_close(conn); |
| crm_ipc_destroy(conn); |
| } |
| free_xml(cmd); |
| free(name); |
| return rc > 0 ? 0 : rc; |
| } |
| |
| static gint |
| compare_node_uname(gconstpointer a, gconstpointer b) |
| { |
| const crm_node_t *a_node = a; |
| const crm_node_t *b_node = b; |
| return strcmp(a_node->uname?a_node->uname:"", b_node->uname?b_node->uname:""); |
| } |
| |
| static int |
| node_mcp_dispatch(const char *buffer, ssize_t length, gpointer userdata) |
| { |
| xmlNode *msg = string2xml(buffer); |
| |
| if (msg) { |
| xmlNode *node = NULL; |
| GListPtr nodes = NULL; |
| GListPtr iter = NULL; |
| const char *quorate = crm_element_value(msg, "quorate"); |
| |
| crm_log_xml_trace(msg, "message"); |
| if (command == 'q' && quorate != NULL) { |
| fprintf(stdout, "%s\n", quorate); |
| crm_node_exit(pcmk_ok); |
| |
| } else if(command == 'q') { |
| crm_node_exit(1); |
| } |
| |
| for (node = __xml_first_child(msg); node != NULL; node = __xml_next(node)) { |
| crm_node_t *peer = calloc(1, sizeof(crm_node_t)); |
| |
| nodes = g_list_insert_sorted(nodes, peer, compare_node_uname); |
| peer->uname = (char*)crm_element_value_copy(node, "uname"); |
| peer->state = (char*)crm_element_value_copy(node, "state"); |
| crm_element_value_int(node, "id", (int*)&peer->id); |
| } |
| |
| for(iter = nodes; iter; iter = iter->next) { |
| crm_node_t *peer = iter->data; |
| if (command == 'l') { |
| fprintf(stdout, "%u %s %s\n", |
| peer->id, peer->uname, (peer->state? peer->state : "")); |
| |
| } else if (command == 'p') { |
| if(safe_str_eq(peer->state, CRM_NODE_MEMBER)) { |
| fprintf(stdout, "%s ", peer->uname); |
| } |
| |
| } else if (command == 'i') { |
| if(safe_str_eq(peer->state, CRM_NODE_MEMBER)) { |
| fprintf(stdout, "%u ", peer->id); |
| } |
| } |
| } |
| |
| g_list_free_full(nodes, free); |
| free_xml(msg); |
| |
| if (command == 'p') { |
| fprintf(stdout, "\n"); |
| } |
| |
| crm_node_exit(pcmk_ok); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| try_pacemaker(int command, enum cluster_type_e stack) |
| { |
| if (stack == pcmk_cluster_heartbeat) { |
| /* Nothing to do for them */ |
| return; |
| } |
| |
| switch (command) { |
| case 'e': |
| /* Age only applies to heartbeat clusters */ |
| fprintf(stdout, "1\n"); |
| crm_node_exit(pcmk_ok); |
| break; |
| |
| case 'R': |
| { |
| int lpc = 0; |
| const char *daemons[] = { |
| CRM_SYSTEM_CRMD, |
| "stonith-ng", |
| T_ATTRD, |
| CRM_SYSTEM_MCP, |
| }; |
| |
| for(lpc = 0; lpc < DIMOF(daemons); lpc++) { |
| if (tools_remove_node_cache(target_uname, daemons[lpc])) { |
| crm_err("Failed to connect to %s to remove node '%s'", daemons[lpc], target_uname); |
| crm_node_exit(pcmk_err_generic); |
| } |
| } |
| crm_node_exit(pcmk_ok); |
| } |
| break; |
| |
| case 'i': |
| case 'l': |
| case 'q': |
| case 'p': |
| /* Go to pacemakerd */ |
| { |
| crm_ipc_t *ipc = NULL; |
| xmlNode *poke = NULL; |
| |
| ipc = new_mainloop_for_ipc(CRM_SYSTEM_MCP, node_mcp_dispatch); |
| |
| // Sending anything will get us a list of nodes |
| poke = create_xml_node(NULL, "poke"); |
| crm_ipc_send(ipc, poke, 0, 0, NULL); |
| free_xml(poke); |
| |
| // Handle reply via node_mcp_dispatch() |
| run_mainloop_and_exit(); |
| } |
| break; |
| } |
| } |
| |
| #if SUPPORT_HEARTBEAT |
| # include <ocf/oc_event.h> |
| # include <ocf/oc_membership.h> |
| # include <clplumbing/cl_uuid.h> |
| |
| # define UUID_LEN 16 |
| |
| static int ccm_fd = 0; |
| oc_ev_t *ccm_token = NULL; |
| static void *ccm_library = NULL; |
| void oc_ev_special(const oc_ev_t *, oc_ev_class_t, int); |
| |
| static gboolean |
| read_local_hb_uuid(void) |
| { |
| cl_uuid_t uuid; |
| char *buffer = NULL; |
| long start = 0, read_len = 0; |
| |
| FILE *input = fopen(UUID_FILE, "r"); |
| |
| if (input == NULL) { |
| crm_info("Could not open UUID file %s", UUID_FILE); |
| return FALSE; |
| } |
| |
| /* see how big the file is */ |
| start = ftell(input); |
| fseek(input, 0L, SEEK_END); |
| if (UUID_LEN != ftell(input)) { |
| fprintf(stderr, "%s must contain exactly %d bytes\n", UUID_FILE, UUID_LEN); |
| abort(); |
| } |
| |
| fseek(input, 0L, start); |
| if (start != ftell(input)) { |
| fprintf(stderr, "fseek not behaving: %ld vs. %ld\n", start, ftell(input)); |
| crm_exit(pcmk_err_generic); |
| } |
| |
| buffer = malloc(50); |
| read_len = fread(uuid.uuid, 1, UUID_LEN, input); |
| fclose(input); |
| |
| if (read_len != UUID_LEN) { |
| fprintf(stderr, "Expected and read bytes differ: %d vs. %ld\n", UUID_LEN, read_len); |
| crm_exit(pcmk_err_generic); |
| |
| } else if (buffer != NULL) { |
| cl_uuid_unparse(&uuid, buffer); |
| fprintf(stdout, "%s\n", buffer); |
| return TRUE; |
| |
| } else { |
| fprintf(stderr, "No buffer to unparse\n"); |
| crm_exit(ENODATA); |
| } |
| |
| free(buffer); |
| return FALSE; |
| } |
| |
| static void |
| ccm_age_callback(oc_ed_t event, void *cookie, size_t size, const void *data) |
| { |
| int lpc; |
| int node_list_size; |
| const oc_ev_membership_t *oc = (const oc_ev_membership_t *)data; |
| |
| int (*ccm_api_callback_done) (void *cookie) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_callback_done", 1); |
| |
| node_list_size = oc->m_n_member; |
| if (command == 'q') { |
| crm_debug("Processing \"%s\" event.", |
| event == OC_EV_MS_NEW_MEMBERSHIP ? "NEW MEMBERSHIP" : |
| event == OC_EV_MS_NOT_PRIMARY ? "NOT PRIMARY" : |
| event == OC_EV_MS_PRIMARY_RESTORED ? "PRIMARY RESTORED" : |
| event == OC_EV_MS_EVICTED ? "EVICTED" : "NO QUORUM MEMBERSHIP"); |
| if (ccm_have_quorum(event)) { |
| fprintf(stdout, "1\n"); |
| } else { |
| fprintf(stdout, "0\n"); |
| } |
| |
| } else if (command == 'e') { |
| crm_debug("Searching %d members for our birth", oc->m_n_member); |
| } |
| for (lpc = 0; lpc < node_list_size; lpc++) { |
| if (command == 'p') { |
| fprintf(stdout, "%s ", oc->m_array[oc->m_memb_idx + lpc].node_uname); |
| |
| } else if (command == 'e') { |
| int (*ccm_api_is_my_nodeid) (const oc_ev_t * token, const oc_node_t * node) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_is_my_nodeid", 1); |
| if ((*ccm_api_is_my_nodeid) (ccm_token, &(oc->m_array[lpc]))) { |
| crm_debug("MATCH: nodeid=%d, uname=%s, born=%d", |
| oc->m_array[oc->m_memb_idx + lpc].node_id, |
| oc->m_array[oc->m_memb_idx + lpc].node_uname, |
| oc->m_array[oc->m_memb_idx + lpc].node_born_on); |
| fprintf(stdout, "%d\n", oc->m_array[oc->m_memb_idx + lpc].node_born_on); |
| } |
| } |
| } |
| |
| (*ccm_api_callback_done) (cookie); |
| |
| if (command == 'p') { |
| fprintf(stdout, "\n"); |
| } |
| fflush(stdout); |
| crm_exit(pcmk_ok); |
| } |
| |
| static gboolean |
| ccm_age_connect(int *ccm_fd) |
| { |
| gboolean did_fail = FALSE; |
| int ret = 0; |
| |
| int (*ccm_api_register) (oc_ev_t ** token) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_register", 1); |
| |
| int (*ccm_api_set_callback) (const oc_ev_t * token, |
| oc_ev_class_t class, |
| oc_ev_callback_t * fn, |
| oc_ev_callback_t ** prev_fn) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_set_callback", 1); |
| |
| void (*ccm_api_special) (const oc_ev_t *, oc_ev_class_t, int) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_special", 1); |
| int (*ccm_api_activate) (const oc_ev_t * token, int *fd) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_activate", 1); |
| |
| crm_debug("Registering with CCM"); |
| ret = (*ccm_api_register) (&ccm_token); |
| if (ret != 0) { |
| crm_info("CCM registration failed: %d", ret); |
| did_fail = TRUE; |
| } |
| |
| if (did_fail == FALSE) { |
| crm_debug("Setting up CCM callbacks"); |
| ret = (*ccm_api_set_callback) (ccm_token, OC_EV_MEMB_CLASS, ccm_age_callback, NULL); |
| if (ret != 0) { |
| crm_warn("CCM callback not set: %d", ret); |
| did_fail = TRUE; |
| } |
| } |
| if (did_fail == FALSE) { |
| (*ccm_api_special) (ccm_token, OC_EV_MEMB_CLASS, 0 /*don't care */ ); |
| |
| crm_debug("Activating CCM token"); |
| ret = (*ccm_api_activate) (ccm_token, ccm_fd); |
| if (ret != 0) { |
| crm_warn("CCM Activation failed: %d", ret); |
| did_fail = TRUE; |
| } |
| } |
| |
| return !did_fail; |
| } |
| |
| static gboolean |
| try_heartbeat(int command, enum cluster_type_e stack) |
| { |
| crm_debug("Attempting to process %c command", command); |
| |
| if (command == 'i') { |
| if (read_local_hb_uuid()) { |
| crm_exit(pcmk_ok); |
| } |
| |
| } else if (command == 'R') { |
| if (tools_remove_node_cache(target_uname, CRM_SYSTEM_CRMD)) { |
| crm_err("Failed to connect to "CRM_SYSTEM_CRMD" to remove node '%s'", target_uname); |
| crm_exit(pcmk_err_generic); |
| } |
| crm_exit(pcmk_ok); |
| |
| } else if (ccm_age_connect(&ccm_fd)) { |
| int rc = 0; |
| fd_set rset; |
| int (*ccm_api_handle_event) (const oc_ev_t * token) = |
| find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_handle_event", 1); |
| |
| while (1) { |
| |
| sleep(1); |
| FD_ZERO(&rset); |
| FD_SET(ccm_fd, &rset); |
| |
| errno = 0; |
| rc = select(ccm_fd + 1, &rset, NULL, NULL, NULL); |
| |
| if (rc > 0 && (*ccm_api_handle_event) (ccm_token) != 0) { |
| crm_err("oc_ev_handle_event failed"); |
| return FALSE; |
| |
| } else if (rc < 0 && errno != EINTR) { |
| crm_perror(LOG_ERR, "select failed: %d", rc); |
| return FALSE; |
| } |
| } |
| } |
| return FALSE; |
| } |
| #endif |
| |
| #if SUPPORT_CMAN |
| # include <libcman.h> |
| # define MAX_NODES 256 |
| static bool valid_cman_name(const char *name, uint32_t nodeid) |
| { |
| bool rc = TRUE; |
| |
| /* Yes, %d, because that's what CMAN does */ |
| char *fakename = crm_strdup_printf("Node%d", nodeid); |
| |
| if(crm_str_eq(fakename, name, TRUE)) { |
| rc = FALSE; |
| crm_notice("Ignoring inferred name from cman: %s", fakename); |
| } |
| free(fakename); |
| return rc; |
| } |
| |
| static gboolean |
| try_cman(int command, enum cluster_type_e stack) |
| { |
| |
| int rc = -1, lpc = 0, node_count = 0; |
| cman_node_t node; |
| cman_cluster_t cluster; |
| cman_handle_t cman_handle = NULL; |
| cman_node_t cman_nodes[MAX_NODES]; |
| |
| memset(&cluster, 0, sizeof(cluster)); |
| |
| cman_handle = cman_init(NULL); |
| if (cman_handle == NULL || cman_is_active(cman_handle) == FALSE) { |
| crm_info("Couldn't connect to cman"); |
| return FALSE; |
| } |
| |
| switch (command) { |
| case 'R': |
| try_pacemaker(command, stack); |
| break; |
| |
| case 'e': |
| /* Age makes no sense (yet?) in a cman cluster */ |
| fprintf(stdout, "1\n"); |
| break; |
| |
| case 'q': |
| fprintf(stdout, "%d\n", cman_is_quorate(cman_handle)); |
| break; |
| |
| case 'l': |
| case 'p': |
| memset(cman_nodes, 0, MAX_NODES * sizeof(cman_node_t)); |
| rc = cman_get_nodes(cman_handle, MAX_NODES, &node_count, cman_nodes); |
| if (rc != 0) { |
| fprintf(stderr, "Couldn't query cman node list: %d %d", rc, errno); |
| goto cman_bail; |
| } |
| |
| for (lpc = 0; lpc < node_count; lpc++) { |
| if(valid_cman_name(cman_nodes[lpc].cn_name, cman_nodes[lpc].cn_nodeid) == FALSE) { |
| /* The name was invented, but we need to print something, make it the id instead */ |
| printf("%u ", cman_nodes[lpc].cn_nodeid); |
| |
| } if (command == 'l') { |
| printf("%s ", cman_nodes[lpc].cn_name); |
| |
| } else if (cman_nodes[lpc].cn_nodeid != 0 && cman_nodes[lpc].cn_member) { |
| /* Never allow node ID 0 to be considered a member #315711 */ |
| printf("%s ", cman_nodes[lpc].cn_name); |
| } |
| } |
| printf("\n"); |
| break; |
| |
| case 'i': |
| memset(&node, 0, sizeof(cman_node_t)); |
| rc = cman_get_node(cman_handle, CMAN_NODEID_US, &node); |
| if (rc != 0) { |
| fprintf(stderr, "Couldn't query cman node id: %d %d", rc, errno); |
| goto cman_bail; |
| } |
| fprintf(stdout, "%u\n", node.cn_nodeid); |
| break; |
| |
| default: |
| fprintf(stderr, "Unknown option '%c'\n", command); |
| crm_help('?', EX_USAGE); |
| } |
| cman_finish(cman_handle); |
| crm_exit(pcmk_ok); |
| |
| cman_bail: |
| cman_finish(cman_handle); |
| return crm_exit(EINVAL); |
| } |
| #endif |
| |
| #if HAVE_CONFDB |
| static void |
| ais_membership_destroy(gpointer user_data) |
| { |
| crm_err("AIS connection terminated"); |
| ais_fd_sync = -1; |
| crm_exit(ENOTCONN); |
| } |
| |
| static gint |
| member_sort(gconstpointer a, gconstpointer b) |
| { |
| const crm_node_t *node_a = a; |
| const crm_node_t *node_b = b; |
| |
| return strcmp(node_a->uname, node_b->uname); |
| } |
| |
| static void |
| crm_add_member(gpointer key, gpointer value, gpointer user_data) |
| { |
| GList **list = user_data; |
| crm_node_t *node = value; |
| |
| if (node->uname != NULL) { |
| *list = g_list_insert_sorted(*list, node, member_sort); |
| } |
| } |
| |
| static void |
| ais_membership_dispatch(cpg_handle_t handle, |
| const struct cpg_name *groupName, |
| uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) |
| { |
| uint32_t kind = 0; |
| const char *from = NULL; |
| char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); |
| |
| switch (kind) { |
| case crm_class_members: |
| case crm_class_notify: |
| case crm_class_quorum: |
| break; |
| default: |
| free(data); |
| return; |
| |
| break; |
| } |
| |
| if (command == 'q') { |
| if (crm_have_quorum) { |
| fprintf(stdout, "1\n"); |
| } else { |
| fprintf(stdout, "0\n"); |
| } |
| |
| } else if (command == 'l') { |
| GList *nodes = NULL; |
| GListPtr lpc = NULL; |
| |
| g_hash_table_foreach(crm_peer_cache, crm_add_member, &nodes); |
| for (lpc = nodes; lpc != NULL; lpc = lpc->next) { |
| crm_node_t *node = (crm_node_t *) lpc->data; |
| |
| fprintf(stdout, "%u %s %s\n", node->id, node->uname, node->state); |
| } |
| fprintf(stdout, "\n"); |
| |
| } else if (command == 'p') { |
| GList *nodes = NULL; |
| GListPtr lpc = NULL; |
| |
| g_hash_table_foreach(crm_peer_cache, crm_add_member, &nodes); |
| for (lpc = nodes; lpc != NULL; lpc = lpc->next) { |
| crm_node_t *node = (crm_node_t *) lpc->data; |
| |
| if (node->uname && safe_str_eq(node->state, CRM_NODE_MEMBER)) { |
| fprintf(stdout, "%s ", node->uname); |
| } |
| } |
| fprintf(stdout, "\n"); |
| } |
| |
| free(data); |
| crm_exit(pcmk_ok); |
| |
| return; |
| } |
| #endif |
| |
| #ifdef SUPPORT_CS_QUORUM |
| # include <corosync/quorum.h> |
| # include <corosync/cpg.h> |
| |
| static void |
| try_corosync(int command, enum cluster_type_e stack) |
| { |
| int rc = 0; |
| int quorate = 0; |
| uint32_t quorum_type = 0; |
| quorum_handle_t q_handle = 0; |
| |
| switch (command) { |
| case 'q': |
| /* Go direct to the Quorum API */ |
| rc = quorum_initialize(&q_handle, NULL, &quorum_type); |
| if (rc != CS_OK) { |
| crm_err("Could not connect to the Quorum API: %d", rc); |
| return; |
| } |
| |
| rc = quorum_getquorate(q_handle, &quorate); |
| if (rc != CS_OK) { |
| crm_err("Could not obtain the current Quorum API state: %d", rc); |
| return; |
| } |
| |
| printf("%d\n", quorate); |
| quorum_finalize(q_handle); |
| crm_node_exit(pcmk_ok); |
| break; |
| |
| case 'i': |
| run_controller_mainloop(0); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| #endif |
| |
| #if HAVE_CONFDB |
| static gboolean |
| try_openais(int command, enum cluster_type_e stack) |
| { |
| static crm_cluster_t cluster; |
| |
| cluster.destroy = ais_membership_destroy; |
| cluster.cpg.cpg_deliver_fn = ais_membership_dispatch; |
| cluster.cpg.cpg_confchg_fn = NULL; |
| |
| if (init_cs_connection_once(&cluster)) { |
| |
| GMainLoop *amainloop = NULL; |
| |
| switch (command) { |
| case 'R': |
| send_cluster_text(crm_class_rmpeer, target_uname, TRUE, NULL, crm_msg_ais); |
| cib_remove_node(0, target_uname); |
| crm_exit(pcmk_ok); |
| |
| case 'e': |
| /* Age makes no sense (yet) in an AIS cluster */ |
| fprintf(stdout, "1\n"); |
| crm_exit(pcmk_ok); |
| |
| case 'q': |
| send_cluster_text(crm_class_quorum, NULL, TRUE, NULL, crm_msg_ais); |
| break; |
| |
| case 'l': |
| case 'p': |
| crm_info("Requesting the list of configured nodes"); |
| send_cluster_text(crm_class_members, __FUNCTION__, TRUE, NULL, crm_msg_ais); |
| break; |
| |
| case 'i': |
| printf("%u\n", cluster.nodeid); |
| crm_exit(pcmk_ok); |
| |
| default: |
| fprintf(stderr, "Unknown option '%c'\n", command); |
| crm_help('?', EX_USAGE); |
| } |
| amainloop = g_main_new(FALSE); |
| g_main_run(amainloop); |
| } |
| return FALSE; |
| } |
| #endif |
| |
| int set_cluster_type(enum cluster_type_e type); |
| |
| int |
| main(int argc, char **argv) |
| { |
| int flag = 0; |
| int argerr = 0; |
| uint32_t nodeid = 0; |
| gboolean force_flag = FALSE; |
| gboolean dangerous_cmd = FALSE; |
| enum cluster_type_e try_stack = pcmk_cluster_unknown; |
| |
| int option_index = 0; |
| |
| crm_peer_init(); |
| crm_log_cli_init("crm_node"); |
| crm_set_options(NULL, "command [options]", long_options, |
| "Tool for displaying low-level node information"); |
| |
| while (flag >= 0) { |
| flag = crm_get_option(argc, argv, &option_index); |
| switch (flag) { |
| case -1: |
| break; |
| case 'V': |
| crm_bump_log_level(argc, argv); |
| break; |
| case '$': |
| case '?': |
| return crm_help(flag, EX_OK); |
| break; |
| case 'Q': |
| // currently unused |
| break; |
| case 'H': |
| set_cluster_type(pcmk_cluster_heartbeat); |
| break; |
| case 'A': |
| set_cluster_type(pcmk_cluster_classic_ais); |
| break; |
| case 'C': |
| set_cluster_type(pcmk_cluster_corosync); |
| break; |
| case 'c': |
| set_cluster_type(pcmk_cluster_cman); |
| break; |
| case 'f': |
| force_flag = TRUE; |
| break; |
| case 'R': |
| command = flag; |
| dangerous_cmd = TRUE; |
| target_uname = optarg; |
| if (optarg == NULL) { |
| ++argerr; |
| } |
| break; |
| case 'N': |
| command = flag; |
| nodeid = crm_parse_int(optarg, NULL); |
| break; |
| case 'p': |
| case 'e': |
| case 'q': |
| case 'i': |
| case 'l': |
| case 'n': |
| command = flag; |
| break; |
| default: |
| ++argerr; |
| break; |
| } |
| } |
| |
| if (optind > argc) { |
| ++argerr; |
| } |
| |
| if (argerr) { |
| return crm_help('?', EX_USAGE); |
| } |
| |
| if (dangerous_cmd && force_flag == FALSE) { |
| fprintf(stderr, "The supplied command is considered dangerous." |
| " To prevent accidental destruction of the cluster," |
| " the --force flag is required in order to proceed.\n"); |
| crm_node_exit(EINVAL); |
| } |
| |
| if (command == 'n') { |
| print_node_name(); |
| |
| } else if (command == 'N') { |
| run_controller_mainloop(nodeid); |
| } |
| |
| try_stack = get_cluster_type(); |
| crm_debug("Attempting to process -%c command for cluster type: %s", command, |
| name_for_cluster_type(try_stack)); |
| |
| #if SUPPORT_CMAN |
| if (try_stack == pcmk_cluster_cman) { |
| try_cman(command, try_stack); |
| } |
| #endif |
| |
| #ifdef SUPPORT_CS_QUORUM |
| if (try_stack == pcmk_cluster_corosync) { |
| try_corosync(command, try_stack); |
| } |
| if ((try_stack == pcmk_cluster_unknown) && (command == 'i')) { |
| // possibly a Pacemaker Remote node |
| run_controller_mainloop(0); |
| } |
| #endif |
| |
| #if HAVE_CONFDB |
| /* Only an option if we're using the plugins */ |
| if (try_stack == pcmk_cluster_classic_ais) { |
| try_openais(command, try_stack); |
| } |
| #endif |
| |
| #if SUPPORT_HEARTBEAT |
| if (try_stack == pcmk_cluster_heartbeat) { |
| try_heartbeat(command, try_stack); |
| } |
| #endif |
| |
| try_pacemaker(command, try_stack); |
| |
| // We only get here if command hasn't been handled |
| crm_node_exit(1); |
| } |