diff --git a/daemons/controld/controld_messages.c b/daemons/controld/controld_messages.c index 6dec0eda4b..6a48dc35bb 100644 --- a/daemons/controld/controld_messages.c +++ b/daemons/controld/controld_messages.c @@ -1,1302 +1,1336 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include extern void crm_shutdown(int nsig); static enum crmd_fsa_input handle_message(xmlNode *msg, enum crmd_fsa_cause cause); static void handle_response(xmlNode *stored_msg); static enum crmd_fsa_input handle_request(xmlNode *stored_msg, enum crmd_fsa_cause cause); static enum crmd_fsa_input handle_shutdown_request(xmlNode *stored_msg); static void send_msg_via_ipc(xmlNode * msg, const char *sys); /* debug only, can wrap all it likes */ static int last_data_id = 0; void register_fsa_error_adv(enum crmd_fsa_cause cause, enum crmd_fsa_input input, fsa_data_t * cur_data, void *new_data, const char *raised_from) { /* save the current actions if any */ if (controld_globals.fsa_actions != A_NOTHING) { register_fsa_input_adv(cur_data ? cur_data->fsa_cause : C_FSA_INTERNAL, I_NULL, cur_data ? cur_data->data : NULL, controld_globals.fsa_actions, TRUE, __func__); } /* reset the action list */ crm_info("Resetting the current action list"); fsa_dump_actions(controld_globals.fsa_actions, "Drop"); controld_globals.fsa_actions = A_NOTHING; /* register the error */ register_fsa_input_adv(cause, input, new_data, A_NOTHING, TRUE, raised_from); } void register_fsa_input_adv(enum crmd_fsa_cause cause, enum crmd_fsa_input input, void *data, uint64_t with_actions, gboolean prepend, const char *raised_from) { unsigned old_len = g_list_length(controld_globals.fsa_message_queue); fsa_data_t *fsa_data = NULL; if (raised_from == NULL) { raised_from = ""; } if (input == I_NULL && with_actions == A_NOTHING /* && data == NULL */ ) { /* no point doing anything */ crm_err("Cannot add entry to queue: no input and no action"); return; } if (input == I_WAIT_FOR_EVENT) { controld_set_global_flags(controld_fsa_is_stalled); crm_debug("Stalling the FSA pending further input: source=%s cause=%s data=%p queue=%d", raised_from, fsa_cause2string(cause), data, old_len); if (old_len > 0) { fsa_dump_queue(LOG_TRACE); prepend = FALSE; } if (data == NULL) { controld_set_fsa_action_flags(with_actions); fsa_dump_actions(with_actions, "Restored"); return; } /* Store everything in the new event and reset * controld_globals.fsa_actions */ with_actions |= controld_globals.fsa_actions; controld_globals.fsa_actions = A_NOTHING; } last_data_id++; crm_trace("%s %s FSA input %d (%s) due to %s, %s data", raised_from, (prepend? "prepended" : "appended"), last_data_id, fsa_input2string(input), fsa_cause2string(cause), (data? "with" : "without")); fsa_data = calloc(1, sizeof(fsa_data_t)); fsa_data->id = last_data_id; fsa_data->fsa_input = input; fsa_data->fsa_cause = cause; fsa_data->origin = raised_from; fsa_data->data = NULL; fsa_data->data_type = fsa_dt_none; fsa_data->actions = with_actions; if (with_actions != A_NOTHING) { crm_trace("Adding actions %.16llx to input", (unsigned long long) with_actions); } if (data != NULL) { switch (cause) { case C_FSA_INTERNAL: case C_CRMD_STATUS_CALLBACK: case C_IPC_MESSAGE: case C_HA_MESSAGE: CRM_CHECK(((ha_msg_input_t *) data)->msg != NULL, crm_err("Bogus data from %s", raised_from)); crm_trace("Copying %s data from %s as cluster message data", fsa_cause2string(cause), raised_from); fsa_data->data = copy_ha_msg_input(data); fsa_data->data_type = fsa_dt_ha_msg; break; case C_LRM_OP_CALLBACK: crm_trace("Copying %s data from %s as lrmd_event_data_t", fsa_cause2string(cause), raised_from); fsa_data->data = lrmd_copy_event((lrmd_event_data_t *) data); fsa_data->data_type = fsa_dt_lrm; break; case C_TIMER_POPPED: case C_SHUTDOWN: case C_UNKNOWN: case C_STARTUP: crm_crit("Copying %s data (from %s) is not yet implemented", fsa_cause2string(cause), raised_from); crmd_exit(CRM_EX_SOFTWARE); break; } } /* make sure to free it properly later */ if (prepend) { controld_globals.fsa_message_queue = g_list_prepend(controld_globals.fsa_message_queue, fsa_data); } else { controld_globals.fsa_message_queue = g_list_append(controld_globals.fsa_message_queue, fsa_data); } crm_trace("FSA message queue length is %d", g_list_length(controld_globals.fsa_message_queue)); /* fsa_dump_queue(LOG_TRACE); */ if (old_len == g_list_length(controld_globals.fsa_message_queue)) { crm_err("Couldn't add message to the queue"); } if (input != I_WAIT_FOR_EVENT) { controld_trigger_fsa(); } } void fsa_dump_queue(int log_level) { int offset = 0; for (GList *iter = controld_globals.fsa_message_queue; iter != NULL; iter = iter->next) { fsa_data_t *data = (fsa_data_t *) iter->data; do_crm_log_unlikely(log_level, "queue[%d.%d]: input %s raised by %s(%p.%d)\t(cause=%s)", offset++, data->id, fsa_input2string(data->fsa_input), data->origin, data->data, data->data_type, fsa_cause2string(data->fsa_cause)); } } ha_msg_input_t * copy_ha_msg_input(ha_msg_input_t * orig) { ha_msg_input_t *copy = calloc(1, sizeof(ha_msg_input_t)); CRM_ASSERT(copy != NULL); copy->msg = (orig && orig->msg)? copy_xml(orig->msg) : NULL; copy->xml = get_message_xml(copy->msg, F_CRM_DATA); return copy; } void delete_fsa_input(fsa_data_t * fsa_data) { lrmd_event_data_t *op = NULL; xmlNode *foo = NULL; if (fsa_data == NULL) { return; } crm_trace("About to free %s data", fsa_cause2string(fsa_data->fsa_cause)); if (fsa_data->data != NULL) { switch (fsa_data->data_type) { case fsa_dt_ha_msg: delete_ha_msg_input(fsa_data->data); break; case fsa_dt_xml: foo = fsa_data->data; free_xml(foo); break; case fsa_dt_lrm: op = (lrmd_event_data_t *) fsa_data->data; lrmd_free_event(op); break; case fsa_dt_none: if (fsa_data->data != NULL) { crm_err("Don't know how to free %s data from %s", fsa_cause2string(fsa_data->fsa_cause), fsa_data->origin); crmd_exit(CRM_EX_SOFTWARE); } break; } crm_trace("%s data freed", fsa_cause2string(fsa_data->fsa_cause)); } free(fsa_data); } /* returns the next message */ fsa_data_t * get_message(void) { fsa_data_t *message = (fsa_data_t *) controld_globals.fsa_message_queue->data; controld_globals.fsa_message_queue = g_list_remove(controld_globals.fsa_message_queue, message); crm_trace("Processing input %d", message->id); return message; } void * fsa_typed_data_adv(fsa_data_t * fsa_data, enum fsa_data_type a_type, const char *caller) { void *ret_val = NULL; if (fsa_data == NULL) { crm_err("%s: No FSA data available", caller); } else if (fsa_data->data == NULL) { crm_err("%s: No message data available. Origin: %s", caller, fsa_data->origin); } else if (fsa_data->data_type != a_type) { crm_crit("%s: Message data was the wrong type! %d vs. requested=%d. Origin: %s", caller, fsa_data->data_type, a_type, fsa_data->origin); CRM_ASSERT(fsa_data->data_type == a_type); } else { ret_val = fsa_data->data; } return ret_val; } /* A_MSG_ROUTE */ void do_msg_route(long long action, enum crmd_fsa_cause cause, enum crmd_fsa_state cur_state, enum crmd_fsa_input current_input, fsa_data_t * msg_data) { ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg); route_message(msg_data->fsa_cause, input->msg); } void route_message(enum crmd_fsa_cause cause, xmlNode * input) { ha_msg_input_t fsa_input; enum crmd_fsa_input result = I_NULL; fsa_input.msg = input; CRM_CHECK(cause == C_IPC_MESSAGE || cause == C_HA_MESSAGE, return); /* try passing the buck first */ if (relay_message(input, cause == C_IPC_MESSAGE)) { return; } /* handle locally */ result = handle_message(input, cause); /* done or process later? */ switch (result) { case I_NULL: case I_CIB_OP: case I_ROUTER: case I_NODE_JOIN: case I_JOIN_REQUEST: case I_JOIN_RESULT: break; default: /* Defering local processing of message */ register_fsa_input_later(cause, result, &fsa_input); return; } if (result != I_NULL) { /* add to the front of the queue */ register_fsa_input(cause, result, &fsa_input); } } gboolean relay_message(xmlNode * msg, gboolean originated_locally) { - int dest = 1; + enum crm_ais_msg_types dest = crm_msg_ais; bool is_for_dc = false; bool is_for_dcib = false; bool is_for_te = false; bool is_for_crm = false; bool is_for_cib = false; bool is_local = false; - const char *host_to = crm_element_value(msg, F_CRM_HOST_TO); - const char *sys_to = crm_element_value(msg, F_CRM_SYS_TO); - const char *sys_from = crm_element_value(msg, F_CRM_SYS_FROM); - const char *type = crm_element_value(msg, F_TYPE); - const char *task = crm_element_value(msg, F_CRM_TASK); - const char *ref = crm_element_value(msg, XML_ATTR_REFERENCE); + bool broadcast = false; + const char *host_to = NULL; + const char *sys_to = NULL; + const char *sys_from = NULL; + const char *type = NULL; + const char *task = NULL; + const char *ref = NULL; + crm_node_t *node_to = NULL; + + CRM_CHECK(msg != NULL, return TRUE); + + host_to = crm_element_value(msg, F_CRM_HOST_TO); + sys_to = crm_element_value(msg, F_CRM_SYS_TO); + sys_from = crm_element_value(msg, F_CRM_SYS_FROM); + type = crm_element_value(msg, F_TYPE); + task = crm_element_value(msg, F_CRM_TASK); + ref = crm_element_value(msg, XML_ATTR_REFERENCE); + + broadcast = pcmk__str_empty(host_to); if (ref == NULL) { ref = "without reference ID"; } - if (msg == NULL) { - crm_warn("Cannot route empty message"); - return TRUE; - - } else if (pcmk__str_eq(task, CRM_OP_HELLO, pcmk__str_casei)) { - crm_trace("No routing needed for hello message %s", ref); + if (pcmk__str_eq(task, CRM_OP_HELLO, pcmk__str_casei)) { + crm_trace("Received hello %s from %s (no processing needed)", + ref, pcmk__s(sys_from, "unidentified source")); + crm_log_xml_trace(msg, "hello"); return TRUE; + } - } else if (!pcmk__str_eq(type, T_CRM, pcmk__str_casei)) { - crm_warn("Received invalid message %s: type '%s' not '" T_CRM "'", + // Require message type (set by create_request()) + if (!pcmk__str_eq(type, T_CRM, pcmk__str_casei)) { + crm_warn("Ignoring invalid message %s with type '%s' (not '" T_CRM "')", ref, pcmk__s(type, "")); - crm_log_xml_warn(msg, "[bad message type]"); + crm_log_xml_trace(msg, "ignored"); return TRUE; + } - } else if (sys_to == NULL) { - crm_warn("Received invalid message %s: no subsystem", ref); - crm_log_xml_warn(msg, "[no subsystem]"); + // Require a destination subsystem (also set by create_request()) + if (sys_to == NULL) { + crm_warn("Ignoring invalid message %s with no " F_CRM_SYS_TO, ref); + crm_log_xml_trace(msg, "ignored"); return TRUE; } + // Get the message type appropriate to the destination subsystem + if (is_corosync_cluster()) { + dest = text2msg_type(sys_to); + if ((dest < crm_msg_ais) || (dest > crm_msg_stonith_ng)) { + /* Unrecognized value, use a sane default + * + * @TODO Maybe we should bail instead + */ + dest = crm_msg_crmd; + } + } + is_for_dc = (strcasecmp(CRM_SYSTEM_DC, sys_to) == 0); is_for_dcib = (strcasecmp(CRM_SYSTEM_DCIB, sys_to) == 0); is_for_te = (strcasecmp(CRM_SYSTEM_TENGINE, sys_to) == 0); is_for_cib = (strcasecmp(CRM_SYSTEM_CIB, sys_to) == 0); is_for_crm = (strcasecmp(CRM_SYSTEM_CRMD, sys_to) == 0); + // Check whether message should be processed locally is_local = false; - if (pcmk__str_empty(host_to)) { + if (broadcast) { if (is_for_dc || is_for_te) { is_local = false; } else if (is_for_crm) { if (pcmk__strcase_any_of(task, CRM_OP_NODE_INFO, PCMK__CONTROLD_CMD_NODES, NULL)) { /* Node info requests do not specify a host, which is normally * treated as "all hosts", because the whole point is that the * client may not know the local node name. Always handle these * requests locally. */ is_local = true; } else { is_local = !originated_locally; } } else { is_local = true; } } else if (pcmk__str_eq(controld_globals.our_nodename, host_to, pcmk__str_casei)) { is_local = true; + } else if (is_for_crm && pcmk__str_eq(task, CRM_OP_LRM_DELETE, pcmk__str_casei)) { xmlNode *msg_data = get_message_xml(msg, F_CRM_DATA); const char *mode = crm_element_value(msg_data, PCMK__XA_MODE); if (pcmk__str_eq(mode, XML_TAG_CIB, pcmk__str_casei)) { // Local delete of an offline node's resource history is_local = true; } } - if (is_for_dc || is_for_dcib || is_for_te) { - if (AM_I_DC && is_for_te) { - crm_trace("Route message %s locally as transition request", ref); - send_msg_via_ipc(msg, sys_to); + // Check whether message should be relayed - } else if (AM_I_DC) { + if (is_for_dc || is_for_dcib || is_for_te) { + if (AM_I_DC) { + if (is_for_te) { + crm_trace("Route message %s locally as transition request", + ref); + crm_log_xml_trace(msg, sys_to); + send_msg_via_ipc(msg, sys_to); + return TRUE; // No further processing of message is needed + } crm_trace("Route message %s locally as DC request", ref); return FALSE; // More to be done by caller + } - } else if (originated_locally && !pcmk__strcase_any_of(sys_from, CRM_SYSTEM_PENGINE, - CRM_SYSTEM_TENGINE, NULL)) { - - if (is_corosync_cluster()) { - dest = text2msg_type(sys_to); + if (originated_locally + && !pcmk__strcase_any_of(sys_from, CRM_SYSTEM_PENGINE, + CRM_SYSTEM_TENGINE, NULL)) { + crm_trace("Relay message %s to DC (via %s)", + ref, pcmk__s(host_to, "broadcast")); + crm_log_xml_trace(msg, "relayed"); + if (!broadcast) { + node_to = crm_get_peer(0, host_to); } - crm_trace("Relay message %s to DC", ref); - send_cluster_message(host_to ? crm_get_peer(0, host_to) : NULL, dest, msg, TRUE); - - } else { - /* Neither the TE nor the scheduler should be sending messages - * to DCs on other nodes. By definition, if we are no longer the DC, - * then the scheduler's or TE's data should be discarded. - */ - crm_trace("Discard message %s because we are not DC", ref); + send_cluster_message(node_to, dest, msg, TRUE); + return TRUE; } - } else if (is_local && (is_for_crm || is_for_cib)) { - crm_trace("Route message %s locally as controller request", ref); - return FALSE; // More to be done by caller - - } else if (is_local) { - crm_trace("Relay message %s locally to %s", - ref, (sys_to? sys_to : "unknown client")); - crm_log_xml_trace(msg, "[IPC relay]"); - send_msg_via_ipc(msg, sys_to); - - } else { - crm_node_t *node_to = NULL; - - if (is_corosync_cluster()) { - dest = text2msg_type(sys_to); + /* Transition engine and scheduler messages are sent only to the DC on + * the same node. If we are no longer the DC, discard this message. + */ + crm_trace("Ignoring message %s because we are no longer DC", ref); + crm_log_xml_trace(msg, "ignored"); + return TRUE; // No further processing of message is needed + } - if (dest == crm_msg_none || dest > crm_msg_stonith_ng) { - dest = crm_msg_crmd; - } + if (is_local) { + if (is_for_crm || is_for_cib) { + crm_trace("Route message %s locally as controller request", ref); + return FALSE; // More to be done by caller } + crm_trace("Relay message %s locally to %s", ref, sys_to); + crm_log_xml_trace(msg, "IPC-relay"); + send_msg_via_ipc(msg, sys_to); + return TRUE; + } - if (host_to) { - node_to = pcmk__search_cluster_node_cache(0, host_to, NULL); - if (node_to == NULL) { - crm_warn("Cannot route message %s: Unknown node %s", - ref, host_to); - return TRUE; - } - crm_trace("Relay message %s to %s", - ref, (node_to->uname? node_to->uname : "peer")); - } else { - crm_trace("Broadcast message %s to all peers", ref); + if (!broadcast) { + node_to = pcmk__search_cluster_node_cache(0, host_to, NULL); + if (node_to == NULL) { + crm_warn("Ignoring message %s because node %s is unknown", + ref, host_to); + crm_log_xml_trace(msg, "ignored"); + return TRUE; } - send_cluster_message(host_to ? node_to : NULL, dest, msg, TRUE); } - return TRUE; // No further processing of message is needed + crm_trace("Relay message %s to %s", + ref, pcmk__s(host_to, "all peers")); + crm_log_xml_trace(msg, "relayed"); + send_cluster_message(node_to, dest, msg, TRUE); + return TRUE; } // Return true if field contains a positive integer static bool authorize_version(xmlNode *message_data, const char *field, const char *client_name, const char *ref, const char *uuid) { const char *version = crm_element_value(message_data, field); long long version_num; if ((pcmk__scan_ll(version, &version_num, -1LL) != pcmk_rc_ok) || (version_num < 0LL)) { crm_warn("Rejected IPC hello from %s: '%s' is not a valid protocol %s " CRM_XS " ref=%s uuid=%s", client_name, ((version == NULL)? "" : version), field, (ref? ref : "none"), uuid); return false; } return true; } /*! * \internal * \brief Check whether a client IPC message is acceptable * * If a given client IPC message is a hello, "authorize" it by ensuring it has * valid information such as a protocol version, and return false indicating * that nothing further needs to be done with the message. If the message is not * a hello, just return true to indicate it needs further processing. * * \param[in] client_msg XML of IPC message * \param[in,out] curr_client If IPC is not proxied, client that sent message * \param[in] proxy_session If IPC is proxied, the session ID * * \return true if message needs further processing, false if it doesn't */ bool controld_authorize_ipc_message(const xmlNode *client_msg, pcmk__client_t *curr_client, const char *proxy_session) { xmlNode *message_data = NULL; const char *client_name = NULL; const char *op = crm_element_value(client_msg, F_CRM_TASK); const char *ref = crm_element_value(client_msg, XML_ATTR_REFERENCE); const char *uuid = (curr_client? curr_client->id : proxy_session); if (uuid == NULL) { crm_warn("IPC message from client rejected: No client identifier " CRM_XS " ref=%s", (ref? ref : "none")); goto rejected; } if (!pcmk__str_eq(CRM_OP_HELLO, op, pcmk__str_casei)) { // Only hello messages need to be authorized return true; } message_data = get_message_xml(client_msg, F_CRM_DATA); client_name = crm_element_value(message_data, "client_name"); if (pcmk__str_empty(client_name)) { crm_warn("IPC hello from client rejected: No client name", CRM_XS " ref=%s uuid=%s", (ref? ref : "none"), uuid); goto rejected; } if (!authorize_version(message_data, "major_version", client_name, ref, uuid)) { goto rejected; } if (!authorize_version(message_data, "minor_version", client_name, ref, uuid)) { goto rejected; } crm_trace("Validated IPC hello from client %s", client_name); + crm_log_xml_trace(client_msg, "hello"); if (curr_client) { curr_client->userdata = strdup(client_name); } controld_trigger_fsa(); return false; rejected: + crm_log_xml_trace(client_msg, "rejected"); if (curr_client) { qb_ipcs_disconnect(curr_client->ipcs); } return false; } static enum crmd_fsa_input handle_message(xmlNode *msg, enum crmd_fsa_cause cause) { const char *type = NULL; CRM_CHECK(msg != NULL, return I_NULL); type = crm_element_value(msg, F_CRM_MSG_TYPE); if (pcmk__str_eq(type, XML_ATTR_REQUEST, pcmk__str_none)) { return handle_request(msg, cause); } else if (pcmk__str_eq(type, XML_ATTR_RESPONSE, pcmk__str_none)) { handle_response(msg); return I_NULL; } - crm_err("Unknown message type: %s", type); + crm_warn("Ignoring message with unknown " F_CRM_MSG_TYPE " '%s'", + pcmk__s(type, "")); + crm_log_xml_trace(msg, "bad"); return I_NULL; } static enum crmd_fsa_input handle_failcount_op(xmlNode * stored_msg) { const char *rsc = NULL; const char *uname = NULL; const char *op = NULL; char *interval_spec = NULL; guint interval_ms = 0; gboolean is_remote_node = FALSE; xmlNode *xml_op = get_message_xml(stored_msg, F_CRM_DATA); if (xml_op) { xmlNode *xml_rsc = first_named_child(xml_op, XML_CIB_TAG_RESOURCE); xmlNode *xml_attrs = first_named_child(xml_op, XML_TAG_ATTRS); if (xml_rsc) { rsc = ID(xml_rsc); } if (xml_attrs) { op = crm_element_value(xml_attrs, CRM_META "_" XML_RSC_ATTR_CLEAR_OP); crm_element_value_ms(xml_attrs, CRM_META "_" XML_RSC_ATTR_CLEAR_INTERVAL, &interval_ms); } } uname = crm_element_value(xml_op, XML_LRM_ATTR_TARGET); if ((rsc == NULL) || (uname == NULL)) { crm_log_xml_warn(stored_msg, "invalid failcount op"); return I_NULL; } if (crm_element_value(xml_op, XML_LRM_ATTR_ROUTER_NODE)) { is_remote_node = TRUE; } crm_debug("Clearing failures for %s-interval %s on %s " "from attribute manager, CIB, and executor state", pcmk__readable_interval(interval_ms), rsc, uname); if (interval_ms) { interval_spec = crm_strdup_printf("%ums", interval_ms); } update_attrd_clear_failures(uname, rsc, op, interval_spec, is_remote_node); free(interval_spec); controld_cib_delete_last_failure(rsc, uname, op, interval_ms); lrm_clear_last_failure(rsc, uname, op, interval_ms); return I_NULL; } static enum crmd_fsa_input handle_lrm_delete(xmlNode *stored_msg) { const char *mode = NULL; xmlNode *msg_data = get_message_xml(stored_msg, F_CRM_DATA); CRM_CHECK(msg_data != NULL, return I_NULL); /* CRM_OP_LRM_DELETE has two distinct modes. The default behavior is to * relay the operation to the affected node, which will unregister the * resource from the local executor, clear the resource's history from the * CIB, and do some bookkeeping in the controller. * * However, if the affected node is offline, the client will specify * mode="cib" which means the controller receiving the operation should * clear the resource's history from the CIB and nothing else. This is used * to clear shutdown locks. */ mode = crm_element_value(msg_data, PCMK__XA_MODE); if ((mode == NULL) || strcmp(mode, XML_TAG_CIB)) { // Relay to affected node crm_xml_add(stored_msg, F_CRM_SYS_TO, CRM_SYSTEM_LRMD); return I_ROUTER; } else { // Delete CIB history locally (compare with do_lrm_delete()) const char *from_sys = NULL; const char *user_name = NULL; const char *rsc_id = NULL; const char *node = NULL; xmlNode *rsc_xml = NULL; int rc = pcmk_rc_ok; rsc_xml = first_named_child(msg_data, XML_CIB_TAG_RESOURCE); CRM_CHECK(rsc_xml != NULL, return I_NULL); rsc_id = ID(rsc_xml); from_sys = crm_element_value(stored_msg, F_CRM_SYS_FROM); node = crm_element_value(msg_data, XML_LRM_ATTR_TARGET); user_name = pcmk__update_acl_user(stored_msg, F_CRM_USER, NULL); crm_debug("Handling " CRM_OP_LRM_DELETE " for %s on %s locally%s%s " "(clearing CIB resource history only)", rsc_id, node, (user_name? " for user " : ""), (user_name? user_name : "")); rc = controld_delete_resource_history(rsc_id, node, user_name, cib_dryrun|cib_sync_call); if (rc == pcmk_rc_ok) { rc = controld_delete_resource_history(rsc_id, node, user_name, crmd_cib_smart_opt()); } //Notify client and tengine.(Only notify tengine if mode = "cib" and CRM_OP_LRM_DELETE.) if (from_sys) { lrmd_event_data_t *op = NULL; const char *from_host = crm_element_value(stored_msg, F_CRM_HOST_FROM); const char *transition; if (strcmp(from_sys, CRM_SYSTEM_TENGINE)) { transition = crm_element_value(msg_data, XML_ATTR_TRANSITION_KEY); } else { transition = crm_element_value(stored_msg, XML_ATTR_TRANSITION_KEY); } crm_info("Notifying %s on %s that %s was%s deleted", from_sys, (from_host? from_host : "local node"), rsc_id, ((rc == pcmk_rc_ok)? "" : " not")); op = lrmd_new_event(rsc_id, PCMK_ACTION_DELETE, 0); op->type = lrmd_event_exec_complete; op->user_data = strdup(transition? transition : FAKE_TE_ID); op->params = pcmk__strkey_table(free, free); g_hash_table_insert(op->params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET)); controld_rc2event(op, rc); controld_ack_event_directly(from_host, from_sys, NULL, op, rsc_id); lrmd_free_event(op); controld_trigger_delete_refresh(from_sys, rsc_id); } return I_NULL; } } /*! * \brief Handle a CRM_OP_REMOTE_STATE message by updating remote peer cache * * \param[in] msg Message XML * * \return Next FSA input */ static enum crmd_fsa_input handle_remote_state(const xmlNode *msg) { const char *conn_host = NULL; const char *remote_uname = ID(msg); crm_node_t *remote_peer; bool remote_is_up = false; int rc = pcmk_rc_ok; rc = pcmk__xe_get_bool_attr(msg, XML_NODE_IN_CLUSTER, &remote_is_up); CRM_CHECK(remote_uname && rc == pcmk_rc_ok, return I_NULL); remote_peer = crm_remote_peer_get(remote_uname); CRM_CHECK(remote_peer, return I_NULL); pcmk__update_peer_state(__func__, remote_peer, remote_is_up ? CRM_NODE_MEMBER : CRM_NODE_LOST, 0); conn_host = crm_element_value(msg, PCMK__XA_CONN_HOST); if (conn_host) { pcmk__str_update(&remote_peer->conn_host, conn_host); } else if (remote_peer->conn_host) { free(remote_peer->conn_host); remote_peer->conn_host = NULL; } return I_NULL; } /*! * \brief Handle a CRM_OP_PING message * * \param[in] msg Message XML * * \return Next FSA input */ static enum crmd_fsa_input handle_ping(const xmlNode *msg) { const char *value = NULL; xmlNode *ping = NULL; xmlNode *reply = NULL; // Build reply ping = create_xml_node(NULL, XML_CRM_TAG_PING); value = crm_element_value(msg, F_CRM_SYS_TO); crm_xml_add(ping, XML_PING_ATTR_SYSFROM, value); // Add controller state value = fsa_state2string(controld_globals.fsa_state); crm_xml_add(ping, XML_PING_ATTR_CRMDSTATE, value); crm_notice("Current ping state: %s", value); // CTS needs this // Add controller health // @TODO maybe do some checks to determine meaningful status crm_xml_add(ping, XML_PING_ATTR_STATUS, "ok"); // Send reply reply = create_reply(msg, ping); free_xml(ping); if (reply != NULL) { (void) relay_message(reply, TRUE); free_xml(reply); } // Nothing further to do return I_NULL; } /*! * \brief Handle a PCMK__CONTROLD_CMD_NODES message * * \param[in] request Message XML * * \return Next FSA input */ static enum crmd_fsa_input handle_node_list(const xmlNode *request) { GHashTableIter iter; crm_node_t *node = NULL; xmlNode *reply = NULL; xmlNode *reply_data = NULL; // Create message data for reply reply_data = create_xml_node(NULL, XML_CIB_TAG_NODES); g_hash_table_iter_init(&iter, crm_peer_cache); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) { xmlNode *xml = create_xml_node(reply_data, XML_CIB_TAG_NODE); crm_xml_add_ll(xml, XML_ATTR_ID, (long long) node->id); // uint32_t crm_xml_add(xml, XML_ATTR_UNAME, node->uname); crm_xml_add(xml, XML_NODE_IN_CLUSTER, node->state); } // Create and send reply reply = create_reply(request, reply_data); free_xml(reply_data); if (reply) { (void) relay_message(reply, TRUE); free_xml(reply); } // Nothing further to do return I_NULL; } /*! * \brief Handle a CRM_OP_NODE_INFO request * * \param[in] msg Message XML * * \return Next FSA input */ static enum crmd_fsa_input handle_node_info_request(const xmlNode *msg) { const char *value = NULL; crm_node_t *node = NULL; int node_id = 0; xmlNode *reply = NULL; xmlNode *reply_data = NULL; // Build reply reply_data = create_xml_node(NULL, XML_CIB_TAG_NODE); crm_xml_add(reply_data, XML_PING_ATTR_SYSFROM, CRM_SYSTEM_CRMD); // Add whether current partition has quorum pcmk__xe_set_bool_attr(reply_data, XML_ATTR_HAVE_QUORUM, pcmk_is_set(controld_globals.flags, controld_has_quorum)); // Check whether client requested node info by ID and/or name crm_element_value_int(msg, XML_ATTR_ID, &node_id); if (node_id < 0) { node_id = 0; } value = crm_element_value(msg, XML_ATTR_UNAME); // Default to local node if none given if ((node_id == 0) && (value == NULL)) { value = controld_globals.our_nodename; } node = pcmk__search_node_caches(node_id, value, CRM_GET_PEER_ANY); if (node) { crm_xml_add(reply_data, XML_ATTR_ID, node->uuid); crm_xml_add(reply_data, XML_ATTR_UNAME, node->uname); crm_xml_add(reply_data, XML_NODE_IS_PEER, node->state); pcmk__xe_set_bool_attr(reply_data, XML_NODE_IS_REMOTE, pcmk_is_set(node->flags, crm_remote_node)); } // Send reply reply = create_reply(msg, reply_data); free_xml(reply_data); if (reply != NULL) { (void) relay_message(reply, TRUE); free_xml(reply); } // Nothing further to do return I_NULL; } static void verify_feature_set(xmlNode *msg) { const char *dc_version = crm_element_value(msg, XML_ATTR_CRM_VERSION); if (dc_version == NULL) { /* All we really know is that the DC feature set is older than 3.1.0, * but that's also all that really matters. */ dc_version = "3.0.14"; } if (feature_set_compatible(dc_version, CRM_FEATURE_SET)) { crm_trace("Local feature set (%s) is compatible with DC's (%s)", CRM_FEATURE_SET, dc_version); } else { crm_err("Local feature set (%s) is incompatible with DC's (%s)", CRM_FEATURE_SET, dc_version); // Nothing is likely to improve without administrator involvement controld_set_fsa_input_flags(R_STAYDOWN); crmd_exit(CRM_EX_FATAL); } } // DC gets own shutdown all-clear static enum crmd_fsa_input handle_shutdown_self_ack(xmlNode *stored_msg) { const char *host_from = crm_element_value(stored_msg, F_CRM_HOST_FROM); if (pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) { // The expected case -- we initiated own shutdown sequence crm_info("Shutting down controller"); return I_STOP; } if (pcmk__str_eq(host_from, controld_globals.dc_name, pcmk__str_casei)) { // Must be logic error -- DC confirming its own unrequested shutdown crm_err("Shutting down controller immediately due to " "unexpected shutdown confirmation"); return I_TERMINATE; } if (controld_globals.fsa_state != S_STOPPING) { // Shouldn't happen -- non-DC confirming unrequested shutdown crm_err("Starting new DC election because %s is " "confirming shutdown we did not request", (host_from? host_from : "another node")); return I_ELECTION; } // Shouldn't happen, but we are already stopping anyway crm_debug("Ignoring unexpected shutdown confirmation from %s", (host_from? host_from : "another node")); return I_NULL; } // Non-DC gets shutdown all-clear from DC static enum crmd_fsa_input handle_shutdown_ack(xmlNode *stored_msg) { const char *host_from = crm_element_value(stored_msg, F_CRM_HOST_FROM); if (host_from == NULL) { crm_warn("Ignoring shutdown request without origin specified"); return I_NULL; } if (pcmk__str_eq(host_from, controld_globals.dc_name, pcmk__str_null_matches|pcmk__str_casei)) { if (pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) { crm_info("Shutting down controller after confirmation from %s", host_from); } else { crm_err("Shutting down controller after unexpected " "shutdown request from %s", host_from); controld_set_fsa_input_flags(R_STAYDOWN); } return I_STOP; } crm_warn("Ignoring shutdown request from %s because DC is %s", host_from, controld_globals.dc_name); return I_NULL; } static enum crmd_fsa_input handle_request(xmlNode *stored_msg, enum crmd_fsa_cause cause) { xmlNode *msg = NULL; const char *op = crm_element_value(stored_msg, F_CRM_TASK); /* Optimize this for the DC - it has the most to do */ + crm_log_xml_trace(stored_msg, "request"); if (op == NULL) { - crm_log_xml_warn(stored_msg, "[request without " F_CRM_TASK "]"); + crm_warn("Ignoring request without " F_CRM_TASK); return I_NULL; } if (strcmp(op, CRM_OP_SHUTDOWN_REQ) == 0) { const char *from = crm_element_value(stored_msg, F_CRM_HOST_FROM); crm_node_t *node = pcmk__search_cluster_node_cache(0, from, NULL); pcmk__update_peer_expected(__func__, node, CRMD_JOINSTATE_DOWN); if(AM_I_DC == FALSE) { return I_NULL; /* Done */ } } /*========== DC-Only Actions ==========*/ if (AM_I_DC) { if (strcmp(op, CRM_OP_JOIN_ANNOUNCE) == 0) { return I_NODE_JOIN; } else if (strcmp(op, CRM_OP_JOIN_REQUEST) == 0) { return I_JOIN_REQUEST; } else if (strcmp(op, CRM_OP_JOIN_CONFIRM) == 0) { return I_JOIN_RESULT; } else if (strcmp(op, CRM_OP_SHUTDOWN) == 0) { return handle_shutdown_self_ack(stored_msg); } else if (strcmp(op, CRM_OP_SHUTDOWN_REQ) == 0) { // Another controller wants to shut down its node return handle_shutdown_request(stored_msg); } } /*========== common actions ==========*/ if (strcmp(op, CRM_OP_NOVOTE) == 0) { ha_msg_input_t fsa_input; fsa_input.msg = stored_msg; register_fsa_input_adv(C_HA_MESSAGE, I_NULL, &fsa_input, A_ELECTION_COUNT | A_ELECTION_CHECK, FALSE, __func__); } else if (strcmp(op, CRM_OP_REMOTE_STATE) == 0) { /* a remote connection host is letting us know the node state */ return handle_remote_state(stored_msg); } else if (strcmp(op, CRM_OP_THROTTLE) == 0) { throttle_update(stored_msg); if (AM_I_DC && (controld_globals.transition_graph != NULL) && !controld_globals.transition_graph->complete) { crm_debug("The throttle changed. Trigger a graph."); trigger_graph(); } return I_NULL; } else if (strcmp(op, CRM_OP_CLEAR_FAILCOUNT) == 0) { return handle_failcount_op(stored_msg); } else if (strcmp(op, CRM_OP_VOTE) == 0) { /* count the vote and decide what to do after that */ ha_msg_input_t fsa_input; fsa_input.msg = stored_msg; register_fsa_input_adv(C_HA_MESSAGE, I_NULL, &fsa_input, A_ELECTION_COUNT | A_ELECTION_CHECK, FALSE, __func__); /* Sometimes we _must_ go into S_ELECTION */ if (controld_globals.fsa_state == S_HALT) { crm_debug("Forcing an election from S_HALT"); return I_ELECTION; } } else if (strcmp(op, CRM_OP_JOIN_OFFER) == 0) { verify_feature_set(stored_msg); crm_debug("Raising I_JOIN_OFFER: join-%s", crm_element_value(stored_msg, F_CRM_JOIN_ID)); return I_JOIN_OFFER; } else if (strcmp(op, CRM_OP_JOIN_ACKNAK) == 0) { crm_debug("Raising I_JOIN_RESULT: join-%s", crm_element_value(stored_msg, F_CRM_JOIN_ID)); return I_JOIN_RESULT; } else if (strcmp(op, CRM_OP_LRM_DELETE) == 0) { return handle_lrm_delete(stored_msg); } else if ((strcmp(op, CRM_OP_LRM_FAIL) == 0) || (strcmp(op, CRM_OP_LRM_REFRESH) == 0) // @COMPAT || (strcmp(op, CRM_OP_REPROBE) == 0)) { crm_xml_add(stored_msg, F_CRM_SYS_TO, CRM_SYSTEM_LRMD); return I_ROUTER; } else if (strcmp(op, CRM_OP_NOOP) == 0) { return I_NULL; } else if (strcmp(op, CRM_OP_LOCAL_SHUTDOWN) == 0) { crm_shutdown(SIGTERM); /*return I_SHUTDOWN; */ return I_NULL; } else if (strcmp(op, CRM_OP_PING) == 0) { return handle_ping(stored_msg); } else if (strcmp(op, CRM_OP_NODE_INFO) == 0) { return handle_node_info_request(stored_msg); } else if (strcmp(op, CRM_OP_RM_NODE_CACHE) == 0) { int id = 0; const char *name = NULL; crm_element_value_int(stored_msg, XML_ATTR_ID, &id); name = crm_element_value(stored_msg, XML_ATTR_UNAME); if(cause == C_IPC_MESSAGE) { msg = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL); if (send_cluster_message(NULL, crm_msg_crmd, msg, TRUE) == FALSE) { crm_err("Could not instruct peers to remove references to node %s/%u", name, id); } else { crm_notice("Instructing peers to remove references to node %s/%u", name, id); } free_xml(msg); } else { reap_crm_member(id, name); /* If we're forgetting this node, also forget any failures to fence * it, so we don't carry that over to any node added later with the * same name. */ st_fail_count_reset(name); } } else if (strcmp(op, CRM_OP_MAINTENANCE_NODES) == 0) { xmlNode *xml = get_message_xml(stored_msg, F_CRM_DATA); remote_ra_process_maintenance_nodes(xml); } else if (strcmp(op, PCMK__CONTROLD_CMD_NODES) == 0) { return handle_node_list(stored_msg); /*========== (NOT_DC)-Only Actions ==========*/ } else if (!AM_I_DC) { if (strcmp(op, CRM_OP_SHUTDOWN) == 0) { return handle_shutdown_ack(stored_msg); } } else { crm_err("Unexpected request (%s) sent to %s", op, AM_I_DC ? "the DC" : "non-DC node"); crm_log_xml_err(stored_msg, "Unexpected"); } return I_NULL; } static void handle_response(xmlNode *stored_msg) { const char *op = crm_element_value(stored_msg, F_CRM_TASK); + crm_log_xml_trace(stored_msg, "reply"); if (op == NULL) { - crm_log_xml_err(stored_msg, "Bad message"); + crm_warn("Ignoring reply without " F_CRM_TASK); } else if (AM_I_DC && strcmp(op, CRM_OP_PECALC) == 0) { // Check whether scheduler answer been superseded by subsequent request const char *msg_ref = crm_element_value(stored_msg, XML_ATTR_REFERENCE); if (msg_ref == NULL) { crm_err("%s - Ignoring calculation with no reference", op); } else if (pcmk__str_eq(msg_ref, controld_globals.fsa_pe_ref, pcmk__str_none)) { ha_msg_input_t fsa_input; controld_stop_sched_timer(); fsa_input.msg = stored_msg; register_fsa_input_later(C_IPC_MESSAGE, I_PE_SUCCESS, &fsa_input); } else { crm_info("%s calculation %s is obsolete", op, msg_ref); } } else if (strcmp(op, CRM_OP_VOTE) == 0 || strcmp(op, CRM_OP_SHUTDOWN_REQ) == 0 || strcmp(op, CRM_OP_SHUTDOWN) == 0) { } else { const char *host_from = crm_element_value(stored_msg, F_CRM_HOST_FROM); crm_err("Unexpected response (op=%s, src=%s) sent to the %s", op, host_from, AM_I_DC ? "DC" : "controller"); } } static enum crmd_fsa_input handle_shutdown_request(xmlNode * stored_msg) { /* handle here to avoid potential version issues * where the shutdown message/procedure may have * been changed in later versions. * * This way the DC is always in control of the shutdown */ char *now_s = NULL; const char *host_from = crm_element_value(stored_msg, F_CRM_HOST_FROM); if (host_from == NULL) { /* we're shutting down and the DC */ host_from = controld_globals.our_nodename; } crm_info("Creating shutdown request for %s (state=%s)", host_from, fsa_state2string(controld_globals.fsa_state)); crm_log_xml_trace(stored_msg, "message"); now_s = pcmk__ttoa(time(NULL)); update_attrd(host_from, XML_CIB_ATTR_SHUTDOWN, now_s, NULL, FALSE); free(now_s); /* will be picked up by the TE as long as its running */ return I_NULL; } static void send_msg_via_ipc(xmlNode * msg, const char *sys) { pcmk__client_t *client_channel = NULL; CRM_CHECK(sys != NULL, return); client_channel = pcmk__find_client_by_id(sys); if (crm_element_value(msg, F_CRM_HOST_FROM) == NULL) { crm_xml_add(msg, F_CRM_HOST_FROM, controld_globals.our_nodename); } if (client_channel != NULL) { /* Transient clients such as crmadmin */ pcmk__ipc_send_xml(client_channel, 0, msg, crm_ipc_server_event); } else if (pcmk__str_eq(sys, CRM_SYSTEM_TENGINE, pcmk__str_none)) { xmlNode *data = get_message_xml(msg, F_CRM_DATA); process_te_message(msg, data); } else if (pcmk__str_eq(sys, CRM_SYSTEM_LRMD, pcmk__str_none)) { fsa_data_t fsa_data; ha_msg_input_t fsa_input; fsa_input.msg = msg; fsa_input.xml = get_message_xml(msg, F_CRM_DATA); fsa_data.id = 0; fsa_data.actions = 0; fsa_data.data = &fsa_input; fsa_data.fsa_input = I_MESSAGE; fsa_data.fsa_cause = C_IPC_MESSAGE; fsa_data.origin = __func__; fsa_data.data_type = fsa_dt_ha_msg; do_lrm_invoke(A_LRM_INVOKE, C_IPC_MESSAGE, controld_globals.fsa_state, I_MESSAGE, &fsa_data); } else if (crmd_is_proxy_session(sys)) { crmd_proxy_send(sys, msg); } else { crm_info("Received invalid request: unknown subsystem '%s'", sys); } } void delete_ha_msg_input(ha_msg_input_t * orig) { if (orig == NULL) { return; } free_xml(orig->msg); free(orig); } /*! * \internal * \brief Notify the cluster of a remote node state change * * \param[in] node_name Node's name * \param[in] node_up true if node is up, false if down */ void broadcast_remote_state_message(const char *node_name, bool node_up) { xmlNode *msg = create_request(CRM_OP_REMOTE_STATE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL); crm_info("Notifying cluster of Pacemaker Remote node %s %s", node_name, node_up? "coming up" : "going down"); crm_xml_add(msg, XML_ATTR_ID, node_name); pcmk__xe_set_bool_attr(msg, XML_NODE_IN_CLUSTER, node_up); if (node_up) { crm_xml_add(msg, PCMK__XA_CONN_HOST, controld_globals.our_nodename); } send_cluster_message(NULL, crm_msg_crmd, msg, TRUE); free_xml(msg); } diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c index c3bd108e4d..083e2d734e 100644 --- a/lib/common/ipc_server.c +++ b/lib/common/ipc_server.c @@ -1,1010 +1,1011 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" /* Evict clients whose event queue grows this large (by default) */ #define PCMK_IPC_DEFAULT_QUEUE_MAX 500 static GHashTable *client_connections = NULL; /*! * \internal * \brief Count IPC clients * * \return Number of active IPC client connections */ guint pcmk__ipc_client_count(void) { return client_connections? g_hash_table_size(client_connections) : 0; } /*! * \internal * \brief Execute a function for each active IPC client connection * * \param[in] func Function to call * \param[in,out] user_data Pointer to pass to function * * \note The parameters are the same as for g_hash_table_foreach(). */ void pcmk__foreach_ipc_client(GHFunc func, gpointer user_data) { if ((func != NULL) && (client_connections != NULL)) { g_hash_table_foreach(client_connections, func, user_data); } } pcmk__client_t * pcmk__find_client(const qb_ipcs_connection_t *c) { if (client_connections) { return g_hash_table_lookup(client_connections, c); } crm_trace("No client found for %p", c); return NULL; } pcmk__client_t * pcmk__find_client_by_id(const char *id) { if ((client_connections != NULL) && (id != NULL)) { gpointer key; pcmk__client_t *client = NULL; GHashTableIter iter; g_hash_table_iter_init(&iter, client_connections); while (g_hash_table_iter_next(&iter, &key, (gpointer *) & client)) { if (strcmp(client->id, id) == 0) { return client; } } } crm_trace("No client found with id='%s'", pcmk__s(id, "")); return NULL; } /*! * \internal * \brief Get a client identifier for use in log messages * * \param[in] c Client * * \return Client's name, client's ID, or a string literal, as available * \note This is intended to be used in format strings like "client %s". */ const char * pcmk__client_name(const pcmk__client_t *c) { if (c == NULL) { return "(unspecified)"; } else if (c->name != NULL) { return c->name; } else if (c->id != NULL) { return c->id; } else { return "(unidentified)"; } } void pcmk__client_cleanup(void) { if (client_connections != NULL) { int active = g_hash_table_size(client_connections); if (active > 0) { crm_warn("Exiting with %d active IPC client%s", active, pcmk__plural_s(active)); } g_hash_table_destroy(client_connections); client_connections = NULL; } } void pcmk__drop_all_clients(qb_ipcs_service_t *service) { qb_ipcs_connection_t *c = NULL; if (service == NULL) { return; } c = qb_ipcs_connection_first_get(service); while (c != NULL) { qb_ipcs_connection_t *last = c; c = qb_ipcs_connection_next_get(service, last); /* There really shouldn't be anyone connected at this point */ crm_notice("Disconnecting client %p, pid=%d...", last, pcmk__client_pid(last)); qb_ipcs_disconnect(last); qb_ipcs_connection_unref(last); } } /*! * \internal * \brief Allocate a new pcmk__client_t object based on an IPC connection * * \param[in] c IPC connection (NULL to allocate generic client) * \param[in] key Connection table key (NULL to use sane default) * \param[in] uid_client UID corresponding to c (ignored if c is NULL) * * \return Pointer to new pcmk__client_t (or NULL on error) */ static pcmk__client_t * client_from_connection(qb_ipcs_connection_t *c, void *key, uid_t uid_client) { pcmk__client_t *client = calloc(1, sizeof(pcmk__client_t)); if (client == NULL) { crm_perror(LOG_ERR, "Allocating client"); return NULL; } if (c) { client->user = pcmk__uid2username(uid_client); if (client->user == NULL) { client->user = strdup("#unprivileged"); CRM_CHECK(client->user != NULL, free(client); return NULL); crm_err("Unable to enforce ACLs for user ID %d, assuming unprivileged", uid_client); } client->ipcs = c; pcmk__set_client_flags(client, pcmk__client_ipc); client->pid = pcmk__client_pid(c); if (key == NULL) { key = c; } } client->id = crm_generate_uuid(); if (key == NULL) { key = client->id; } if (client_connections == NULL) { crm_trace("Creating IPC client table"); client_connections = g_hash_table_new(g_direct_hash, g_direct_equal); } g_hash_table_insert(client_connections, key, client); return client; } /*! * \brief Allocate a new pcmk__client_t object and generate its ID * * \param[in] key What to use as connections hash table key (NULL to use ID) * * \return Pointer to new pcmk__client_t (asserts on failure) */ pcmk__client_t * pcmk__new_unauth_client(void *key) { pcmk__client_t *client = client_from_connection(NULL, key, 0); CRM_ASSERT(client != NULL); return client; } pcmk__client_t * pcmk__new_client(qb_ipcs_connection_t *c, uid_t uid_client, gid_t gid_client) { gid_t uid_cluster = 0; gid_t gid_cluster = 0; pcmk__client_t *client = NULL; CRM_CHECK(c != NULL, return NULL); if (pcmk_daemon_user(&uid_cluster, &gid_cluster) < 0) { static bool need_log = TRUE; if (need_log) { crm_warn("Could not find user and group IDs for user %s", CRM_DAEMON_USER); need_log = FALSE; } } if (uid_client != 0) { crm_trace("Giving group %u access to new IPC connection", gid_cluster); /* Passing -1 to chown(2) means don't change */ qb_ipcs_connection_auth_set(c, -1, gid_cluster, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); } /* TODO: Do our own auth checking, return NULL if unauthorized */ client = client_from_connection(c, NULL, uid_client); if (client == NULL) { return NULL; } if ((uid_client == 0) || (uid_client == uid_cluster)) { /* Remember when a connection came from root or hacluster */ pcmk__set_client_flags(client, pcmk__client_privileged); } crm_debug("New IPC client %s for PID %u with uid %d and gid %d", client->id, client->pid, uid_client, gid_client); return client; } static struct iovec * pcmk__new_ipc_event(void) { struct iovec *iov = calloc(2, sizeof(struct iovec)); CRM_ASSERT(iov != NULL); return iov; } /*! * \brief Free an I/O vector created by pcmk__ipc_prepare_iov() * * \param[in,out] event I/O vector to free */ void pcmk_free_ipc_event(struct iovec *event) { if (event != NULL) { free(event[0].iov_base); free(event[1].iov_base); free(event); } } static void free_event(gpointer data) { pcmk_free_ipc_event((struct iovec *) data); } static void add_event(pcmk__client_t *c, struct iovec *iov) { if (c->event_queue == NULL) { c->event_queue = g_queue_new(); } g_queue_push_tail(c->event_queue, iov); } void pcmk__free_client(pcmk__client_t *c) { if (c == NULL) { return; } if (client_connections) { if (c->ipcs) { crm_trace("Destroying %p/%p (%d remaining)", c, c->ipcs, g_hash_table_size(client_connections) - 1); g_hash_table_remove(client_connections, c->ipcs); } else { crm_trace("Destroying remote connection %p (%d remaining)", c, g_hash_table_size(client_connections) - 1); g_hash_table_remove(client_connections, c->id); } } if (c->event_timer) { g_source_remove(c->event_timer); } if (c->event_queue) { crm_debug("Destroying %d events", g_queue_get_length(c->event_queue)); g_queue_free_full(c->event_queue, free_event); } free(c->id); free(c->name); free(c->user); if (c->remote) { if (c->remote->auth_timeout) { g_source_remove(c->remote->auth_timeout); } free(c->remote->buffer); free(c->remote); } free(c); } /*! * \internal * \brief Raise IPC eviction threshold for a client, if allowed * * \param[in,out] client Client to modify * \param[in] qmax New threshold (as non-NULL string) * * \return true if change was allowed, false otherwise */ bool pcmk__set_client_queue_max(pcmk__client_t *client, const char *qmax) { if (pcmk_is_set(client->flags, pcmk__client_privileged)) { long long qmax_ll; if ((pcmk__scan_ll(qmax, &qmax_ll, 0LL) == pcmk_rc_ok) && (qmax_ll > 0LL) && (qmax_ll <= UINT_MAX)) { client->queue_max = (unsigned int) qmax_ll; return true; } } return false; } int pcmk__client_pid(qb_ipcs_connection_t *c) { struct qb_ipcs_connection_stats stats; stats.client_pid = 0; qb_ipcs_connection_stats_get(c, &stats, 0); return stats.client_pid; } /*! * \internal * \brief Retrieve message XML from data read from client IPC * * \param[in,out] c IPC client connection * \param[in] data Data read from client connection * \param[out] id Where to store message ID from libqb header * \param[out] flags Where to store flags from libqb header * * \return Message XML on success, NULL otherwise */ xmlNode * pcmk__client_data2xml(pcmk__client_t *c, void *data, uint32_t *id, uint32_t *flags) { xmlNode *xml = NULL; char *uncompressed = NULL; char *text = ((char *)data) + sizeof(pcmk__ipc_header_t); pcmk__ipc_header_t *header = data; if (!pcmk__valid_ipc_header(header)) { return NULL; } if (id) { *id = ((struct qb_ipc_response_header *)data)->id; } if (flags) { *flags = header->flags; } if (pcmk_is_set(header->flags, crm_ipc_proxied)) { /* Mark this client as being the endpoint of a proxy connection. * Proxy connections responses are sent on the event channel, to avoid * blocking the controller serving as proxy. */ pcmk__set_client_flags(c, pcmk__client_proxied); } if (header->size_compressed) { int rc = 0; unsigned int size_u = 1 + header->size_uncompressed; uncompressed = calloc(1, size_u); crm_trace("Decompressing message data %u bytes into %u bytes", header->size_compressed, size_u); rc = BZ2_bzBuffToBuffDecompress(uncompressed, &size_u, text, header->size_compressed, 1, 0); text = uncompressed; rc = pcmk__bzlib2rc(rc); if (rc != pcmk_rc_ok) { crm_err("Decompression failed: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); free(uncompressed); return NULL; } } CRM_ASSERT(text[header->size_uncompressed - 1] == 0); xml = string2xml(text); crm_log_xml_trace(xml, "[IPC received]"); free(uncompressed); return xml; } static int crm_ipcs_flush_events(pcmk__client_t *c); static gboolean crm_ipcs_flush_events_cb(gpointer data) { pcmk__client_t *c = data; c->event_timer = 0; crm_ipcs_flush_events(c); return FALSE; } /*! * \internal * \brief Add progressive delay before next event queue flush * * \param[in,out] c Client connection to add delay to * \param[in] queue_len Current event queue length */ static inline void delay_next_flush(pcmk__client_t *c, unsigned int queue_len) { /* Delay a maximum of 1.5 seconds */ guint delay = (queue_len < 5)? (1000 + 100 * queue_len) : 1500; c->event_timer = g_timeout_add(delay, crm_ipcs_flush_events_cb, c); } /*! * \internal * \brief Send client any messages in its queue * * \param[in,out] c Client to flush * * \return Standard Pacemaker return value */ static int crm_ipcs_flush_events(pcmk__client_t *c) { int rc = pcmk_rc_ok; ssize_t qb_rc = 0; unsigned int sent = 0; unsigned int queue_len = 0; if (c == NULL) { return rc; } else if (c->event_timer) { /* There is already a timer, wait until it goes off */ crm_trace("Timer active for %p - %d", c->ipcs, c->event_timer); return rc; } if (c->event_queue) { queue_len = g_queue_get_length(c->event_queue); } while (sent < 100) { pcmk__ipc_header_t *header = NULL; struct iovec *event = NULL; if (c->event_queue) { // We don't pop unless send is successful event = g_queue_peek_head(c->event_queue); } if (event == NULL) { // Queue is empty break; } qb_rc = qb_ipcs_event_sendv(c->ipcs, event, 2); if (qb_rc < 0) { rc = (int) -qb_rc; break; } event = g_queue_pop_head(c->event_queue); sent++; header = event[0].iov_base; if (header->size_compressed) { crm_trace("Event %d to %p[%d] (%lld compressed bytes) sent", header->qb.id, c->ipcs, c->pid, (long long) qb_rc); } else { crm_trace("Event %d to %p[%d] (%lld bytes) sent: %.120s", header->qb.id, c->ipcs, c->pid, (long long) qb_rc, (char *) (event[1].iov_base)); } pcmk_free_ipc_event(event); } queue_len -= sent; if (sent > 0 || queue_len) { crm_trace("Sent %d events (%d remaining) for %p[%d]: %s (%lld)", sent, queue_len, c->ipcs, c->pid, pcmk_rc_str(rc), (long long) qb_rc); } if (queue_len) { /* Allow clients to briefly fall behind on processing incoming messages, * but drop completely unresponsive clients so the connection doesn't * consume resources indefinitely. */ if (queue_len > QB_MAX(c->queue_max, PCMK_IPC_DEFAULT_QUEUE_MAX)) { if ((c->queue_backlog <= 1) || (queue_len < c->queue_backlog)) { /* Don't evict for a new or shrinking backlog */ crm_warn("Client with process ID %u has a backlog of %u messages " CRM_XS " %p", c->pid, queue_len, c->ipcs); } else { crm_err("Evicting client with process ID %u due to backlog of %u messages " CRM_XS " %p", c->pid, queue_len, c->ipcs); c->queue_backlog = 0; qb_ipcs_disconnect(c->ipcs); return rc; } } c->queue_backlog = queue_len; delay_next_flush(c, queue_len); } else { /* Event queue is empty, there is no backlog */ c->queue_backlog = 0; } return rc; } /*! * \internal * \brief Create an I/O vector for sending an IPC XML message * * \param[in] request Identifier for libqb response header * \param[in,out] message XML message to send * \param[in] max_send_size If 0, default IPC buffer size is used * \param[out] result Where to store prepared I/O vector * \param[out] bytes Size of prepared data in bytes * * \return Standard Pacemaker return code */ int pcmk__ipc_prepare_iov(uint32_t request, xmlNode *message, uint32_t max_send_size, struct iovec **result, ssize_t *bytes) { static unsigned int biggest = 0; struct iovec *iov; unsigned int total = 0; char *compressed = NULL; char *buffer = NULL; pcmk__ipc_header_t *header = NULL; if ((message == NULL) || (result == NULL)) { return EINVAL; } header = calloc(1, sizeof(pcmk__ipc_header_t)); if (header == NULL) { return ENOMEM; /* errno mightn't be set by allocator */ } buffer = dump_xml_unformatted(message); if (max_send_size == 0) { max_send_size = crm_ipc_default_buffer_size(); } CRM_LOG_ASSERT(max_send_size != 0); *result = NULL; iov = pcmk__new_ipc_event(); iov[0].iov_len = sizeof(pcmk__ipc_header_t); iov[0].iov_base = header; header->version = PCMK__IPC_VERSION; header->size_uncompressed = 1 + strlen(buffer); total = iov[0].iov_len + header->size_uncompressed; if (total < max_send_size) { iov[1].iov_base = buffer; iov[1].iov_len = header->size_uncompressed; } else { unsigned int new_size = 0; if (pcmk__compress(buffer, (unsigned int) header->size_uncompressed, (unsigned int) max_send_size, &compressed, &new_size) == pcmk_rc_ok) { pcmk__set_ipc_flags(header->flags, "send data", crm_ipc_compressed); header->size_compressed = new_size; iov[1].iov_len = header->size_compressed; iov[1].iov_base = compressed; free(buffer); biggest = QB_MAX(header->size_compressed, biggest); } else { crm_log_xml_trace(message, "EMSGSIZE"); biggest = QB_MAX(header->size_uncompressed, biggest); crm_err("Could not compress %u-byte message into less than IPC " "limit of %u bytes; set PCMK_ipc_buffer to higher value " "(%u bytes suggested)", header->size_uncompressed, max_send_size, 4 * biggest); free(compressed); free(buffer); pcmk_free_ipc_event(iov); return EMSGSIZE; } } header->qb.size = iov[0].iov_len + iov[1].iov_len; header->qb.id = (int32_t)request; /* Replying to a specific request */ *result = iov; CRM_ASSERT(header->qb.size > 0); if (bytes != NULL) { *bytes = header->qb.size; } return pcmk_rc_ok; } int pcmk__ipc_send_iov(pcmk__client_t *c, struct iovec *iov, uint32_t flags) { int rc = pcmk_rc_ok; static uint32_t id = 1; pcmk__ipc_header_t *header = iov[0].iov_base; if (c->flags & pcmk__client_proxied) { /* _ALL_ replies to proxied connections need to be sent as events */ if (!pcmk_is_set(flags, crm_ipc_server_event)) { /* The proxied flag lets us know this was originally meant to be a * response, even though we're sending it over the event channel. */ pcmk__set_ipc_flags(flags, "server event", crm_ipc_server_event |crm_ipc_proxied_relay_response); } } pcmk__set_ipc_flags(header->flags, "server event", flags); if (flags & crm_ipc_server_event) { header->qb.id = id++; /* We don't really use it, but doesn't hurt to set one */ if (flags & crm_ipc_server_free) { crm_trace("Sending the original to %p[%d]", c->ipcs, c->pid); add_event(c, iov); } else { struct iovec *iov_copy = pcmk__new_ipc_event(); crm_trace("Sending a copy to %p[%d]", c->ipcs, c->pid); iov_copy[0].iov_len = iov[0].iov_len; iov_copy[0].iov_base = malloc(iov[0].iov_len); memcpy(iov_copy[0].iov_base, iov[0].iov_base, iov[0].iov_len); iov_copy[1].iov_len = iov[1].iov_len; iov_copy[1].iov_base = malloc(iov[1].iov_len); memcpy(iov_copy[1].iov_base, iov[1].iov_base, iov[1].iov_len); add_event(c, iov_copy); } } else { ssize_t qb_rc; CRM_LOG_ASSERT(header->qb.id != 0); /* Replying to a specific request */ qb_rc = qb_ipcs_response_sendv(c->ipcs, iov, 2); if (qb_rc < header->qb.size) { if (qb_rc < 0) { rc = (int) -qb_rc; } crm_notice("Response %d to pid %d failed: %s " CRM_XS " bytes=%u rc=%lld ipcs=%p", header->qb.id, c->pid, pcmk_rc_str(rc), header->qb.size, (long long) qb_rc, c->ipcs); } else { crm_trace("Response %d sent, %lld bytes to %p[%d]", header->qb.id, (long long) qb_rc, c->ipcs, c->pid); } if (flags & crm_ipc_server_free) { pcmk_free_ipc_event(iov); } } if (flags & crm_ipc_server_event) { rc = crm_ipcs_flush_events(c); } else { crm_ipcs_flush_events(c); } if ((rc == EPIPE) || (rc == ENOTCONN)) { crm_trace("Client %p disconnected", c->ipcs); } return rc; } int pcmk__ipc_send_xml(pcmk__client_t *c, uint32_t request, xmlNode *message, uint32_t flags) { struct iovec *iov = NULL; int rc = pcmk_rc_ok; if (c == NULL) { return EINVAL; } rc = pcmk__ipc_prepare_iov(request, message, crm_ipc_default_buffer_size(), &iov, NULL); if (rc == pcmk_rc_ok) { pcmk__set_ipc_flags(flags, "send data", crm_ipc_server_free); rc = pcmk__ipc_send_iov(c, iov, flags); } else { pcmk_free_ipc_event(iov); crm_notice("IPC message to pid %d failed: %s " CRM_XS " rc=%d", c->pid, pcmk_rc_str(rc), rc); } return rc; } /*! * \internal * \brief Create an acknowledgement with a status code to send to a client * * \param[in] function Calling function * \param[in] line Source file line within calling function * \param[in] flags IPC flags to use when sending * \param[in] tag Element name to use for acknowledgement * \param[in] ver IPC protocol version (can be NULL) * \param[in] status Exit status code to add to ack * * \return Newly created XML for ack * \note The caller is responsible for freeing the return value with free_xml(). */ xmlNode * pcmk__ipc_create_ack_as(const char *function, int line, uint32_t flags, const char *tag, const char *ver, crm_exit_t status) { xmlNode *ack = NULL; if (pcmk_is_set(flags, crm_ipc_client_response)) { ack = create_xml_node(NULL, tag); crm_xml_add(ack, "function", function); crm_xml_add_int(ack, "line", line); crm_xml_add_int(ack, "status", (int) status); crm_xml_add(ack, PCMK__XA_IPC_PROTO_VERSION, ver); } return ack; } /*! * \internal * \brief Send an acknowledgement with a status code to a client * * \param[in] function Calling function * \param[in] line Source file line within calling function * \param[in] c Client to send ack to * \param[in] request Request ID being replied to * \param[in] flags IPC flags to use when sending * \param[in] tag Element name to use for acknowledgement * \param[in] ver IPC protocol version (can be NULL) * \param[in] status Status code to send with acknowledgement * * \return Standard Pacemaker return code */ int pcmk__ipc_send_ack_as(const char *function, int line, pcmk__client_t *c, uint32_t request, uint32_t flags, const char *tag, const char *ver, crm_exit_t status) { int rc = pcmk_rc_ok; xmlNode *ack = pcmk__ipc_create_ack_as(function, line, flags, tag, ver, status); if (ack != NULL) { crm_trace("Ack'ing IPC message from client %s as <%s status=%d>", pcmk__client_name(c), tag, status); + crm_log_xml_trace(ack, "sent-ack"); c->request_id = 0; rc = pcmk__ipc_send_xml(c, request, ack, flags); free_xml(ack); } return rc; } /*! * \internal * \brief Add an IPC server to the main loop for the pacemaker-based API * * \param[out] ipcs_ro New IPC server for read-only pacemaker-based API * \param[out] ipcs_rw New IPC server for read/write pacemaker-based API * \param[out] ipcs_shm New IPC server for shared-memory pacemaker-based API * \param[in] ro_cb IPC callbacks for read-only API * \param[in] rw_cb IPC callbacks for read/write and shared-memory APIs * * \note This function exits fatally if unable to create the servers. */ void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro, qb_ipcs_service_t **ipcs_rw, qb_ipcs_service_t **ipcs_shm, struct qb_ipcs_service_handlers *ro_cb, struct qb_ipcs_service_handlers *rw_cb) { *ipcs_ro = mainloop_add_ipc_server(PCMK__SERVER_BASED_RO, QB_IPC_NATIVE, ro_cb); *ipcs_rw = mainloop_add_ipc_server(PCMK__SERVER_BASED_RW, QB_IPC_NATIVE, rw_cb); *ipcs_shm = mainloop_add_ipc_server(PCMK__SERVER_BASED_SHM, QB_IPC_SHM, rw_cb); if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) { crm_err("Failed to create the CIB manager: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled"); crm_exit(CRM_EX_FATAL); } } /*! * \internal * \brief Destroy IPC servers for pacemaker-based API * * \param[out] ipcs_ro IPC server for read-only pacemaker-based API * \param[out] ipcs_rw IPC server for read/write pacemaker-based API * \param[out] ipcs_shm IPC server for shared-memory pacemaker-based API * * \note This is a convenience function for calling qb_ipcs_destroy() for each * argument. */ void pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro, qb_ipcs_service_t *ipcs_rw, qb_ipcs_service_t *ipcs_shm) { qb_ipcs_destroy(ipcs_ro); qb_ipcs_destroy(ipcs_rw); qb_ipcs_destroy(ipcs_shm); } /*! * \internal * \brief Add an IPC server to the main loop for the pacemaker-controld API * * \param[in] cb IPC callbacks * * \return Newly created IPC server */ qb_ipcs_service_t * pcmk__serve_controld_ipc(struct qb_ipcs_service_handlers *cb) { return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb); } /*! * \internal * \brief Add an IPC server to the main loop for the pacemaker-attrd API * * \param[out] ipcs Where to store newly created IPC server * \param[in] cb IPC callbacks * * \note This function exits fatally if unable to create the servers. */ void pcmk__serve_attrd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server(T_ATTRD, QB_IPC_NATIVE, cb); if (*ipcs == NULL) { crm_err("Failed to create pacemaker-attrd server: exiting and inhibiting respawn"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } /*! * \internal * \brief Add an IPC server to the main loop for the pacemaker-fenced API * * \param[out] ipcs Where to store newly created IPC server * \param[in] cb IPC callbacks * * \note This function exits fatally if unable to create the servers. */ void pcmk__serve_fenced_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server_with_prio("stonith-ng", QB_IPC_NATIVE, cb, QB_LOOP_HIGH); if (*ipcs == NULL) { crm_err("Failed to create fencer: exiting and inhibiting respawn."); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); crm_exit(CRM_EX_FATAL); } } /*! * \internal * \brief Add an IPC server to the main loop for the pacemakerd API * * \param[out] ipcs Where to store newly created IPC server * \param[in] cb IPC callbacks * * \note This function exits with CRM_EX_OSERR if unable to create the servers. */ void pcmk__serve_pacemakerd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { *ipcs = mainloop_add_ipc_server(CRM_SYSTEM_MCP, QB_IPC_NATIVE, cb); if (*ipcs == NULL) { crm_err("Couldn't start pacemakerd IPC server"); crm_warn("Verify pacemaker and pacemaker_remote are not both enabled."); /* sub-daemons are observed by pacemakerd. Thus we exit CRM_EX_FATAL * if we want to prevent pacemakerd from restarting them. * With pacemakerd we leave the exit-code shown to e.g. systemd * to what it was prior to moving the code here from pacemakerd.c */ crm_exit(CRM_EX_OSERR); } } /*! * \internal * \brief Add an IPC server to the main loop for the pacemaker-schedulerd API * * \param[in] cb IPC callbacks * * \return Newly created IPC server * \note This function exits fatally if unable to create the servers. */ qb_ipcs_service_t * pcmk__serve_schedulerd_ipc(struct qb_ipcs_service_handlers *cb) { return mainloop_add_ipc_server(CRM_SYSTEM_PENGINE, QB_IPC_NATIVE, cb); } /*! * \brief Check whether string represents a client name used by cluster daemons * * \param[in] name String to check * * \return true if name is standard client name used by daemons, false otherwise * * \note This is provided by the client, and so cannot be used by itself as a * secure means of authentication. */ bool crm_is_daemon_name(const char *name) { name = pcmk__message_name(name); return (!strcmp(name, CRM_SYSTEM_CRMD) || !strcmp(name, CRM_SYSTEM_STONITHD) || !strcmp(name, "stonith-ng") || !strcmp(name, "attrd") || !strcmp(name, CRM_SYSTEM_CIB) || !strcmp(name, CRM_SYSTEM_MCP) || !strcmp(name, CRM_SYSTEM_DC) || !strcmp(name, CRM_SYSTEM_TENGINE) || !strcmp(name, CRM_SYSTEM_LRMD)); } diff --git a/tools/crm_node.c b/tools/crm_node.c index 80e8807ca7..85452aa316 100644 --- a/tools/crm_node.c +++ b/tools/crm_node.c @@ -1,854 +1,857 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SUMMARY "crm_node - Tool for displaying low-level node information" struct { gboolean corosync; gboolean dangerous_cmd; gboolean force_flag; char command; int nodeid; char *target_uname; } options = { .command = '\0', .force_flag = FALSE }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error); static GError *error = NULL; static GMainLoop *mainloop = NULL; static crm_exit_t exit_code = CRM_EX_OK; static pcmk__output_t *out = NULL; #define INDENT " " static GOptionEntry command_entries[] = { { "cluster-id", 'i', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display this node's cluster id", NULL }, { "list", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display all known members (past and present) of this cluster", NULL }, { "name", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the name used by the cluster for this node", NULL }, { "partition", 'p', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the members of this partition", NULL }, { "quorum", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display a 1 if our partition has quorum, 0 if not", NULL }, { "name-for-id", 'N', 0, G_OPTION_ARG_CALLBACK, name_cb, "Display the name used by the cluster for the node with the specified ID", "ID" }, { "remove", 'R', 0, G_OPTION_ARG_CALLBACK, remove_cb, "(Advanced) Remove the (stopped) node with the specified name from Pacemaker's\n" INDENT "configuration and caches (the node must already have been removed from\n" INDENT "the underlying cluster stack configuration", "NAME" }, { NULL } }; static GOptionEntry addl_entries[] = { { "force", 'f', 0, G_OPTION_ARG_NONE, &options.force_flag, NULL, NULL }, #if SUPPORT_COROSYNC /* Unused and deprecated */ { "corosync", 'C', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.corosync, NULL, NULL }, #endif // @TODO add timeout option for when IPC replies are needed { NULL } }; static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_eq("-i", option_name, pcmk__str_casei) || pcmk__str_eq("--cluster-id", option_name, pcmk__str_casei)) { options.command = 'i'; } else if (pcmk__str_eq("-l", option_name, pcmk__str_casei) || pcmk__str_eq("--list", option_name, pcmk__str_casei)) { options.command = 'l'; } else if (pcmk__str_eq("-n", option_name, pcmk__str_casei) || pcmk__str_eq("--name", option_name, pcmk__str_casei)) { options.command = 'n'; } else if (pcmk__str_eq("-p", option_name, pcmk__str_casei) || pcmk__str_eq("--partition", option_name, pcmk__str_casei)) { options.command = 'p'; } else if (pcmk__str_eq("-q", option_name, pcmk__str_casei) || pcmk__str_eq("--quorum", option_name, pcmk__str_casei)) { options.command = 'q'; } else { g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Unknown param passed to command_cb: %s", option_name); return FALSE; } return TRUE; } gboolean name_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.command = 'N'; pcmk__scan_min_int(optarg, &(options.nodeid), 0); return TRUE; } gboolean remove_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (optarg == NULL) { g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "-R option requires an argument"); return FALSE; } options.command = 'R'; options.dangerous_cmd = TRUE; pcmk__str_update(&options.target_uname, optarg); return TRUE; } PCMK__OUTPUT_ARGS("node-id", "uint32_t") static int node_id_default(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); out->info(out, "%" PRIu32, node_id); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-id", "uint32_t") static int node_id_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); char *id_s = crm_strdup_printf("%" PRIu32, node_id); pcmk__output_create_xml_node(out, "node-info", "nodeid", id_s, NULL); free(id_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-list", "GList *") static int node_list_default(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; out->info(out, "%" PRIu32 " %s %s", node->id, pcmk__s(node->uname, ""), pcmk__s(node->state, "")); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-list", "GList *") static int node_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); out->begin_list(out, NULL, NULL, "nodes"); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; char *id_s = crm_strdup_printf("%" PRIu32, node->id); pcmk__output_create_xml_node(out, "node", "id", id_s, "name", node->uname, "state", node->state, NULL); free(id_s); } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-name", "uint32_t", "const char *") static int node_name_default(pcmk__output_t *out, va_list args) { uint32_t node_id G_GNUC_UNUSED = va_arg(args, uint32_t); const char *node_name = va_arg(args, const char *); out->info(out, "%s", node_name); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-name", "uint32_t", "const char *") static int node_name_xml(pcmk__output_t *out, va_list args) { uint32_t node_id = va_arg(args, uint32_t); const char *node_name = va_arg(args, const char *); char *id_s = crm_strdup_printf("%" PRIu32, node_id); pcmk__output_create_xml_node(out, "node-info", "nodeid", id_s, XML_ATTR_UNAME, node_name, NULL); free(id_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("partition-list", "GList *") static int partition_list_default(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); GString *buffer = NULL; for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; if (pcmk__str_eq(node->state, "member", pcmk__str_none)) { pcmk__add_separated_word(&buffer, 128, pcmk__s(node->uname, ""), " "); } } if (buffer != NULL) { out->info(out, "%s", buffer->str); g_string_free(buffer, TRUE); return pcmk_rc_ok; } return pcmk_rc_no_output; } PCMK__OUTPUT_ARGS("partition-list", "GList *") static int partition_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); out->begin_list(out, NULL, NULL, "nodes"); for (GList *node_iter = nodes; node_iter != NULL; node_iter = node_iter->next) { pcmk_controld_api_node_t *node = node_iter->data; if (pcmk__str_eq(node->state, "member", pcmk__str_none)) { char *id_s = crm_strdup_printf("%" PRIu32, node->id); pcmk__output_create_xml_node(out, "node", "id", id_s, "name", node->uname, "state", node->state, NULL); free(id_s); } } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("quorum", "bool") static int quorum_default(pcmk__output_t *out, va_list args) { bool have_quorum = va_arg(args, int); out->info(out, "%d", have_quorum); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("quorum", "bool") static int quorum_xml(pcmk__output_t *out, va_list args) { bool have_quorum = va_arg(args, int); pcmk__output_create_xml_node(out, "cluster-info", "quorum", have_quorum ? "true" : "false", NULL); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "node-id", "default", node_id_default }, { "node-id", "xml", node_id_xml }, { "node-list", "default", node_list_default }, { "node-list", "xml", node_list_xml }, { "node-name", "default", node_name_default }, { "node-name", "xml", node_name_xml }, { "quorum", "default", quorum_default }, { "quorum", "xml", quorum_xml }, { "partition-list", "default", partition_list_default }, { "partition-list", "xml", partition_list_xml }, { NULL, NULL, NULL } }; static gint sort_node(gconstpointer a, gconstpointer b) { const pcmk_controld_api_node_t *node_a = a; const pcmk_controld_api_node_t *node_b = b; return pcmk__numeric_strcasecmp((node_a->uname? node_a->uname : ""), (node_b->uname? node_b->uname : "")); } 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 g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Lost connection to controller"); } goto done; break; case pcmk_ipc_event_reply: break; default: return; } if (status != CRM_EX_OK) { exit_code = status; g_set_error(&error, PCMK__EXITC_ERROR, status, "Bad reply from controller: %s", crm_exit_str(status)); goto done; } if (reply->reply_type != pcmk_controld_reply_nodes) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_INDETERMINATE, "Unknown reply type %d from controller", reply->reply_type); goto done; } reply->data.nodes = g_list_sort(reply->data.nodes, sort_node); if (options.command == 'p') { out->message(out, "partition-list", reply->data.nodes); } else if (options.command == 'l') { out->message(out, "node-list", reply->data.nodes); } // Success exit_code = CRM_EX_OK; done: pcmk_disconnect_ipc(controld_api); pcmk_quit_main_loop(mainloop, 10); } static void run_controller_mainloop(void) { pcmk_ipc_api_t *controld_api = NULL; int rc; // Set disconnect exit code to handle unexpected disconnects exit_code = CRM_EX_DISCONNECT; // Create controller IPC object rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not connect to controller: %s", pcmk_rc_str(rc)); return; } pcmk_register_ipc_callback(controld_api, controller_event_cb, NULL); // Connect to controller rc = pcmk__connect_ipc(controld_api, pcmk_ipc_dispatch_main, 5); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not connect to %s: %s", pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc)); return; } rc = pcmk_controld_api_list_nodes(controld_api); if (rc != pcmk_rc_ok) { pcmk_disconnect_ipc(controld_api); exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Could not ping controller: %s", pcmk_rc_str(rc)); return; } // Run main loop to get controller reply via controller_event_cb() mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); g_main_loop_unref(mainloop); mainloop = NULL; pcmk_free_ipc_api(controld_api); } static void print_node_id(void) { uint32_t nodeid; int rc = pcmk__query_node_info(out, &nodeid, NULL, NULL, NULL, NULL, NULL, false, 0); if (rc != pcmk_rc_ok) { /* pcmk__query_node_info already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "node-id", nodeid); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print node ID: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } static void print_node_name(uint32_t nodeid) { int rc = pcmk_rc_ok; char *node_name = NULL; if (nodeid == 0) { // 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) { rc = out->message(out, "node-name", 0, name); goto done; } } // Otherwise ask the controller /* pcmk__query_node_name already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ rc = pcmk__query_node_name(out, nodeid, &node_name, 0); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "node-name", 0, node_name); done: if (node_name != NULL) { free(node_name); } if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print node name: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } static void print_quorum(void) { bool quorum; int rc = pcmk__query_node_info(out, NULL, NULL, NULL, NULL, &quorum, NULL, false, 0); if (rc != pcmk_rc_ok) { /* pcmk__query_node_info already sets an error message on the output object, * so there's no need to call g_set_error here. That would just create a * duplicate error message in the output. */ exit_code = pcmk_rc2exitc(rc); return; } rc = out->message(out, "quorum", quorum); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, "Could not print quorum status: %s", pcmk_rc_str(rc)); } exit_code = pcmk_rc2exitc(rc); } -/* Returns a standard Pacemaker return code */ +/*! + * \internal + * \brief Purge a node from CIB + * + * \param[in] node_name Name of node to purge (or NULL to leave unspecified) + * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) + * + * \note At least one of node_name and node_id must be specified. + * \return Standard Pacemaker return code + */ static int -cib_remove_node(long id, const char *name) +purge_node_from_cib(const char *node_name, long node_id) { - int rc; + int rc = pcmk_rc_ok; cib_t *cib = NULL; - xmlNode *node = NULL; - xmlNode *node_state = NULL; - - crm_trace("Removing %s from the CIB", name); - - if (name == NULL && id == 0) { - exit_code = pcmk_rc2exitc(ENOTUNIQ); - - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Neither node ID nor name given"); - 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 > 0) { - crm_xml_set_id(node, "%ld", id); - crm_xml_add(node_state, XML_ATTR_ID, ID(node)); - } + xmlNode *xml = NULL; + // Connect to CIB cib = cib_new(); - cib->cmds->signon(cib, crm_system_name, cib_command); - - rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, node, cib_sync_call); + if (cib == NULL) { + return ENOTCONN; + } + rc = cib->cmds->signon(cib, crm_system_name, cib_command); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); - exit_code = pcmk_rc2exitc(rc); + goto done; + } - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Could not remove %s[%ld] from " XML_CIB_TAG_NODES ": %s", - name, id, pcmk_strerror(rc)); + // Remove from configuration section + xml = create_xml_node(NULL, XML_CIB_TAG_NODE); + crm_xml_add(xml, XML_ATTR_UNAME, node_name); + if (node_id > 0) { + crm_xml_set_id(xml, "%ld", node_id); } - rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, node_state, cib_sync_call); + rc = cib->cmds->remove(cib, XML_CIB_TAG_NODES, xml, cib_sync_call); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); - exit_code = pcmk_rc2exitc(rc); + goto done; + } + free_xml(xml); - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Could not remove %s[%ld] from " XML_CIB_TAG_STATUS ": %s", - name, id, pcmk_strerror(rc)); + // Remove from status section + xml = create_xml_node(NULL, XML_CIB_TAG_STATE); + crm_xml_add(xml, XML_ATTR_UNAME, node_name); + if (node_id > 0) { + crm_xml_set_id(xml, "%ld", node_id); + } + rc = cib->cmds->remove(cib, XML_CIB_TAG_STATUS, xml, cib_sync_call); + rc = pcmk_legacy2rc(rc); + if (rc == pcmk_rc_ok) { + crm_debug("Purged node %s (%ld) from CIB", + pcmk__s(node_name, "by ID"), node_id); } +done: + free_xml(xml); cib__clean_up_connection(&cib); return rc; } +/*! + * \internal + * \brief Purge a node from a single server's peer cache + * + * \param[in] server IPC server to send request to + * \param[in] node_name Name of node to purge (or NULL to leave unspecified) + * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) + * + * \note At least one of node_name and node_id must be specified. + * \return Standard Pacemaker return code + */ static int -controller_remove_node(const char *node_name, long nodeid) +purge_node_from(enum pcmk_ipc_server server, const char *node_name, + long node_id) { - pcmk_ipc_api_t *controld_api = NULL; + pcmk_ipc_api_t *api = NULL; int rc; - // Create controller IPC object - rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); + rc = pcmk_new_ipc_api(&api, server); if (rc != pcmk_rc_ok) { - g_set_error(&error, PCMK__RC_ERROR, rc, - "Could not connect to controller: %s", pcmk_rc_str(rc)); - return ENOTCONN; + goto done; } - // Connect to controller (without main loop) - rc = pcmk__connect_ipc(controld_api, pcmk_ipc_dispatch_sync, 5); + rc = pcmk__connect_ipc(api, pcmk_ipc_dispatch_sync, 5); if (rc != pcmk_rc_ok) { - g_set_error(&error, PCMK__RC_ERROR, rc, - "Could not connect to %s: %s", - pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc)); - pcmk_free_ipc_api(controld_api); - return rc; + goto done; } - rc = pcmk_ipc_purge_node(controld_api, node_name, nodeid); - if (rc != pcmk_rc_ok) { + rc = pcmk_ipc_purge_node(api, node_name, node_id); +done: + if (rc != pcmk_rc_ok) { // Debug message already logged on success g_set_error(&error, PCMK__RC_ERROR, rc, - "Could not clear node from controller's cache: %s", + "Could not purge node %s from %s: %s", + pcmk__s(node_name, "by ID"), pcmk_ipc_name(api, true), pcmk_rc_str(rc)); } - - pcmk_free_ipc_api(controld_api); - return pcmk_rc_ok; + pcmk_free_ipc_api(api); + return rc; } -/* Returns a standard Pacemaker return code */ +/*! + * \internal + * \brief Purge a node from the fencer's peer cache + * + * \param[in] node_name Name of node to purge (or NULL to leave unspecified) + * \param[in] node_id Node ID of node to purge (or 0 to leave unspecified) + * + * \note At least one of node_name and node_id must be specified. + * \return Standard Pacemaker return code + */ static int -tools_remove_node_cache(const char *node_name, long nodeid, const char *target) +purge_node_from_fencer(const char *node_name, long node_id) { - int rc = -1; + int rc = pcmk_rc_ok; crm_ipc_t *conn = NULL; xmlNode *cmd = NULL; - conn = crm_ipc_new(target, 0); - if (!conn) { - exit_code = pcmk_rc2exitc(ENOTCONN); + conn = crm_ipc_new("stonith-ng", 0); + if (conn == NULL) { + rc = ENOTCONN; + exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Could not connect to controller"); - return ENOTCONN; + "Could not connect to fencer to purge node %s", + pcmk__s(node_name, "by ID")); + return rc; } rc = pcmk__connect_generic_ipc(conn); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Could not connect to controller: %s", pcmk_rc_str(rc)); + "Could not connect to fencer to purge node %s: %s", + pcmk__s(node_name, "by ID"), pcmk_rc_str(rc)); crm_ipc_destroy(conn); return rc; } - crm_trace("Removing %s[%ld] from the %s membership cache", - node_name, nodeid, target); - - if(pcmk__str_eq(target, T_ATTRD, pcmk__str_casei)) { - cmd = create_xml_node(NULL, __func__); - - crm_xml_add(cmd, F_TYPE, T_ATTRD); - crm_xml_add(cmd, F_ORIG, crm_system_name); - - crm_xml_add(cmd, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE); - - pcmk__xe_add_node(cmd, node_name, nodeid); - - } else { // Fencer or pacemakerd - cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, target, - crm_system_name, NULL); - if (nodeid > 0) { - crm_xml_set_id(cmd, "%ld", nodeid); - } - crm_xml_add(cmd, XML_ATTR_UNAME, node_name); + cmd = create_request(CRM_OP_RM_NODE_CACHE, NULL, NULL, "stonith-ng", + crm_system_name, NULL); + if (node_id > 0) { + crm_xml_set_id(cmd, "%ld", node_id); } + crm_xml_add(cmd, XML_ATTR_UNAME, node_name); rc = crm_ipc_send(conn, cmd, 0, 0, NULL); - crm_debug("%s peer cache cleanup for %s (%ld): %d", - target, node_name, nodeid, rc); - - if (rc > 0) { - // @TODO Should this be done just once after all the rest? - rc = cib_remove_node(nodeid, node_name); + if (rc >= 0) { + rc = pcmk_rc_ok; + crm_debug("Purged node %s (%ld) from fencer", + pcmk__s(node_name, "by ID"), node_id); } else { - rc = -rc; - exit_code = pcmk_rc2exitc(rc); - - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Could not send IPC request: %s", pcmk_rc_str(rc)); - } - - if (conn) { - crm_ipc_close(conn); - crm_ipc_destroy(conn); + rc = pcmk_legacy2rc(rc); + fprintf(stderr, "Could not purge node %s from fencer: %s\n", + pcmk__s(node_name, "by ID"), pcmk_rc_str(rc)); } free_xml(cmd); + crm_ipc_close(conn); + crm_ipc_destroy(conn); return rc; } static void remove_node(const char *target_uname) { - int rc; - int d = 0; + int rc = pcmk_rc_ok; long nodeid = 0; const char *node_name = NULL; char *endptr = NULL; - const char *daemons[] = { - "stonith-ng", - T_ATTRD, - CRM_SYSTEM_MCP, + const enum pcmk_ipc_server servers[] = { + pcmk_ipc_controld, + pcmk_ipc_attrd, }; // Check whether node was specified by name or numeric ID errno = 0; nodeid = strtol(target_uname, &endptr, 10); if ((errno != 0) || (endptr == target_uname) || (*endptr != '\0') || (nodeid <= 0)) { // It's not a positive integer, so assume it's a node name nodeid = 0; node_name = target_uname; } - rc = controller_remove_node(node_name, nodeid); + for (int i = 0; i < PCMK__NELEM(servers); ++i) { + rc = purge_node_from(servers[i], node_name, nodeid); + if (rc != pcmk_rc_ok) { + exit_code = pcmk_rc2exitc(rc); + return; + } + } + + // The fencer hasn't been converted to pcmk_ipc_api_t yet + rc = purge_node_from_fencer(node_name, nodeid); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); return; } - for (d = 0; d < PCMK__NELEM(daemons); d++) { - if (tools_remove_node_cache(node_name, nodeid, daemons[d]) != pcmk_rc_ok) { - return; - } - } - - exit_code = CRM_EX_OK; + // Lastly, purge the node from the CIB itself + rc = purge_node_from_cib(node_name, nodeid); + exit_code = pcmk_rc2exitc(rc); } static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { NULL } }; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); /* Add the -q option, which cannot be part of the globally supported options * because some tools use that flag for something else. */ pcmk__add_main_args(context, extra_prog_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { int rc = pcmk_rc_ok; GOptionGroup *output_group = NULL; pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); gchar **processed_args = pcmk__cmdline_preproc(argv, "NR"); GOptionContext *context = build_arg_context(args, &output_group); pcmk__register_formats(output_group, formats); if (!g_option_context_parse_strv(context, &processed_args, &error)) { exit_code = CRM_EX_USAGE; goto done; } pcmk__cli_init_logging("crm_node", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s", args->output_ty, pcmk_rc_str(rc)); goto done; } if (!pcmk__force_args(context, &error, "%s --xml-simple-list", g_get_prgname())) { exit_code = CRM_EX_SOFTWARE; goto done; } if (args->version) { out->version(out, false); goto done; } if (options.command == 0) { char *help = g_option_context_get_help(context, TRUE, NULL); out->err(out, "%s", help); g_free(help); exit_code = CRM_EX_USAGE; goto done; } if (options.dangerous_cmd && options.force_flag == FALSE) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed."); goto done; } pcmk__register_lib_messages(out); pcmk__register_messages(out, fmt_functions); switch (options.command) { case 'i': print_node_id(); break; case 'n': print_node_name(0); break; case 'q': print_quorum(); break; case 'N': print_node_name(options.nodeid); break; case 'R': remove_node(options.target_uname); break; case 'l': case 'p': run_controller_mainloop(); break; default: break; } done: g_strfreev(processed_args); pcmk__free_arg_context(context); pcmk__output_and_clear_error(&error, out); if (out != NULL) { out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } pcmk__unregister_formats(); return crm_exit(exit_code); }