diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index 26eb315f79..88f7ac1489 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -1,514 +1,518 @@ /* * Copyright 2004-2025 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 /* pid_t, sleep, ssize_t */ #include #include #include #include #include #include #include #include // xmlXPathObject, etc. #include static void handle_disconnect(void); static pcmk_ipc_api_t *schedulerd_api = NULL; /*! * \internal * \brief Close any scheduler connection and free associated memory */ void controld_shutdown_schedulerd_ipc(void) { controld_clear_fsa_input_flags(R_PE_REQUIRED); pcmk_disconnect_ipc(schedulerd_api); handle_disconnect(); pcmk_free_ipc_api(schedulerd_api); schedulerd_api = NULL; } /*! * \internal * \brief Save CIB query result to file, raising FSA error * * \param[in] msg Ignored * \param[in] call_id Call ID of CIB query * \param[in] rc Return code of CIB query * \param[in] output Result of CIB query * \param[in] user_data Unique identifier for filename * * \note This is intended to be called after a scheduler connection fails. */ static void save_cib_contents(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data) { const char *id = user_data; register_fsa_error_adv(C_FSA_INTERNAL, I_ERROR, NULL, NULL, __func__); CRM_CHECK(id != NULL, return); if (rc == pcmk_ok) { char *filename = crm_strdup_printf(PCMK_SCHEDULER_INPUT_DIR "/pe-core-%s.bz2", id); if (pcmk__xml_write_file(output, filename, true) != pcmk_rc_ok) { crm_err("Could not save Cluster Information Base to %s after scheduler crash", filename); } else { crm_notice("Saved Cluster Information Base to %s after scheduler crash", filename); } free(filename); } } /*! * \internal * \brief Respond to scheduler connection failure */ static void handle_disconnect(void) { // If we aren't connected to the scheduler, we can't expect a reply controld_expect_sched_reply(NULL); if (pcmk_is_set(controld_globals.fsa_input_register, R_PE_REQUIRED)) { int rc = pcmk_ok; char *uuid_str = crm_generate_uuid(); crm_crit("Lost connection to the scheduler " QB_XS " CIB will be saved to " PCMK_SCHEDULER_INPUT_DIR "/pe-core-%s.bz2", uuid_str); /* * The scheduler died... * * Save the current CIB so that we have a chance of * figuring out what killed it. * * Delay raising the I_ERROR until the query below completes or * 5s is up, whichever comes first. * */ rc = controld_globals.cib_conn->cmds->query(controld_globals.cib_conn, NULL, NULL, cib_none); fsa_register_cib_callback(rc, uuid_str, save_cib_contents); } controld_clear_fsa_input_flags(R_PE_CONNECTED); controld_trigger_fsa(); return; } static void handle_reply(pcmk_schedulerd_api_reply_t *reply) { const char *msg_ref = NULL; if (!AM_I_DC) { return; } msg_ref = reply->data.graph.reference; if (msg_ref == NULL) { crm_err("%s - Ignoring calculation with no reference", CRM_OP_PECALC); } else if (pcmk__str_eq(msg_ref, controld_globals.fsa_pe_ref, pcmk__str_none)) { ha_msg_input_t fsa_input; xmlNode *crm_data_node; controld_stop_sched_timer(); /* do_te_invoke (which will eventually process the fsa_input we are constructing * here) requires that fsa_input.xml be non-NULL. That will only happen if * copy_ha_msg_input (which is called by register_fsa_input_adv) sees the * fsa_input.msg that it is expecting. The scheduler's IPC dispatch function * gave us the values we need, we just need to put them into XML. * * The name of the top level element here is irrelevant. Nothing checks it. */ fsa_input.msg = pcmk__xe_create(NULL, "dummy-reply"); crm_xml_add(fsa_input.msg, PCMK_XA_REFERENCE, msg_ref); crm_xml_add(fsa_input.msg, PCMK__XA_CRM_TGRAPH_IN, reply->data.graph.input); crm_data_node = pcmk__xe_create(fsa_input.msg, PCMK__XE_CRM_XML); pcmk__xml_copy(crm_data_node, reply->data.graph.tgraph); register_fsa_input_later(C_IPC_MESSAGE, I_PE_SUCCESS, &fsa_input); pcmk__xml_free(fsa_input.msg); } else { crm_info("%s calculation %s is obsolete", CRM_OP_PECALC, msg_ref); } } static void scheduler_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { pcmk_schedulerd_api_reply_t *reply = event_data; switch (event_type) { case pcmk_ipc_event_disconnect: handle_disconnect(); break; case pcmk_ipc_event_reply: handle_reply(reply); break; default: break; } } static bool new_schedulerd_ipc_connection(void) { int rc; controld_set_fsa_input_flags(R_PE_REQUIRED); if (schedulerd_api == NULL) { rc = pcmk_new_ipc_api(&schedulerd_api, pcmk_ipc_schedulerd); if (rc != pcmk_rc_ok) { crm_err("Error connecting to the scheduler: %s", pcmk_rc_str(rc)); return false; } } pcmk_register_ipc_callback(schedulerd_api, scheduler_event_callback, NULL); rc = pcmk__connect_ipc(schedulerd_api, pcmk_ipc_dispatch_main, 3); if (rc != pcmk_rc_ok) { crm_err("Error connecting to %s: %s", pcmk_ipc_name(schedulerd_api, true), pcmk_rc_str(rc)); return false; } controld_set_fsa_input_flags(R_PE_CONNECTED); return true; } static void do_pe_invoke_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *user_data); /* A_PE_START, A_PE_STOP, O_PE_RESTART */ void do_pe_control(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) { if (pcmk_is_set(action, A_PE_STOP)) { controld_clear_fsa_input_flags(R_PE_REQUIRED); pcmk_disconnect_ipc(schedulerd_api); handle_disconnect(); } if (pcmk_is_set(action, A_PE_START) && !pcmk_is_set(controld_globals.fsa_input_register, R_PE_CONNECTED)) { if (cur_state == S_STOPPING) { crm_info("Ignoring request to connect to scheduler while shutting down"); } else if (!new_schedulerd_ipc_connection()) { crm_warn("Could not connect to scheduler"); register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL); } } } static int fsa_pe_query = 0; static mainloop_timer_t *controld_sched_timer = NULL; // @TODO Make this a configurable cluster option if there's demand for it #define SCHED_TIMEOUT_MS (120000) /*! * \internal * \brief Handle a timeout waiting for scheduler reply * * \param[in] user_data Ignored * * \return FALSE (indicating that timer should not be restarted) */ static gboolean controld_sched_timeout(gpointer user_data) { if (AM_I_DC) { /* If this node is the DC but can't communicate with the scheduler, just * exit (and likely get fenced) so this node doesn't interfere with any * further DC elections. * * @TODO We could try something less drastic first, like disconnecting * and reconnecting to the scheduler, but something is likely going * seriously wrong, so perhaps it's better to just fail as quickly as * possible. */ crmd_exit(CRM_EX_FATAL); } return FALSE; } void controld_stop_sched_timer(void) { if ((controld_sched_timer != NULL) && (controld_globals.fsa_pe_ref != NULL)) { crm_trace("Stopping timer for scheduler reply %s", controld_globals.fsa_pe_ref); } mainloop_timer_stop(controld_sched_timer); } /*! * \internal * \brief Set the scheduler request currently being waited on * * \param[in] ref Request to expect reply to (or NULL for none) * * \note This function takes ownership of \p ref. */ void controld_expect_sched_reply(char *ref) { if (ref) { if (controld_sched_timer == NULL) { controld_sched_timer = mainloop_timer_add("scheduler_reply_timer", SCHED_TIMEOUT_MS, FALSE, controld_sched_timeout, NULL); } mainloop_timer_start(controld_sched_timer); } else { controld_stop_sched_timer(); } free(controld_globals.fsa_pe_ref); controld_globals.fsa_pe_ref = ref; } /*! * \internal * \brief Free the scheduler reply timer */ void controld_free_sched_timer(void) { if (controld_sched_timer != NULL) { mainloop_timer_del(controld_sched_timer); controld_sched_timer = NULL; } } /* A_PE_INVOKE */ void do_pe_invoke(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) { cib_t *cib_conn = controld_globals.cib_conn; if (AM_I_DC == FALSE) { crm_err("Not invoking scheduler because not DC: %s", fsa_action2string(action)); return; } if (!pcmk_is_set(controld_globals.fsa_input_register, R_PE_CONNECTED)) { if (pcmk_is_set(controld_globals.fsa_input_register, R_SHUTDOWN)) { crm_err("Cannot shut down gracefully without the scheduler"); register_fsa_input_before(C_FSA_INTERNAL, I_TERMINATE, NULL); } else { crm_info("Waiting for the scheduler to connect"); crmd_fsa_stall(FALSE); controld_set_fsa_action_flags(A_PE_START); controld_trigger_fsa(); } return; } if (cur_state != S_POLICY_ENGINE) { crm_notice("Not invoking scheduler because in state %s", fsa_state2string(cur_state)); return; } if (!pcmk_is_set(controld_globals.fsa_input_register, R_HAVE_CIB)) { crm_err("Attempted to invoke scheduler without consistent Cluster Information Base!"); /* start the join from scratch */ register_fsa_input_before(C_FSA_INTERNAL, I_ELECTION, NULL); return; } fsa_pe_query = cib_conn->cmds->query(cib_conn, NULL, NULL, cib_none); crm_debug("Query %d: Requesting the current CIB: %s", fsa_pe_query, fsa_state2string(controld_globals.fsa_state)); controld_expect_sched_reply(NULL); fsa_register_cib_callback(fsa_pe_query, NULL, do_pe_invoke_callback); } static void force_local_option(xmlNode *xml, const char *attr_name, const char *attr_value) { int max = 0; int lpc = 0; const char *xpath_base = NULL; char *xpath_string = NULL; xmlXPathObject *xpathObj = NULL; xpath_base = pcmk_cib_xpath_for(PCMK_XE_CRM_CONFIG); if (xpath_base == NULL) { crm_err(PCMK_XE_CRM_CONFIG " CIB element not known (bug?)"); return; } xpath_string = crm_strdup_printf("%s//%s//nvpair[@name='%s']", xpath_base, PCMK_XE_CLUSTER_PROPERTY_SET, attr_name); xpathObj = pcmk__xpath_search(xml->doc, xpath_string); max = pcmk__xpath_num_results(xpathObj); free(xpath_string); for (lpc = 0; lpc < max; lpc++) { xmlNode *match = pcmk__xpath_result(xpathObj, lpc); + + if (match == NULL) { + continue; + } crm_trace("Forcing %s/%s = %s", pcmk__xe_id(match), attr_name, attr_value); crm_xml_add(match, PCMK_XA_VALUE, attr_value); } if(max == 0) { xmlNode *configuration = NULL; xmlNode *crm_config = NULL; xmlNode *cluster_property_set = NULL; crm_trace("Creating %s-%s for %s=%s", PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, attr_name, attr_name, attr_value); configuration = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL, NULL); if (configuration == NULL) { configuration = pcmk__xe_create(xml, PCMK_XE_CONFIGURATION); } crm_config = pcmk__xe_first_child(configuration, PCMK_XE_CRM_CONFIG, NULL, NULL); if (crm_config == NULL) { crm_config = pcmk__xe_create(configuration, PCMK_XE_CRM_CONFIG); } cluster_property_set = pcmk__xe_first_child(crm_config, PCMK_XE_CLUSTER_PROPERTY_SET, NULL, NULL); if (cluster_property_set == NULL) { cluster_property_set = pcmk__xe_create(crm_config, PCMK_XE_CLUSTER_PROPERTY_SET); crm_xml_add(cluster_property_set, PCMK_XA_ID, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS); } xml = pcmk__xe_create(cluster_property_set, PCMK_XE_NVPAIR); pcmk__xe_set_id(xml, "%s-%s", PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, attr_name); crm_xml_add(xml, PCMK_XA_NAME, attr_name); crm_xml_add(xml, PCMK_XA_VALUE, attr_value); } xmlXPathFreeObject(xpathObj); } static void do_pe_invoke_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { char *ref = NULL; pid_t watchdog = pcmk__locate_sbd(); if (rc != pcmk_ok) { crm_err("Could not retrieve the Cluster Information Base: %s " QB_XS " rc=%d call=%d", pcmk_strerror(rc), rc, call_id); register_fsa_error_adv(C_FSA_INTERNAL, I_ERROR, NULL, NULL, __func__); return; } else if (call_id != fsa_pe_query) { crm_trace("Skipping superseded CIB query: %d (current=%d)", call_id, fsa_pe_query); return; } else if (!AM_I_DC || !pcmk_is_set(controld_globals.fsa_input_register, R_PE_CONNECTED)) { crm_debug("No need to invoke the scheduler anymore"); return; } else if (controld_globals.fsa_state != S_POLICY_ENGINE) { crm_debug("Discarding scheduler request in state: %s", fsa_state2string(controld_globals.fsa_state)); return; /* this callback counts as 1 */ } else if (num_cib_op_callbacks() > 1) { crm_debug("Re-asking for the CIB: %d other peer updates still pending", (num_cib_op_callbacks() - 1)); sleep(1); controld_set_fsa_action_flags(A_PE_INVOKE); controld_trigger_fsa(); return; } CRM_LOG_ASSERT(output != NULL); /* Refresh the remote node cache and the known node cache when the * scheduler is invoked */ pcmk__refresh_node_caches_from_cib(output); crm_xml_add(output, PCMK_XA_DC_UUID, controld_globals.our_uuid); pcmk__xe_set_bool_attr(output, PCMK_XA_HAVE_QUORUM, pcmk_is_set(controld_globals.flags, controld_has_quorum)); force_local_option(output, PCMK_OPT_HAVE_WATCHDOG, pcmk__btoa(watchdog)); if (pcmk_is_set(controld_globals.flags, controld_ever_had_quorum) && !pcmk__cluster_has_quorum()) { crm_xml_add_int(output, PCMK_XA_NO_QUORUM_PANIC, 1); } rc = pcmk_schedulerd_api_graph(schedulerd_api, output, &ref); if (rc != pcmk_rc_ok) { free(ref); crm_err("Could not contact the scheduler: %s " QB_XS " rc=%d", pcmk_rc_str(rc), rc); register_fsa_error_adv(C_FSA_INTERNAL, I_ERROR, NULL, NULL, __func__); } else { pcmk__assert(ref != NULL); controld_expect_sched_reply(ref); crm_debug("Invoking the scheduler: query=%d, ref=%s, seq=%llu, " "quorate=%s", fsa_pe_query, controld_globals.fsa_pe_ref, controld_globals.peer_seq, pcmk__flag_text(controld_globals.flags, controld_has_quorum)); } } diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c index 0b1561aa03..b74da0ffb5 100644 --- a/daemons/controld/controld_te_callbacks.c +++ b/daemons/controld/controld_te_callbacks.c @@ -1,524 +1,527 @@ /* * Copyright 2004-2025 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 // xmlXPathObject, etc. #include // An explicit PCMK_OPT_SHUTDOWN_LOCK of 0 means the lock has been cleared static bool shutdown_lock_cleared(xmlNode *lrm_resource) { time_t shutdown_lock = 0; return (crm_element_value_epoch(lrm_resource, PCMK_OPT_SHUTDOWN_LOCK, &shutdown_lock) == pcmk_ok) && (shutdown_lock == 0); } static void process_lrm_resource_diff(xmlNode *lrm_resource, const char *node) { for (xmlNode *rsc_op = pcmk__xe_first_child(lrm_resource, NULL, NULL, NULL); rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op, NULL)) { process_graph_event(rsc_op, node); } if (shutdown_lock_cleared(lrm_resource)) { // @TODO would be more efficient to abort once after transition done abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Shutdown lock cleared", lrm_resource); } } static void process_resource_updates(const char *node, xmlNode *xml, xmlNode *change, const char *op, const char *xpath) { xmlNode *rsc = NULL; if (xml == NULL) { return; } if (pcmk__xe_is(xml, PCMK__XE_LRM)) { xml = pcmk__xe_first_child(xml, PCMK__XE_LRM_RESOURCES, NULL, NULL); CRM_CHECK(xml != NULL, return); } CRM_CHECK(pcmk__xe_is(xml, PCMK__XE_LRM_RESOURCES), return); /* * Updates by, or in response to, TE actions will never contain updates * for more than one resource at a time, so such updates indicate an * LRM refresh. * * In that case, start a new transition rather than check each result * individually, which can result in _huge_ speedups in large clusters. * * Unfortunately, we can only do so when there are no pending actions. * Otherwise, we could mistakenly throw away those results here, and * the cluster will stall waiting for them and time out the operation. */ if ((controld_globals.transition_graph->pending == 0) && (xml->children != NULL) && (xml->children->next != NULL)) { crm_log_xml_trace(change, "lrm-refresh"); abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "History refresh", NULL); return; } for (rsc = pcmk__xe_first_child(xml, NULL, NULL, NULL); rsc != NULL; rsc = pcmk__xe_next(rsc, NULL)) { crm_trace("Processing %s", pcmk__xe_id(rsc)); process_lrm_resource_diff(rsc, node); } } static char *extract_node_uuid(const char *xpath) { char *mutable_path = pcmk__str_copy(xpath); char *node_uuid = NULL; char *search = NULL; char *match = NULL; match = strstr(mutable_path, PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'"); if (match == NULL) { free(mutable_path); return NULL; } match += strlen(PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'"); search = strchr(match, '\''); if (search == NULL) { free(mutable_path); return NULL; } search[0] = 0; node_uuid = pcmk__str_copy(match); free(mutable_path); return node_uuid; } static void abort_unless_down(const char *xpath, const char *op, xmlNode *change, const char *reason) { char *node_uuid = NULL; pcmk__graph_action_t *down = NULL; if (!pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none)) { abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason, change); return; } node_uuid = extract_node_uuid(xpath); if(node_uuid == NULL) { crm_err("Could not extract node ID from %s", xpath); abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason, change); return; } down = match_down_event(node_uuid); if (down == NULL) { crm_trace("Not expecting %s to be down (%s)", node_uuid, xpath); abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason, change); } else { crm_trace("Expecting changes to %s (%s)", node_uuid, xpath); } free(node_uuid); } static void process_op_deletion(const char *xpath, xmlNode *change) { char *mutable_key = pcmk__str_copy(xpath); char *key; char *node_uuid; // Extract the part of xpath between last pair of single quotes key = strrchr(mutable_key, '\''); if (key != NULL) { *key = '\0'; key = strrchr(mutable_key, '\''); } if (key == NULL) { crm_warn("Ignoring malformed CIB update (resource deletion of %s)", xpath); free(mutable_key); return; } ++key; node_uuid = extract_node_uuid(xpath); if (confirm_cancel_action(key, node_uuid) == FALSE) { abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Resource operation removal", change); } free(mutable_key); free(node_uuid); } static void process_delete_diff(const char *xpath, const char *op, xmlNode *change) { if (strstr(xpath, "/" PCMK__XE_LRM_RSC_OP "[")) { process_op_deletion(xpath, change); } else if (strstr(xpath, "/" PCMK__XE_LRM "[")) { abort_unless_down(xpath, op, change, "Resource state removal"); } else if (strstr(xpath, "/" PCMK__XE_NODE_STATE "[")) { abort_unless_down(xpath, op, change, "Node state removal"); } else { crm_trace("Ignoring delete of %s", xpath); } } static void process_node_state_diff(xmlNode *state, xmlNode *change, const char *op, const char *xpath) { xmlNode *lrm = pcmk__xe_first_child(state, PCMK__XE_LRM, NULL, NULL); process_resource_updates(pcmk__xe_id(state), lrm, change, op, xpath); } static void process_status_diff(xmlNode *status, xmlNode *change, const char *op, const char *xpath) { for (xmlNode *state = pcmk__xe_first_child(status, NULL, NULL, NULL); state != NULL; state = pcmk__xe_next(state, NULL)) { process_node_state_diff(state, change, op, xpath); } } static void process_cib_diff(xmlNode *cib, xmlNode *change, const char *op, const char *xpath) { xmlNode *status = pcmk__xe_first_child(cib, PCMK_XE_STATUS, NULL, NULL); xmlNode *config = pcmk__xe_first_child(cib, PCMK_XE_CONFIGURATION, NULL, NULL); if (status) { process_status_diff(status, change, op, xpath); } if (config) { abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Non-status-only change", change); } } static int te_update_diff_element(xmlNode *change, void *userdata) { xmlNode *match = NULL; const char *name = NULL; const char *xpath = crm_element_value(change, PCMK_XA_PATH); // Possible ops: create, modify, delete, move const char *op = crm_element_value(change, PCMK_XA_OPERATION); // Ignore uninteresting updates if (op == NULL) { return pcmk_rc_ok; } else if (xpath == NULL) { crm_trace("Ignoring %s change for version field", op); return pcmk_rc_ok; } else if ((strcmp(op, PCMK_VALUE_MOVE) == 0) && (strstr(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES) == NULL)) { /* We still need to consider moves within the resources section, * since they affect placement order. */ crm_trace("Ignoring move change at %s", xpath); return pcmk_rc_ok; } // Find the result of create/modify ops if (strcmp(op, PCMK_VALUE_CREATE) == 0) { match = change->children; } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) { match = pcmk__xe_first_child(change, PCMK_XE_CHANGE_RESULT, NULL, NULL); if(match) { match = match->children; } } else if (!pcmk__str_any_of(op, PCMK_VALUE_DELETE, PCMK_VALUE_MOVE, NULL)) { crm_warn("Ignoring malformed CIB update (%s operation on %s is unrecognized)", op, xpath); return pcmk_rc_ok; } if (match) { if (match->type == XML_COMMENT_NODE) { crm_trace("Ignoring %s operation for comment at %s", op, xpath); return pcmk_rc_ok; } name = (const char *)match->name; } crm_trace("Handling %s operation for %s%s%s", op, (xpath? xpath : "CIB"), (name? " matched by " : ""), (name? name : "")); if (strstr(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION)) { abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Configuration change", change); return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for } else if (strstr(xpath, "/" PCMK_XE_TICKETS) || pcmk__str_eq(name, PCMK_XE_TICKETS, pcmk__str_none)) { abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Ticket attribute change", change); return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for } else if (strstr(xpath, "/" PCMK__XE_TRANSIENT_ATTRIBUTES "[") || pcmk__str_eq(name, PCMK__XE_TRANSIENT_ATTRIBUTES, pcmk__str_none)) { abort_unless_down(xpath, op, change, "Transient attribute change"); return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) { process_delete_diff(xpath, op, change); } else if (name == NULL) { crm_warn("Ignoring malformed CIB update (%s at %s has no result)", op, xpath); } else if (strcmp(name, PCMK_XE_CIB) == 0) { process_cib_diff(match, change, op, xpath); } else if (strcmp(name, PCMK_XE_STATUS) == 0) { process_status_diff(match, change, op, xpath); } else if (strcmp(name, PCMK__XE_NODE_STATE) == 0) { process_node_state_diff(match, change, op, xpath); } else if (strcmp(name, PCMK__XE_LRM) == 0) { process_resource_updates(pcmk__xe_id(match), match, change, op, xpath); } else if (strcmp(name, PCMK__XE_LRM_RESOURCES) == 0) { char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM); process_resource_updates(local_node, match, change, op, xpath); free(local_node); } else if (strcmp(name, PCMK__XE_LRM_RESOURCE) == 0) { char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM); process_lrm_resource_diff(match, local_node); free(local_node); } else if (strcmp(name, PCMK__XE_LRM_RSC_OP) == 0) { char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM); process_graph_event(match, local_node); free(local_node); } else { crm_warn("Ignoring malformed CIB update (%s at %s has unrecognized result %s)", op, xpath, name); } return pcmk_rc_ok; } void te_update_diff(const char *event, xmlNode * msg) { xmlNode *wrapper = NULL; xmlNode *diff = NULL; const char *op = NULL; int rc = -EINVAL; int format = 1; int p_add[] = { 0, 0, 0 }; int p_del[] = { 0, 0, 0 }; CRM_CHECK(msg != NULL, return); crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc); if (controld_globals.transition_graph == NULL) { crm_trace("No graph"); return; } else if (rc < pcmk_ok) { crm_trace("Filter rc=%d (%s)", rc, pcmk_strerror(rc)); return; } else if (controld_globals.transition_graph->complete && (controld_globals.fsa_state != S_IDLE) && (controld_globals.fsa_state != S_TRANSITION_ENGINE) && (controld_globals.fsa_state != S_POLICY_ENGINE)) { crm_trace("Filter state=%s (complete)", fsa_state2string(controld_globals.fsa_state)); return; } op = crm_element_value(msg, PCMK__XA_CIB_OP); wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL); diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); xml_patch_versions(diff, p_add, p_del); crm_debug("Processing (%s) diff: %d.%d.%d -> %d.%d.%d (%s)", op, p_del[0], p_del[1], p_del[2], p_add[0], p_add[1], p_add[2], fsa_state2string(controld_globals.fsa_state)); crm_element_value_int(diff, PCMK_XA_FORMAT, &format); if (format == 2) { crm_log_xml_trace(diff, "patch"); pcmk__xe_foreach_child(diff, NULL, te_update_diff_element, NULL); } else { crm_warn("Ignoring malformed CIB update (unknown patch format %d)", format); } controld_remove_all_outside_events(); } void process_te_message(xmlNode * msg, xmlNode * xml_data) { const char *value = NULL; xmlXPathObject *xpathObj = NULL; int nmatches = 0; CRM_CHECK(msg != NULL, return); // Transition requests must specify transition engine as subsystem value = crm_element_value(msg, PCMK__XA_CRM_SYS_TO); if (pcmk__str_empty(value) || !pcmk__str_eq(value, CRM_SYSTEM_TENGINE, pcmk__str_none)) { crm_info("Received invalid transition request: subsystem '%s' not '" CRM_SYSTEM_TENGINE "'", pcmk__s(value, "")); return; } // Only the lrm_invoke command is supported as a transition request value = crm_element_value(msg, PCMK__XA_CRM_TASK); if (!pcmk__str_eq(value, CRM_OP_INVOKE_LRM, pcmk__str_none)) { crm_info("Received invalid transition request: command '%s' not '" CRM_OP_INVOKE_LRM "'", pcmk__s(value, "")); return; } // Transition requests must be marked as coming from the executor value = crm_element_value(msg, PCMK__XA_CRM_SYS_FROM); if (!pcmk__str_eq(value, CRM_SYSTEM_LRMD, pcmk__str_none)) { crm_info("Received invalid transition request: from '%s' not '" CRM_SYSTEM_LRMD "'", pcmk__s(value, "")); return; } crm_debug("Processing transition request with ref='%s' origin='%s'", pcmk__s(crm_element_value(msg, PCMK_XA_REFERENCE), ""), pcmk__s(crm_element_value(msg, PCMK__XA_SRC), "")); xpathObj = pcmk__xpath_search(xml_data->doc, "//" PCMK__XE_LRM_RSC_OP); nmatches = pcmk__xpath_num_results(xpathObj); if (nmatches == 0) { crm_err("Received transition request with no results (bug?)"); } else { for (int lpc = 0; lpc < nmatches; lpc++) { xmlNode *rsc_op = pcmk__xpath_result(xpathObj, lpc); - const char *node = get_node_id(rsc_op); - process_graph_event(rsc_op, node); + if (rsc_op != NULL) { + const char *node = get_node_id(rsc_op); + + process_graph_event(rsc_op, node); + } } } xmlXPathFreeObject(xpathObj); } void cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { if (rc < pcmk_ok) { crm_err("Update %d FAILED: %s", call_id, pcmk_strerror(rc)); } } /*! * \brief Handle a timeout in node-to-node communication * * \param[in,out] data Pointer to graph action * * \return FALSE (indicating that source should be not be re-added) */ gboolean action_timer_callback(gpointer data) { pcmk__graph_action_t *action = (pcmk__graph_action_t *) data; const char *task = NULL; const char *on_node = NULL; const char *via_node = NULL; CRM_CHECK(data != NULL, return FALSE); stop_te_timer(action); task = crm_element_value(action->xml, PCMK_XA_OPERATION); on_node = crm_element_value(action->xml, PCMK__META_ON_NODE); via_node = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE); if (controld_globals.transition_graph->complete) { crm_notice("Node %s did not send %s result (via %s) within %dms " "(ignoring because transition not in progress)", (on_node? on_node : ""), (task? task : "unknown action"), (via_node? via_node : "controller"), action->timeout); } else { /* fail the action */ crm_err("Node %s did not send %s result (via %s) within %dms " "(action timeout plus " PCMK_OPT_CLUSTER_DELAY ")", (on_node? on_node : ""), (task? task : "unknown action"), (via_node? via_node : "controller"), (action->timeout + controld_globals.transition_graph->network_delay)); pcmk__log_graph_action(LOG_ERR, action); pcmk__set_graph_action_flags(action, pcmk__graph_action_failed); te_action_confirmed(action, controld_globals.transition_graph); abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Action lost", NULL); // Record timeout in the CIB if appropriate if ((action->type == pcmk__rsc_graph_action) && controld_action_is_recordable(task)) { controld_record_action_timeout(action); } } return FALSE; } diff --git a/daemons/fenced/fenced_cib.c b/daemons/fenced/fenced_cib.c index 796a858b1c..6bca8f8eda 100644 --- a/daemons/fenced/fenced_cib.c +++ b/daemons/fenced/fenced_cib.c @@ -1,617 +1,620 @@ /* * Copyright 2009-2025 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 // xmlXPathObject, etc. #include #include #include #include #include #include static xmlNode *local_cib = NULL; static cib_t *cib_api = NULL; static bool have_cib_devices = FALSE; /*! * \internal * \brief Check whether a node has a specific attribute name/value * * \param[in] node Name of node to check * \param[in] name Name of an attribute to look for * \param[in] value The value the named attribute needs to be set to in order to be considered a match * * \return TRUE if the locally cached CIB has the specified node attribute */ gboolean node_has_attr(const char *node, const char *name, const char *value) { GString *xpath = NULL; xmlNode *match; CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL) && (value != NULL), return FALSE); /* Search for the node's attributes in the CIB. While the schema allows * multiple sets of instance attributes, and allows instance attributes to * use id-ref to reference values elsewhere, that is intended for resources, * so we ignore that here. */ xpath = g_string_sized_new(256); pcmk__g_strcat(xpath, "//" PCMK_XE_NODES "/" PCMK_XE_NODE "[@" PCMK_XA_UNAME "='", node, "']" "/" PCMK_XE_INSTANCE_ATTRIBUTES "/" PCMK_XE_NVPAIR "[@" PCMK_XA_NAME "='", name, "' " "and @" PCMK_XA_VALUE "='", value, "']", NULL); match = get_xpath_object((const char *) xpath->str, local_cib, LOG_NEVER); g_string_free(xpath, TRUE); return (match != NULL); } static void add_topology_level(xmlNode *match) { char *desc = NULL; pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; CRM_CHECK(match != NULL, return); fenced_register_level(match, &desc, &result); fenced_send_config_notification(STONITH_OP_LEVEL_ADD, &result, desc); pcmk__reset_result(&result); free(desc); } static void topology_remove_helper(const char *node, int level) { char *desc = NULL; pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; xmlNode *data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL); crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__); crm_xml_add_int(data, PCMK_XA_INDEX, level); crm_xml_add(data, PCMK_XA_TARGET, node); fenced_unregister_level(data, &desc, &result); fenced_send_config_notification(STONITH_OP_LEVEL_DEL, &result, desc); pcmk__reset_result(&result); pcmk__xml_free(data); free(desc); } static void remove_topology_level(xmlNode *match) { int index = 0; char *key = NULL; CRM_CHECK(match != NULL, return); key = stonith_level_key(match, fenced_target_by_unknown); crm_element_value_int(match, PCMK_XA_INDEX, &index); topology_remove_helper(key, index); free(key); } static void register_fencing_topology(xmlXPathObjectPtr xpathObj) { int max = pcmk__xpath_num_results(xpathObj); for (int lpc = 0; lpc < max; lpc++) { xmlNode *match = pcmk__xpath_result(xpathObj, lpc); + if (match == NULL) { + continue; + } remove_topology_level(match); add_topology_level(match); } } /* Fencing */ void fencing_topology_init(void) { xmlXPathObject *xpathObj = NULL; const char *xpath = "//" PCMK_XE_FENCING_LEVEL; crm_trace("Full topology refresh"); free_topology_list(); init_topology_list(); /* Grab everything */ xpathObj = pcmk__xpath_search(local_cib->doc, xpath); register_fencing_topology(xpathObj); xmlXPathFreeObject(xpathObj); } #define XPATH_WATCHDOG_TIMEOUT "//" PCMK_XE_NVPAIR \ "[@" PCMK_XA_NAME "='" \ PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "']" static void update_stonith_watchdog_timeout_ms(xmlNode *cib) { long long timeout_ms = 0; xmlNode *stonith_watchdog_xml = NULL; const char *value = NULL; // @TODO An XPath search can't handle multiple instances or rules stonith_watchdog_xml = get_xpath_object(XPATH_WATCHDOG_TIMEOUT, cib, LOG_NEVER); if (stonith_watchdog_xml) { value = crm_element_value(stonith_watchdog_xml, PCMK_XA_VALUE); } if (value) { timeout_ms = crm_get_msec(value); } if (timeout_ms < 0) { timeout_ms = pcmk__auto_stonith_watchdog_timeout(); } stonith_watchdog_timeout_ms = timeout_ms; } /*! * \internal * \brief Update all STONITH device definitions based on current CIB */ static void cib_devices_update(void) { GHashTableIter iter; stonith_device_t *device = NULL; crm_info("Updating devices to version %s.%s.%s", crm_element_value(local_cib, PCMK_XA_ADMIN_EPOCH), crm_element_value(local_cib, PCMK_XA_EPOCH), crm_element_value(local_cib, PCMK_XA_NUM_UPDATES)); g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) { if (device->cib_registered) { device->dirty = TRUE; } } /* have list repopulated if cib has a watchdog-fencing-resource TODO: keep a cached list for queries happening while we are refreshing */ g_list_free_full(stonith_watchdog_targets, free); stonith_watchdog_targets = NULL; fenced_scheduler_run(local_cib); g_hash_table_iter_init(&iter, device_list); while (g_hash_table_iter_next(&iter, NULL, (void **)&device)) { if (device->dirty) { g_hash_table_iter_remove(&iter); } } } static void update_cib_stonith_devices(const char *event, xmlNode * msg) { int format = 1; xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL); xmlNode *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); char *reason = NULL; CRM_CHECK(patchset != NULL, return); crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); if (format != 2) { crm_warn("Unknown patch format: %d", format); return; } for (xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL, NULL); change != NULL; change = pcmk__xe_next(change, NULL)) { const char *op = crm_element_value(change, PCMK_XA_OPERATION); const char *xpath = crm_element_value(change, PCMK_XA_PATH); const char *shortpath = NULL; if (pcmk__str_eq(op, PCMK_VALUE_MOVE, pcmk__str_null_matches) || (strstr(xpath, "/" PCMK_XE_STATUS) != NULL)) { continue; } if (pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none) && (strstr(xpath, "/" PCMK_XE_PRIMITIVE) != NULL)) { const char *rsc_id = NULL; char *search = NULL; char *mutable = NULL; if ((strstr(xpath, PCMK_XE_INSTANCE_ATTRIBUTES) != NULL) || (strstr(xpath, PCMK_XE_META_ATTRIBUTES) != NULL)) { reason = pcmk__str_copy("(meta) attribute deleted from " "resource"); break; } mutable = pcmk__str_copy(xpath); rsc_id = strstr(mutable, PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "=\'"); if (rsc_id != NULL) { rsc_id += strlen(PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "=\'"); search = strchr(rsc_id, '\''); } if (search != NULL) { *search = 0; stonith_device_remove(rsc_id, true); /* watchdog_device_update called afterwards to fall back to implicit definition if needed */ } else { crm_warn("Ignoring malformed CIB update (resource deletion)"); } free(mutable); } else if (strstr(xpath, "/" PCMK_XE_RESOURCES) || strstr(xpath, "/" PCMK_XE_CONSTRAINTS) || strstr(xpath, "/" PCMK_XE_RSC_DEFAULTS)) { shortpath = strrchr(xpath, '/'); pcmk__assert(shortpath != NULL); reason = crm_strdup_printf("%s %s", op, shortpath+1); break; } } if (reason != NULL) { crm_info("Updating device list from CIB: %s", reason); cib_devices_update(); free(reason); } else { crm_trace("No updates for device list found in CIB"); } } static void watchdog_device_update(void) { if (stonith_watchdog_timeout_ms > 0) { if (!g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) && !stonith_watchdog_targets) { /* getting here watchdog-fencing enabled, no device there yet and reason isn't stonith_watchdog_targets preventing that */ int rc; xmlNode *xml; xml = create_device_registration_xml( STONITH_WATCHDOG_ID, st_namespace_internal, STONITH_WATCHDOG_AGENT, NULL, /* stonith_device_register will add our own name as PCMK_STONITH_HOST_LIST param so we can skip that here */ NULL); rc = stonith_device_register(xml, TRUE); pcmk__xml_free(xml); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); exit_code = CRM_EX_FATAL; crm_crit("Cannot register watchdog pseudo fence agent: %s", pcmk_rc_str(rc)); stonith_shutdown(0); } } } else if (g_hash_table_lookup(device_list, STONITH_WATCHDOG_ID) != NULL) { /* be silent if no device - todo parameter to stonith_device_remove */ stonith_device_remove(STONITH_WATCHDOG_ID, true); } } /*! * \internal * \brief Query the full CIB * * \return Standard Pacemaker return code */ static int fenced_query_cib(void) { int rc = pcmk_ok; crm_trace("Re-requesting full CIB"); rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { pcmk__assert(local_cib != NULL); } else { crm_err("Couldn't retrieve the CIB: %s " QB_XS " rc=%d", pcmk_rc_str(rc), rc); } return rc; } static void update_fencing_topology(const char *event, xmlNode *msg) { xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL); xmlNode *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); int format = 1; int add[] = { 0, 0, 0 }; int del[] = { 0, 0, 0 }; CRM_CHECK(patchset != NULL, return); crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); if (format != 2) { crm_warn("Unknown patch format: %d", format); return; } xml_patch_versions(patchset, add, del); for (xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL, NULL); change != NULL; change = pcmk__xe_next(change, NULL)) { const char *op = crm_element_value(change, PCMK_XA_OPERATION); const char *xpath = crm_element_value(change, PCMK_XA_PATH); if (op == NULL) { continue; } if (strstr(xpath, "/" PCMK_XE_FENCING_LEVEL) != NULL) { // Change to a specific entry crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); if (strcmp(op, PCMK_VALUE_DELETE) == 0) { /* We have only path and ID, which is not enough info to remove * a specific entry. Re-initialize the whole topology. */ crm_info("Re-initializing fencing topology after %s operation " "%d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } if (strcmp(op, PCMK_VALUE_CREATE) == 0) { add_topology_level(change->children); } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) { xmlNode *match = pcmk__xe_first_child(change, PCMK_XE_CHANGE_RESULT, NULL, NULL); if (match != NULL) { remove_topology_level(match->children); add_topology_level(match->children); } } continue; } if (strstr(xpath, "/" PCMK_XE_FENCING_TOPOLOGY) != NULL) { // Change to the topology in general crm_info("Re-initializing fencing topology after top-level " "%s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } if ((strstr(xpath, "/" PCMK_XE_CONFIGURATION) != NULL) && (pcmk__xe_first_child(change, PCMK_XE_FENCING_TOPOLOGY, NULL, NULL) != NULL) && pcmk__str_any_of(op, PCMK_VALUE_CREATE, PCMK_VALUE_DELETE, NULL)) { // Topology was created or entire configuration section was deleted crm_info("Re-initializing fencing topology after top-level " "%s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); fencing_topology_init(); return; } crm_trace("Nothing for us in %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath); } } static void update_cib_cache_cb(const char *event, xmlNode * msg) { long long timeout_ms_saved = stonith_watchdog_timeout_ms; bool need_full_refresh = false; if(!have_cib_devices) { crm_trace("Skipping updates until we get a full dump"); return; } else if(msg == NULL) { crm_trace("Missing %s update", event); return; } /* Maintain a local copy of the CIB so that we have full access * to device definitions, location constraints, and node attributes */ if (local_cib != NULL) { int rc = pcmk_ok; xmlNode *wrapper = NULL; xmlNode *patchset = NULL; crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc); if (rc != pcmk_ok) { return; } wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL); patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); rc = xml_apply_patchset(local_cib, patchset, TRUE); switch (rc) { case pcmk_ok: case -pcmk_err_old_data: break; case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); pcmk__xml_free(local_cib); local_cib = NULL; break; default: crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); pcmk__xml_free(local_cib); local_cib = NULL; } } if (local_cib == NULL) { if (fenced_query_cib() != pcmk_rc_ok) { return; } need_full_refresh = true; } pcmk__refresh_node_caches_from_cib(local_cib); update_stonith_watchdog_timeout_ms(local_cib); if (timeout_ms_saved != stonith_watchdog_timeout_ms) { need_full_refresh = true; } if (need_full_refresh) { fencing_topology_init(); cib_devices_update(); } else { // Partial refresh update_fencing_topology(event, msg); update_cib_stonith_devices(event, msg); } watchdog_device_update(); } static void init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { crm_info("Updating device list from CIB"); have_cib_devices = TRUE; local_cib = pcmk__xml_copy(NULL, output); pcmk__refresh_node_caches_from_cib(local_cib); update_stonith_watchdog_timeout_ms(local_cib); fencing_topology_init(); cib_devices_update(); watchdog_device_update(); } static void cib_connection_destroy(gpointer user_data) { if (stonith_shutdown_flag) { crm_info("Connection to the CIB manager closed"); return; } else { crm_crit("Lost connection to the CIB manager, shutting down"); } if (cib_api) { cib_api->cmds->signoff(cib_api); } stonith_shutdown(0); } /*! * \internal * \brief Disconnect from CIB manager */ void fenced_cib_cleanup(void) { if (cib_api != NULL) { cib_api->cmds->del_notify_callback(cib_api, PCMK__VALUE_CIB_DIFF_NOTIFY, update_cib_cache_cb); cib__clean_up_connection(&cib_api); } pcmk__xml_free(local_cib); local_cib = NULL; } void setup_cib(void) { int rc, retries = 0; cib_api = cib_new(); if (cib_api == NULL) { crm_err("No connection to the CIB manager"); return; } do { sleep(retries); rc = cib_api->cmds->signon(cib_api, crm_system_name, cib_command); } while (rc == -ENOTCONN && ++retries < 5); if (rc != pcmk_ok) { crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc); return; } rc = cib_api->cmds->add_notify_callback(cib_api, PCMK__VALUE_CIB_DIFF_NOTIFY, update_cib_cache_cb); if (rc != pcmk_ok) { crm_err("Could not set CIB notification callback"); return; } rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_none); cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb", init_cib_cache_cb); cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy); crm_info("Watching for fencing topology changes"); } diff --git a/lib/fencing/st_rhcs.c b/lib/fencing/st_rhcs.c index c25ea94d9f..6d0c6f9447 100644 --- a/lib/fencing/st_rhcs.c +++ b/lib/fencing/st_rhcs.c @@ -1,328 +1,330 @@ /* * Copyright 2004-2025 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 // xmlXPathObject, etc. #include #include #include #include #include "fencing_private.h" /*! * \internal * \brief \c scandir() filter for RHCS fence agents * * \param[in] entry Directory entry * * \retval 1 if \p entry is a regular file whose name begins with \c "fence_" * \retval 0 otherwise */ static int rhcs_agent_filter(const struct dirent *entry) { char *buf = NULL; struct stat sb; int rc = 0; if (!pcmk__starts_with(entry->d_name, "fence_")) { goto done; } // glibc doesn't enforce PATH_MAX, so don't limit buf size buf = crm_strdup_printf(PCMK__FENCE_BINDIR "/%s", entry->d_name); if ((stat(buf, &sb) != 0) || !S_ISREG(sb.st_mode)) { goto done; } rc = 1; done: free(buf); return rc; } /*! * \internal * \brief Add available RHCS-compatible agents to a list * * \param[in,out] List to add to * * \return Number of agents added */ int stonith__list_rhcs_agents(stonith_key_value_t **devices) { struct dirent **namelist = NULL; const int file_num = scandir(PCMK__FENCE_BINDIR, &namelist, rhcs_agent_filter, alphasort); if (file_num < 0) { int rc = errno; crm_err("Could not list " PCMK__FENCE_BINDIR ": %s", pcmk_rc_str(rc)); free(namelist); return 0; } for (int i = 0; i < file_num; i++) { *devices = stonith_key_value_add(*devices, NULL, namelist[i]->d_name); free(namelist[i]); } free(namelist); return file_num; } static void stonith_rhcs_parameter_not_required(xmlNode *metadata, const char *parameter) { char *xpath = NULL; xmlXPathObject *xpathObj = NULL; CRM_CHECK(metadata != NULL, return); CRM_CHECK(parameter != NULL, return); xpath = crm_strdup_printf("//" PCMK_XE_PARAMETER "[@" PCMK_XA_NAME "='%s']", parameter); /* Fudge metadata so that the parameter isn't required in config * Pacemaker handles and adds it */ xpathObj = pcmk__xpath_search(metadata->doc, xpath); if (pcmk__xpath_num_results(xpathObj) > 0) { xmlNode *tmp = pcmk__xpath_result(xpathObj, 0); - crm_xml_add(tmp, "required", "0"); + if (tmp != NULL) { + crm_xml_add(tmp, "required", "0"); + } } xmlXPathFreeObject(xpathObj); free(xpath); } /*! * \brief Execute RHCS-compatible agent's metadata action * * \param[in] agent Agent to execute * \param[in] timeout_sec Action timeout * \param[out] metadata Where to store output xmlNode (or NULL to ignore) */ static int stonith__rhcs_get_metadata(const char *agent, int timeout_sec, xmlNode **metadata) { xmlNode *xml = NULL; xmlNode *actions = NULL; xmlXPathObject *xpathObj = NULL; stonith_action_t *action = stonith__action_create(agent, PCMK_ACTION_METADATA, NULL, 0, timeout_sec, NULL, NULL, NULL); int rc = stonith__execute(action); pcmk__action_result_t *result = stonith__action_result(action); if (result == NULL) { if (rc < 0) { crm_warn("Could not execute metadata action for %s: %s " QB_XS " rc=%d", agent, pcmk_strerror(rc), rc); } stonith__destroy_action(action); return rc; } if (result->execution_status != PCMK_EXEC_DONE) { crm_warn("Could not execute metadata action for %s: %s", agent, pcmk_exec_status_str(result->execution_status)); rc = pcmk_rc2legacy(stonith__result2rc(result)); stonith__destroy_action(action); return rc; } if (!pcmk__result_ok(result)) { crm_warn("Metadata action for %s returned error code %d", agent, result->exit_status); rc = pcmk_rc2legacy(stonith__result2rc(result)); stonith__destroy_action(action); return rc; } if (result->action_stdout == NULL) { crm_warn("Metadata action for %s returned no data", agent); stonith__destroy_action(action); return -ENODATA; } xml = pcmk__xml_parse(result->action_stdout); stonith__destroy_action(action); if (xml == NULL) { crm_warn("Metadata for %s is invalid", agent); return -pcmk_err_schema_validation; } xpathObj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_ACTIONS); if (pcmk__xpath_num_results(xpathObj) > 0) { actions = pcmk__xpath_result(xpathObj, 0); } xmlXPathFreeObject(xpathObj); // Add start and stop (implemented by pacemaker, not agent) to meta-data xpathObj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_ACTION "[@" PCMK_XA_NAME "='" PCMK_ACTION_STOP "']"); if (pcmk__xpath_num_results(xpathObj) == 0) { xmlNode *tmp = NULL; const char *timeout_str = NULL; timeout_str = pcmk__readable_interval(PCMK_DEFAULT_ACTION_TIMEOUT_MS); tmp = pcmk__xe_create(actions, PCMK_XE_ACTION); crm_xml_add(tmp, PCMK_XA_NAME, PCMK_ACTION_STOP); crm_xml_add(tmp, PCMK_META_TIMEOUT, timeout_str); tmp = pcmk__xe_create(actions, PCMK_XE_ACTION); crm_xml_add(tmp, PCMK_XA_NAME, PCMK_ACTION_START); crm_xml_add(tmp, PCMK_META_TIMEOUT, timeout_str); } xmlXPathFreeObject(xpathObj); // Fudge metadata so parameters are not required in config (pacemaker adds them) stonith_rhcs_parameter_not_required(xml, STONITH_ATTR_ACTION_OP); stonith_rhcs_parameter_not_required(xml, "plug"); stonith_rhcs_parameter_not_required(xml, "port"); if (metadata) { *metadata = xml; } else { pcmk__xml_free(xml); } return pcmk_ok; } /*! * \brief Retrieve metadata for RHCS-compatible fence agent * * \param[in] agent Agent to execute * \param[in] timeout_sec Action timeout * \param[out] output Where to store action output (or NULL to ignore) */ int stonith__rhcs_metadata(const char *agent, int timeout_sec, char **output) { GString *buffer = NULL; xmlNode *xml = NULL; int rc = stonith__rhcs_get_metadata(agent, timeout_sec, &xml); if (rc != pcmk_ok) { goto done; } buffer = g_string_sized_new(1024); pcmk__xml_string(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buffer, 0); if (pcmk__str_empty(buffer->str)) { rc = -pcmk_err_schema_validation; goto done; } if (output != NULL) { pcmk__str_update(output, buffer->str); } done: if (buffer != NULL) { g_string_free(buffer, TRUE); } pcmk__xml_free(xml); return rc; } bool stonith__agent_is_rhcs(const char *agent) { struct stat prop; char *buffer = crm_strdup_printf(PCMK__FENCE_BINDIR "/%s", agent); int rc = stat(buffer, &prop); free(buffer); return (rc >= 0) && S_ISREG(prop.st_mode); } int stonith__rhcs_validate(stonith_t *st, int call_options, const char *target, const char *agent, GHashTable *params, const char * host_arg, int timeout, char **output, char **error_output) { int rc = pcmk_ok; int remaining_timeout = timeout; xmlNode *metadata = NULL; stonith_action_t *action = NULL; pcmk__action_result_t *result = NULL; if (host_arg == NULL) { time_t start_time = time(NULL); rc = stonith__rhcs_get_metadata(agent, remaining_timeout, &metadata); if (rc == pcmk_ok) { uint32_t device_flags = 0; stonith__device_parameter_flags(&device_flags, agent, metadata); if (pcmk_is_set(device_flags, st_device_supports_parameter_port)) { host_arg = "port"; } else if (pcmk_is_set(device_flags, st_device_supports_parameter_plug)) { host_arg = "plug"; } } pcmk__xml_free(metadata); remaining_timeout -= time(NULL) - start_time; if (rc == -ETIME || remaining_timeout <= 0 ) { return -ETIME; } } else if (pcmk__str_eq(host_arg, PCMK_VALUE_NONE, pcmk__str_casei)) { host_arg = NULL; } action = stonith__action_create(agent, PCMK_ACTION_VALIDATE_ALL, target, 0, remaining_timeout, params, NULL, host_arg); rc = stonith__execute(action); result = stonith__action_result(action); if (result != NULL) { rc = pcmk_rc2legacy(stonith__result2rc(result)); // Take ownership of output so stonith__destroy_action() doesn't free it if (output != NULL) { *output = result->action_stdout; result->action_stdout = NULL; } if (error_output != NULL) { *error_output = result->action_stderr; result->action_stderr = NULL; } } stonith__destroy_action(action); return rc; } diff --git a/lib/pacemaker/tests/pcmk_ticket/pcmk__get_ticket_state_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk__get_ticket_state_test.c index cba881968c..2eefed8d6c 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk__get_ticket_state_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk__get_ticket_state_test.c @@ -1,184 +1,188 @@ /* * Copyright 2024-2025 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 // xmlXPathObject, etc. #include static char *cib_path = NULL; static void cib_not_connected(void **state) { xmlNode *xml = NULL; cib_t *cib = cib_new(); /* Without any special setup, cib_new() here will use the native CIB which * means IPC calls. But there's nothing listening for those calls, so * signon() will return ENOTCONN. Check that we handle that. */ assert_int_equal(pcmk__get_ticket_state(cib, "ticketA", &xml), ENOTCONN); cib__clean_up_connection(&cib); } static int setup_test(void **state) { cib_path = pcmk__cib_test_copy_cib("tickets.xml"); if (cib_path == NULL) { return -1; } return 0; } static int teardown_test(void **state) { pcmk__cib_test_cleanup(cib_path); cib_path = NULL; return 0; } static void bad_arguments(void **state) { xmlNode *xml = NULL; cib_t *cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); pcmk__assert_asserts(pcmk__get_ticket_state(NULL, "ticketA", &xml)); pcmk__assert_asserts(pcmk__get_ticket_state(cib, "ticketA", NULL)); cib__clean_up_connection(&cib); } static void unknown_ticket(void **state) { xmlNode *xml = NULL; cib_t *cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); assert_int_equal(pcmk__get_ticket_state(cib, "XYZ", &xml), ENXIO); pcmk__xml_free(xml); cib__clean_up_connection(&cib); } static void ticket_exists(void **state) { xmlNode *xml = NULL; xmlXPathObject *xpath_obj = NULL; cib_t *cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); assert_int_equal(pcmk__get_ticket_state(cib, "ticketA", &xml), pcmk_rc_ok); /* Verify that the XML result has only one , and that its ID is * what we asked for. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK__XE_TICKET_STATE "[@" PCMK_XA_ID "=\"ticketA\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); cib__clean_up_connection(&cib); } static void multiple_tickets(void **state) { xmlNode *xml = NULL; xmlNode *ticket_node = NULL; xmlXPathObject *xpath_obj = NULL; cib_t *cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); assert_int_equal(pcmk__get_ticket_state(cib, NULL, &xml), pcmk_rc_ok); /* Verify that the XML result has four elements, and that their * IDs are as expected. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK__XE_TICKET_STATE); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 4); ticket_node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketA"); ticket_node = pcmk__xpath_result(xpath_obj, 1); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketB"); ticket_node = pcmk__xpath_result(xpath_obj, 2); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketC"); ticket_node = pcmk__xpath_result(xpath_obj, 3); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketC"); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); cib__clean_up_connection(&cib); } static void duplicate_tickets(void **state) { xmlNode *xml = NULL; xmlXPathObject *xpath_obj = NULL; cib_t *cib = cib_new(); cib->cmds->signon(cib, crm_system_name, cib_command); assert_int_equal(pcmk__get_ticket_state(cib, "ticketC", &xml), pcmk_rc_duplicate_id); /* Verify that the XML result has two elements, and that their * IDs are as expected. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK__XE_TICKET_STATE "[@" PCMK_XA_ID "=\"ticketC\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 2); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); cib__clean_up_connection(&cib); } /* There are two kinds of tests in this file: * * (1) Those that test what happens if the CIB is not set up correctly, and * (2) Those that test what happens when run against a CIB. * * Therefore, we need two kinds of setup/teardown functions. We only do * minimal overall setup for the entire group, and then setup the CIB for * those tests that need it. */ PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, cmocka_unit_test_setup_teardown(cib_not_connected, setup_test, teardown_test), cmocka_unit_test_setup_teardown(bad_arguments, setup_test, teardown_test), cmocka_unit_test_setup_teardown(unknown_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(ticket_exists, setup_test, teardown_test), cmocka_unit_test_setup_teardown(multiple_tickets, setup_test, teardown_test), cmocka_unit_test_setup_teardown(duplicate_tickets, setup_test, teardown_test)) diff --git a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_constraints_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_constraints_test.c index 4f4048061d..b3a536fa83 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_constraints_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_constraints_test.c @@ -1,136 +1,138 @@ /* * Copyright 2024-2025 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 // xmlXPathObject, etc. static char *cib_path = NULL; static void cib_not_connected(void **state) { xmlNode *xml = NULL; /* Without any special setup, cib_new() in pcmk_ticket_constraints will use the * native CIB which means IPC calls. But there's nothing listening for those * calls, so signon() will return ENOTCONN. Check that we handle that. */ assert_int_equal(pcmk_ticket_constraints(&xml, NULL), ENOTCONN); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static int setup_test(void **state) { cib_path = pcmk__cib_test_copy_cib("tickets.xml"); if (cib_path == NULL) { return -1; } return 0; } static int teardown_test(void **state) { pcmk__cib_test_cleanup(cib_path); cib_path = NULL; return 0; } static void invalid_argument(void **state) { assert_int_equal(pcmk_ticket_constraints(NULL, "ticketA"), EINVAL); } static void unknown_ticket(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_constraints(&xml, "XYZ"), ENXIO); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static void ticket_exists(void **state) { xmlNode *xml = NULL; xmlXPathObject *xpath_obj = NULL; assert_int_equal(pcmk_ticket_constraints(&xml, "ticketA"), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has only one , and that its ID is * what we asked for. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketA\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } static void multiple_tickets(void **state) { xmlNode *xml = NULL; xmlNode *ticket_node = NULL; xmlXPathObject *xpath_obj = NULL; assert_int_equal(pcmk_ticket_constraints(&xml, NULL), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has two elements, and that their * IDs are as expected. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 2); ticket_node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketA"); ticket_node = pcmk__xpath_result(xpath_obj, 1); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketB"); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } /* There are two kinds of tests in this file: * * (1) Those that test what happens if the CIB is not set up correctly, and * (2) Those that test what happens when run against a CIB. * * Therefore, we need two kinds of setup/teardown functions. We only do * minimal overall setup for the entire group, and then setup the CIB for * those tests that need it. */ PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, cmocka_unit_test(cib_not_connected), cmocka_unit_test_setup_teardown(invalid_argument, setup_test, teardown_test), cmocka_unit_test_setup_teardown(unknown_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(ticket_exists, setup_test, teardown_test), cmocka_unit_test_setup_teardown(multiple_tickets, setup_test, teardown_test)) diff --git a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_get_attr_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_get_attr_test.c index 0008fec821..882eadbb7c 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_get_attr_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_get_attr_test.c @@ -1,157 +1,159 @@ /* * Copyright 2024-2025 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 // xmlXPathObject, etc. #include static char *cib_path = NULL; static int setup_test(void **state) { cib_path = pcmk__cib_test_copy_cib("tickets.xml"); if (cib_path == NULL) { return -1; } return 0; } static int teardown_test(void **state) { pcmk__cib_test_cleanup(cib_path); cib_path = NULL; return 0; } static void bad_arguments(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_get_attr(NULL, "ticketA", "XYZ", NULL), EINVAL); assert_int_equal(pcmk_ticket_get_attr(&xml, NULL, "attrA", NULL), EINVAL); pcmk__assert_validates(xml); pcmk__xml_free(xml); xml = NULL; assert_int_equal(pcmk_ticket_get_attr(&xml, "ticketA", NULL, NULL), EINVAL); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static void unknown_ticket(void **state) { xmlNode *xml = NULL; /* Both an unknown ticket and an unknown attribute on a known ticket * return ENXIO so we can't really differentiate between the two here. * Still, we'd better test both. */ assert_int_equal(pcmk_ticket_get_attr(&xml, "XYZ", "attrA", NULL), ENXIO); pcmk__assert_validates(xml); pcmk__xml_free(xml); xml = NULL; assert_int_equal(pcmk_ticket_get_attr(&xml, "ticketA", "XYZ", NULL), ENXIO); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static void verify_results(xmlNode *xml, const char *ticket_id, const char *attr_name, const char *attr_value) { xmlNode *node = NULL; xmlXPathObject *xpath_obj = NULL; /* Verify that the XML result has only one , and that its ID is * what we asked for. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_ID), ticket_id); xmlXPathFreeObject(xpath_obj); /* Verify that it has an child whose name and value are what * we expect. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "/" PCMK_XE_ATTRIBUTE); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_NAME), attr_name); assert_string_equal(crm_element_value(node, PCMK_XA_VALUE), attr_value); xmlXPathFreeObject(xpath_obj); } static void attribute_exists(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_get_attr(&xml, "ticketA", "owner", NULL), pcmk_rc_ok); pcmk__assert_validates(xml); verify_results(xml, "ticketA", "owner", "1"); pcmk__xml_free(xml); } static void default_no_ticket(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_get_attr(&xml, "ticketX", "ABC", "DEFAULT"), pcmk_rc_ok); pcmk__assert_validates(xml); verify_results(xml, "ticketX", "ABC", "DEFAULT"); pcmk__xml_free(xml); } static void default_no_attribute(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_get_attr(&xml, "ticketA", "ABC", "DEFAULT"), pcmk_rc_ok); pcmk__assert_validates(xml); verify_results(xml, "ticketA", "ABC", "DEFAULT"); pcmk__xml_free(xml); } PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, cmocka_unit_test_setup_teardown(bad_arguments, setup_test, teardown_test), cmocka_unit_test_setup_teardown(unknown_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(attribute_exists, setup_test, teardown_test), cmocka_unit_test_setup_teardown(default_no_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(default_no_attribute, setup_test, teardown_test)) diff --git a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_info_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_info_test.c index b45a6a04a5..486d2c439c 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_info_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_info_test.c @@ -1,150 +1,154 @@ /* * Copyright 2024-2025 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 // xmlXPathObject, etc. static char *cib_path = NULL; static int setup_test(void **state) { cib_path = pcmk__cib_test_copy_cib("tickets.xml"); if (cib_path == NULL) { return -1; } return 0; } static int teardown_test(void **state) { pcmk__cib_test_cleanup(cib_path); cib_path = NULL; return 0; } static void bad_arguments(void **state) { assert_int_equal(pcmk_ticket_info(NULL, "ticketA"), EINVAL); } static void unknown_ticket(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_info(&xml, "XYZ"), ENXIO); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static void all_tickets(void **state) { xmlNode *node = NULL; xmlXPathObject *xpath_obj = NULL; xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_info(&xml, NULL), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has three elements, with the attributes * we expect. The input has four tickets, but when they are loaded into the * scheduler's hash table, the duplicate IDs will collide leaving us with * three. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 3); xmlXPathFreeObject(xpath_obj); xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketA\"]"); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_STATUS), PCMK_VALUE_REVOKED); assert_string_equal(crm_element_value(node, PCMK__XA_GRANTED), "false"); assert_string_equal(crm_element_value(node, PCMK_XA_STANDBY), PCMK_VALUE_FALSE); assert_string_equal(crm_element_value(node, "owner"), "1"); xmlXPathFreeObject(xpath_obj); xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketB\"]"); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_STATUS), PCMK_VALUE_GRANTED); assert_string_equal(crm_element_value(node, PCMK__XA_GRANTED), "true"); assert_string_equal(crm_element_value(node, PCMK_XA_STANDBY), PCMK_VALUE_FALSE); assert_null(crm_element_value(node, "owner")); xmlXPathFreeObject(xpath_obj); xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketC\"]"); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_STATUS), PCMK_VALUE_GRANTED); assert_string_equal(crm_element_value(node, PCMK__XA_GRANTED), "true"); assert_string_equal(crm_element_value(node, PCMK_XA_STANDBY), PCMK_VALUE_FALSE); assert_null(crm_element_value(node, "owner")); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } static void single_ticket(void **state) { xmlNode *node = NULL; xmlXPathObject *xpath_obj = NULL; xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_info(&xml, "ticketA"), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has only one , with the attributes * we expect. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketA\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(node); assert_string_equal(crm_element_value(node, PCMK_XA_STATUS), PCMK_VALUE_REVOKED); assert_string_equal(crm_element_value(node, PCMK__XA_GRANTED), "false"); assert_string_equal(crm_element_value(node, PCMK_XA_STANDBY), PCMK_VALUE_FALSE); assert_string_equal(crm_element_value(node, "owner"), "1"); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, cmocka_unit_test_setup_teardown(bad_arguments, setup_test, teardown_test), cmocka_unit_test_setup_teardown(unknown_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(all_tickets, setup_test, teardown_test), cmocka_unit_test_setup_teardown(single_ticket, setup_test, teardown_test)) diff --git a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_state_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_state_test.c index bc7945afdc..c23b71bd50 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_state_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_state_test.c @@ -1,164 +1,168 @@ /* * Copyright 2024-2025 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 // xmlXPathObject, etc. static char *cib_path = NULL; static void cib_not_connected(void **state) { xmlNode *xml = NULL; /* Without any special setup, cib_new() in pcmk_ticket_state will use the * native CIB which means IPC calls. But there's nothing listening for those * calls, so signon() will return ENOTCONN. Check that we handle that. */ assert_int_equal(pcmk_ticket_state(&xml, "ticketA"), ENOTCONN); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static int setup_test(void **state) { cib_path = pcmk__cib_test_copy_cib("tickets.xml"); if (cib_path == NULL) { return -1; } return 0; } static int teardown_test(void **state) { pcmk__cib_test_cleanup(cib_path); cib_path = NULL; return 0; } static void bad_arguments(void **state) { assert_int_equal(pcmk_ticket_state(NULL, "ticketA"), EINVAL); } static void unknown_ticket(void **state) { xmlNode *xml = NULL; assert_int_equal(pcmk_ticket_state(&xml, "XYZ"), ENXIO); pcmk__assert_validates(xml); pcmk__xml_free(xml); } static void ticket_exists(void **state) { xmlNode *xml = NULL; xmlXPathObject *xpath_obj = NULL; assert_int_equal(pcmk_ticket_state(&xml, "ticketA"), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has only one , and that its ID is * what we asked for. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketA\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 1); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } static void multiple_tickets(void **state) { xmlNode *xml = NULL; xmlNode *ticket_node = NULL; xmlXPathObject *xpath_obj = NULL; assert_int_equal(pcmk_ticket_state(&xml, NULL), pcmk_rc_ok); pcmk__assert_validates(xml); /* Verify that the XML result has four elements, and that their * IDs are as expected. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 4); ticket_node = pcmk__xpath_result(xpath_obj, 0); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketA"); ticket_node = pcmk__xpath_result(xpath_obj, 1); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketB"); ticket_node = pcmk__xpath_result(xpath_obj, 2); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketC"); ticket_node = pcmk__xpath_result(xpath_obj, 3); + assert_non_null(ticket_node); assert_string_equal(crm_element_value(ticket_node, PCMK_XA_ID), "ticketC"); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } static void duplicate_tickets(void **state) { xmlNode *xml = NULL; xmlXPathObject *xpath_obj = NULL; assert_int_equal(pcmk_ticket_state(&xml, "ticketC"), pcmk_rc_duplicate_id); /* Verify that the XML result has two elements, and that their * IDs are as expected. */ xpath_obj = pcmk__xpath_search(xml->doc, "//" PCMK_XE_PACEMAKER_RESULT "/" PCMK_XE_TICKETS "/" PCMK_XE_TICKET "[@" PCMK_XA_ID "=\"ticketC\"]"); assert_int_equal(pcmk__xpath_num_results(xpath_obj), 2); xmlXPathFreeObject(xpath_obj); pcmk__xml_free(xml); } /* There are two kinds of tests in this file: * * (1) Those that test what happens if the CIB is not set up correctly, and * (2) Those that test what happens when run against a CIB. * * Therefore, we need two kinds of setup/teardown functions. We only do * minimal overall setup for the entire group, and then setup the CIB for * those tests that need it. */ PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group, cmocka_unit_test(cib_not_connected), cmocka_unit_test_setup_teardown(bad_arguments, setup_test, teardown_test), cmocka_unit_test_setup_teardown(unknown_ticket, setup_test, teardown_test), cmocka_unit_test_setup_teardown(ticket_exists, setup_test, teardown_test), cmocka_unit_test_setup_teardown(multiple_tickets, setup_test, teardown_test), cmocka_unit_test_setup_teardown(duplicate_tickets, setup_test, teardown_test)) diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 926322a5dc..e4ee98c397 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,2402 +1,2405 @@ /* * Copyright 2004-2025 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 // bool, true, false #include // uint32_t #include #include #include #include #include #include #include #include #include // xmlXPathObject, etc. #include #include #include // PCMK_RESOURCE_CLASS_* #include #include #define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources" enum rsc_command { cmd_ban, cmd_cleanup, cmd_clear, cmd_colocations, cmd_cts, cmd_delete, cmd_delete_param, cmd_digests, cmd_execute_agent, cmd_fail, cmd_get_param, cmd_list_active_ops, cmd_list_agents, cmd_list_all_ops, cmd_list_alternatives, cmd_list_instances, cmd_list_options, cmd_list_providers, cmd_list_resources, cmd_list_standards, cmd_locate, cmd_metadata, cmd_move, cmd_query_xml, cmd_query_xml_raw, cmd_refresh, cmd_restart, cmd_set_param, cmd_wait, cmd_why, // Update this when adding new commands cmd_max = cmd_why, }; /*! * \internal * \brief Handler function for a crm_resource command */ typedef crm_exit_t (*crm_resource_fn_t)(pcmk_resource_t *, pcmk_node_t *, cib_t *, pcmk_scheduler_t *, pcmk_ipc_api_t *, xmlNode *); /*! * \internal * \brief Flags to define attributes of a given command * * These attributes may include required command-line options, how to look up a * resource in the scheduler data, whether the command supports clone instances, * etc. */ enum crm_rsc_flags { //! Use \c pcmk_rsc_match_anon_basename when looking up a resource crm_rsc_find_match_anon_basename = (UINT32_C(1) << 0), //! Use \c pcmk_rsc_match_basename when looking up a resource crm_rsc_find_match_basename = (UINT32_C(1) << 1), //! Use \c pcmk_rsc_match_history when looking up a resource crm_rsc_find_match_history = (UINT32_C(1) << 2), //! Fail if \c --resource refers to a particular clone instance crm_rsc_rejects_clone_instance = (UINT32_C(1) << 3), //! Require CIB connection unless resource is specified by agent crm_rsc_requires_cib = (UINT32_C(1) << 4), //! Require controller connection crm_rsc_requires_controller = (UINT32_C(1) << 5), //! Require \c --node argument crm_rsc_requires_node = (UINT32_C(1) << 6), //! Require \c --resource argument crm_rsc_requires_resource = (UINT32_C(1) << 7), //! Require scheduler data unless resource is specified by agent crm_rsc_requires_scheduler = (UINT32_C(1) << 8), }; /*! * \internal * \brief Handler function and flags for a given command */ typedef struct { crm_resource_fn_t fn; //!< Command handler function uint32_t flags; //!< Group of enum crm_rsc_flags } crm_resource_cmd_info_t; struct { enum rsc_command rsc_cmd; // crm_resource command to perform // Command-line option values gchar *rsc_id; // Value of --resource gchar *rsc_type; // Value of --resource-type gboolean all; // --all was given gboolean force; // --force was given gboolean clear_expired; // --expired was given gboolean recursive; // --recursive was given gboolean promoted_role_only; // --promoted was given gchar *host_uname; // Value of --node gchar *interval_spec; // Value of --interval gchar *move_lifetime; // Value of --lifetime gchar *operation; // Value of --operation enum pcmk__opt_flags opt_list; // Parsed from --list-options const char *attr_set_type; // Instance, meta, utilization, or element attribute gchar *prop_id; // --nvpair (attribute XML ID) char *prop_name; // Attribute name gchar *prop_set; // --set-name (attribute block XML ID) gchar *prop_value; // --parameter-value (attribute value) guint timeout_ms; // Parsed from --timeout value char *agent_spec; // Standard and/or provider and/or agent int check_level; // Optional value of --validate or --force-check // Resource configuration specified via command-line arguments gchar *agent; // Value of --agent gchar *class; // Value of --class gchar *provider; // Value of --provider GHashTable *cmdline_params; // Resource parameters specified // Positional command-line arguments gchar **remainder; // Positional arguments as given GHashTable *override_params; // Resource parameter values that override config } options = { .attr_set_type = PCMK_XE_INSTANCE_ATTRIBUTES, .check_level = -1, .rsc_cmd = cmd_list_resources, // List all resources if no command given }; static crm_exit_t exit_code = CRM_EX_OK; static pcmk__output_t *out = NULL; static pcmk__common_args_t *args = NULL; // Things that should be cleaned up on exit static GError *error = NULL; static GMainLoop *mainloop = NULL; #define MESSAGE_TIMEOUT_S 60 #define INDENT " " static pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; static void quit_main_loop(crm_exit_t ec) { exit_code = ec; if (mainloop != NULL) { GMainLoop *mloop = mainloop; mainloop = NULL; // Don't re-enter this block pcmk_quit_main_loop(mloop, 10); g_main_loop_unref(mloop); } } static gboolean resource_ipc_timeout(gpointer data) { // Start with newline because "Waiting for ..." message doesn't have one if (error != NULL) { g_clear_error(&error); } g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT, _("Aborting because no messages received in %d seconds"), MESSAGE_TIMEOUT_S); quit_main_loop(CRM_EX_TIMEOUT); return FALSE; } static void controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type, crm_exit_t status, void *event_data, void *user_data) { crm_exit_t *ec = user_data; pcmk__assert(ec != NULL); switch (event_type) { case pcmk_ipc_event_disconnect: if (exit_code == CRM_EX_DISCONNECT) { // Unexpected crm_info("Connection to controller was terminated"); } *ec = exit_code; quit_main_loop(*ec); break; case pcmk_ipc_event_reply: if (status != CRM_EX_OK) { out->err(out, "Error: bad reply from controller: %s", crm_exit_str(status)); pcmk_disconnect_ipc(api); *ec = status; quit_main_loop(*ec); } else { if ((pcmk_controld_api_replies_expected(api) == 0) && (mainloop != NULL) && g_main_loop_is_running(mainloop)) { out->info(out, "... got reply (done)"); crm_debug("Got all the replies we expected"); pcmk_disconnect_ipc(api); *ec = CRM_EX_OK; quit_main_loop(*ec); } else { out->info(out, "... got reply"); } } break; default: break; } } static void start_mainloop(pcmk_ipc_api_t *capi) { // @TODO See if we can avoid setting exit_code as a global variable unsigned int count = pcmk_controld_api_replies_expected(capi); if (count > 0) { out->info(out, "Waiting for %u %s from the controller", count, pcmk__plural_alt(count, "reply", "replies")); exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects mainloop = g_main_loop_new(NULL, FALSE); pcmk__create_timer(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL); g_main_loop_run(mainloop); } } static GList * build_constraint_list(xmlNode *root) { GList *retval = NULL; xmlNode *cib_constraints = NULL; xmlXPathObject *xpathObj = NULL; int ndx = 0; int num_results = 0; cib_constraints = pcmk_find_cib_element(root, PCMK_XE_CONSTRAINTS); xpathObj = pcmk__xpath_search(cib_constraints->doc, "//" PCMK_XE_RSC_LOCATION); num_results = pcmk__xpath_num_results(xpathObj); for (ndx = 0; ndx < num_results; ndx++) { xmlNode *match = pcmk__xpath_result(xpathObj, ndx); - retval = g_list_insert_sorted(retval, (gpointer) pcmk__xe_id(match), - (GCompareFunc) g_strcmp0); + + if (match != NULL) { + retval = g_list_insert_sorted(retval, (gpointer) pcmk__xe_id(match), + (GCompareFunc) g_strcmp0); + } } xmlXPathFreeObject(xpathObj); return retval; } static gboolean validate_opt_list(const gchar *optarg) { if (pcmk__str_eq(optarg, PCMK_VALUE_FENCING, pcmk__str_none)) { options.opt_list = pcmk__opt_fencing; } else if (pcmk__str_eq(optarg, PCMK__VALUE_PRIMITIVE, pcmk__str_none)) { options.opt_list = pcmk__opt_primitive; } else { return FALSE; } return TRUE; } // GOptionArgFunc callback functions static gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) { options.attr_set_type = PCMK_XE_META_ATTRIBUTES; } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) { options.attr_set_type = PCMK_XE_UTILIZATION; } else if (pcmk__str_eq(option_name, "--element", pcmk__str_none)) { options.attr_set_type = ATTR_SET_ELEMENT; } return TRUE; } /*! * \internal * \brief Process options that set the command * * Nothing else should set \c options.rsc_cmd. * * \param[in] option_name Name of the option being parsed * \param[in] optarg Value to be parsed * \param[in] data Ignored * \param[out] error Where to store recoverable error, if any * * \return \c TRUE if the option was successfully parsed, or \c FALSE if an * error occurred, in which case \p *error is set */ static gboolean command_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { // Sorted by enum rsc_command name if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) { options.rsc_cmd = cmd_ban; } else if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) { options.rsc_cmd = cmd_cleanup; } else if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) { options.rsc_cmd = cmd_clear; } else if (pcmk__str_any_of(option_name, "-a", "--constraints", NULL)) { options.rsc_cmd = cmd_colocations; } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) { options.rsc_cmd = cmd_colocations; options.recursive = TRUE; } else if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) { options.rsc_cmd = cmd_cts; } else if (pcmk__str_any_of(option_name, "-D", "--delete", NULL)) { options.rsc_cmd = cmd_delete; } else if (pcmk__str_any_of(option_name, "-d", "--delete-parameter", NULL)) { options.rsc_cmd = cmd_delete_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_eq(option_name, "--digests", pcmk__str_none)) { options.rsc_cmd = cmd_digests; if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(g_free, g_free); } } else if (pcmk__str_any_of(option_name, "--force-demote", "--force-promote", "--force-start", "--force-stop", "--force-check", "--validate", NULL)) { options.rsc_cmd = cmd_execute_agent; g_free(options.operation); options.operation = g_strdup(option_name + 2); // skip "--" if (options.override_params == NULL) { options.override_params = pcmk__strkey_table(g_free, g_free); } if (optarg != NULL) { if (pcmk__scan_min_int(optarg, &options.check_level, 0) != pcmk_rc_ok) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, _("Invalid check level setting: %s"), optarg); return FALSE; } } } else if (pcmk__str_any_of(option_name, "-F", "--fail", NULL)) { options.rsc_cmd = cmd_fail; } else if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) { options.rsc_cmd = cmd_get_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) { options.rsc_cmd = cmd_list_active_ops; } else if (pcmk__str_eq(option_name, "--list-agents", pcmk__str_none)) { options.rsc_cmd = cmd_list_agents; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-o", "--list-all-operations", NULL)) { options.rsc_cmd = cmd_list_all_ops; } else if (pcmk__str_eq(option_name, "--list-ocf-alternatives", pcmk__str_none)) { options.rsc_cmd = cmd_list_alternatives; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_eq(option_name, "--list-options", pcmk__str_none)) { options.rsc_cmd = cmd_list_options; return validate_opt_list(optarg); } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) { options.rsc_cmd = cmd_list_instances; } else if (pcmk__str_eq(option_name, "--list-ocf-providers", pcmk__str_none)) { options.rsc_cmd = cmd_list_providers; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) { options.rsc_cmd = cmd_list_resources; } else if (pcmk__str_eq(option_name, "--list-standards", pcmk__str_none)) { options.rsc_cmd = cmd_list_standards; } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) { options.rsc_cmd = cmd_locate; } else if (pcmk__str_eq(option_name, "--show-metadata", pcmk__str_none)) { options.rsc_cmd = cmd_metadata; pcmk__str_update(&options.agent_spec, optarg); } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) { options.rsc_cmd = cmd_move; } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) { options.rsc_cmd = cmd_query_xml; } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) { options.rsc_cmd = cmd_query_xml_raw; } else if (pcmk__str_any_of(option_name, "-R", "--refresh", NULL)) { options.rsc_cmd = cmd_refresh; } else if (pcmk__str_eq(option_name, "--restart", pcmk__str_none)) { options.rsc_cmd = cmd_restart; } else if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) { options.rsc_cmd = cmd_set_param; pcmk__str_update(&options.prop_name, optarg); } else if (pcmk__str_eq(option_name, "--wait", pcmk__str_none)) { options.rsc_cmd = cmd_wait; } else if (pcmk__str_any_of(option_name, "-Y", "--why", NULL)) { options.rsc_cmd = cmd_why; } return TRUE; } static gboolean option_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { gchar *name = NULL; gchar *value = NULL; if (pcmk__scan_nvpair(optarg, &name, &value) != pcmk_rc_ok) { return FALSE; } /* services__create_resource_action() ultimately takes ownership of * options.cmdline_params. It's not worth trying to ensure that the entire * call path uses (gchar *) strings and g_free(). So create the table for * (char *) strings, and duplicate the (gchar *) strings when inserting. */ if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } pcmk__insert_dup(options.cmdline_params, name, value); g_free(name); g_free(value); return TRUE; } static gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { long long timeout_ms = crm_get_msec(optarg); if (timeout_ms < 0) { return FALSE; } options.timeout_ms = (guint) QB_MIN(timeout_ms, UINT_MAX); return TRUE; } // Command line option specification /* short option letters still available: eEJkKXyYZ */ static GOptionEntry query_entries[] = { { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List all cluster resources with status", NULL }, { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List IDs of all instantiated resources (individual members\n" INDENT "rather than groups etc.)", NULL }, { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, NULL, NULL }, { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List active resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List all resource operations, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { "list-options", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "List all available options of the given type.\n" INDENT "Allowed values:\n" INDENT PCMK__VALUE_PRIMITIVE " (primitive resource meta-attributes),\n" INDENT PCMK_VALUE_FENCING " (parameters common to all fencing resources)", "TYPE" }, { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List supported standards", NULL }, { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "List all available OCF providers", NULL }, { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "List all agents available for the named standard and/or provider", "STD:PROV" }, { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "List all available providers for the named OCF agent", "AGENT" }, { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Show the metadata for the named class:provider:agent", "SPEC" }, { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show XML configuration of resource (after any template expansion)", NULL }, { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show XML configuration of resource (before any template expansion)", NULL }, { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Display named parameter for resource (use instance attribute\n" INDENT "unless --element, --meta, or --utilization is specified)", "PARAM" }, { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show node(s) currently running resource", NULL }, { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Display the location and colocation constraints that apply to a\n" INDENT "resource, and if --recursive is specified, to the resources\n" INDENT "directly or indirectly involved in those colocations.\n" INDENT "If the named resource is part of a group, or a clone or\n" INDENT "bundle instance, constraints for the collective resource\n" INDENT "will be shown unless --force is given.", NULL }, { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Equivalent to --constraints --recursive", NULL }, { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Show why resources are not running, optionally filtered by\n" INDENT "--resource and/or --node", NULL }, { NULL } }; static GOptionEntry command_entries[] = { { "validate", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Validate resource configuration by calling agent's validate-all\n" INDENT "action. The configuration may be specified either by giving an\n" INDENT "existing resource name with -r, or by specifying --class,\n" INDENT "--agent, and --provider arguments, along with any number of\n" INDENT "--option arguments. An optional LEVEL argument can be given\n" INDENT "to control the level of checking performed.", "LEVEL" }, { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "If resource has any past failures, clear its history and fail\n" INDENT "count. Optionally filtered by --resource, --node, --operation\n" INDENT "and --interval (otherwise all). --operation and --interval\n" INDENT "apply to fail counts, but entire history is always clear, to\n" INDENT "allow current state to be rechecked. If the named resource is\n" INDENT "part of a group, or one numbered instance of a clone or bundled\n" INDENT "resource, the clean-up applies to the whole collective resource\n" INDENT "unless --force is given.", NULL }, { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Delete resource's history (including failures) so its current state\n" INDENT "is rechecked. Optionally filtered by --resource and --node\n" INDENT "(otherwise all). If the named resource is part of a group, or one\n" INDENT "numbered instance of a clone or bundled resource, the refresh\n" INDENT "applies to the whole collective resource unless --force is given.", NULL }, { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Set named parameter for resource (requires -v). Use instance\n" INDENT "attribute unless --element, --meta, or --utilization is " "specified.", "PARAM" }, { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, command_cb, "Delete named parameter for resource. Use instance attribute\n" INDENT "unless --element, --meta or, --utilization is specified.", "PARAM" }, { NULL } }; static GOptionEntry location_entries[] = { { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Create a constraint to move resource. If --node is specified,\n" INDENT "the constraint will be to move to that node, otherwise it\n" INDENT "will be to ban the current node. Unless --force is specified\n" INDENT "this will return an error if the resource is already running\n" INDENT "on the specified node. If --force is specified, this will\n" INDENT "always ban the current node.\n" INDENT "Optional: --lifetime, --promoted. NOTE: This may prevent the\n" INDENT "resource from running on its previous location until the\n" INDENT "implicit constraint expires or is removed with --clear.", NULL }, { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Create a constraint to keep resource off a node.\n" INDENT "Optional: --node, --lifetime, --promoted.\n" INDENT "NOTE: This will prevent the resource from running on the\n" INDENT "affected node until the implicit constraint expires or is\n" INDENT "removed with --clear. If --node is not specified, it defaults\n" INDENT "to the node currently running the resource for primitives\n" INDENT "and groups, or the promoted instance of promotable clones with\n" INDENT PCMK_META_PROMOTED_MAX "=1 (all other situations result in an\n" INDENT "error as there is no sane default).", NULL }, { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "Remove all constraints created by the --ban and/or --move\n" INDENT "commands. Requires: --resource. Optional: --node, --promoted,\n" INDENT "--expired. If --node is not specified, all constraints created\n" INDENT "by --ban and --move will be removed for the named resource. If\n" INDENT "--node and --force are specified, any constraint created by\n" INDENT "--move will be cleared, even if it is not for the specified\n" INDENT "node. If --expired is specified, only those constraints whose\n" INDENT "lifetimes have expired will be removed.", NULL }, { "expired", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.clear_expired, "Modifies the --clear argument to remove constraints with\n" INDENT "expired lifetimes.", NULL }, { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime, "Lifespan (as ISO 8601 duration) of created constraints (with\n" INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)", "TIMESPEC" }, { "promoted", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.promoted_role_only, "Limit scope of command to promoted role (with -B, -M, -U). For\n" INDENT "-B and -M, previously promoted instances may remain\n" INDENT "active in the unpromoted role.", NULL }, // Deprecated since 2.1.0 { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.promoted_role_only, "Deprecated: Use --promoted instead", NULL }, { NULL } }; static GOptionEntry advanced_entries[] = { { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Delete a resource from the CIB. Required: -t", NULL }, { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Tell the cluster this resource has failed", NULL }, { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Tell the cluster to restart this resource and\n" INDENT "anything that depends on it. This temporarily modifies\n" INDENT "the CIB, and other CIB modifications should be avoided\n" INDENT "while this is in progress. If a node is fenced because\n" INDENT "the stop portion of the restart fails, CIB modifications\n" INDENT "such as target-role may remain.", NULL }, { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Wait until the cluster settles into a stable state", NULL }, { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Show parameter hashes that Pacemaker uses to detect\n" INDENT "configuration changes (only accurate if there is resource\n" INDENT "history on the specified node). Required: --resource, --node.\n" INDENT "Optional: any NAME=VALUE parameters will be used to override\n" INDENT "the configuration (to see what the hash would be with those\n" INDENT "changes).", NULL }, { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Bypass the cluster and demote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Bypass the cluster and stop a resource on the local node", NULL }, { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Bypass the cluster and start a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Bypass the cluster and promote a resource on the local\n" INDENT "node. Unless --force is specified, this will refuse to do so if\n" INDENT "the cluster believes the resource is a clone instance already\n" INDENT "running on the local node.", NULL }, { "force-check", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, command_cb, "(Advanced) Bypass the cluster and check the state of a resource on\n" INDENT "the local node. An optional LEVEL argument can be given\n" INDENT "to control the level of checking performed.", "LEVEL" }, { NULL } }; static GOptionEntry addl_entries[] = { { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname, "Node name", "NAME" }, { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive, "Follow colocation chains when using --set-parameter or --constraints", NULL }, { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type, "Resource XML element (primitive, group, etc.) (with -D)", "ELEMENT" }, { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value, "Value to use with -p", "PARAM" }, { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource meta-attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource utilization attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "element", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb, "Use resource element attribute instead of instance attribute\n" INDENT "(with -p, -g, -d)", NULL }, { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation, "Operation to clear instead of all (with -C -r)", "OPERATION" }, { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec, "Interval of operation to clear (default 0s) (with -C -r -n)", "N" }, { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.class, "The standard the resource agent conforms to (for example, ocf).\n" INDENT "Use with --agent, --provider, --option, and --validate.", "CLASS" }, { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.agent, "The agent to use (for example, IPaddr). Use with --class,\n" INDENT "--provider, --option, and --validate.", "AGENT" }, { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.provider, "The vendor that supplies the resource agent (for example,\n" INDENT "heartbeat). Use with --class, --agent, --option, and --validate.", "PROVIDER" }, { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb, "Specify a device configuration parameter as NAME=VALUE (may be\n" INDENT "specified multiple times). Use with --validate and without the\n" INDENT "-r option.", "PARAM" }, { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set, "(Advanced) XML ID of attributes element to use (with -p, -d)", "ID" }, { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id, "(Advanced) XML ID of nvpair element to use (with -p, -d)", "ID" }, { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb, "(Advanced) Abort if command does not finish in this time (with\n" INDENT "--restart, --wait, --force-*)", "N" }, { "all", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.all, "List all options, including advanced and deprecated (with\n" INDENT "--list-options)", NULL }, { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force, "Force the action to be performed. See help for individual commands for\n" INDENT "additional behavior.", NULL }, // @COMPAT Used in resource-agents prior to v4.2.0 { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname, NULL, "HOST" }, { NULL } }; static int ban_or_move(pcmk__output_t *out, pcmk_resource_t *rsc, cib_t *cib_conn, const char *move_lifetime) { int rc = pcmk_rc_ok; pcmk_node_t *current = NULL; unsigned int nactive = 0; CRM_CHECK(rsc != NULL, return EINVAL); current = pe__find_active_requires(rsc, &nactive); if (nactive == 1) { rc = cli_resource_ban(out, options.rsc_id, current->priv->name, move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } else if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) { int count = 0; GList *iter = NULL; current = NULL; for (iter = rsc->priv->children; iter != NULL; iter = iter->next) { pcmk_resource_t *child = (pcmk_resource_t *)iter->data; enum rsc_role_e child_role = child->priv->fns->state(child, true); if (child_role == pcmk_role_promoted) { count++; current = pcmk__current_node(child); } } if(count == 1 && current) { rc = cli_resource_ban(out, options.rsc_id, current->priv->name, move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } else { rc = EINVAL; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("Resource '%s' not moved: active in %d locations (promoted in %d).\n" "To prevent '%s' from running on a specific location, " "specify a node." "To prevent '%s' from being promoted at a specific " "location, specify a node and the --promoted option."), options.rsc_id, nactive, count, options.rsc_id, options.rsc_id); } } else { rc = EINVAL; g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("Resource '%s' not moved: active in %d locations.\n" "To prevent '%s' from running on a specific location, " "specify a node."), options.rsc_id, nactive, options.rsc_id); } return rc; } static void cleanup(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node, pcmk_ipc_api_t *controld_api) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Erasing failures of %s (%s requested) on %s", rsc->id, options.rsc_id, ((node != NULL)? pcmk__node_name(node) : "all nodes")); rc = cli_resource_delete(controld_api, rsc, node, options.operation, options.interval_spec, true, options.force); if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped cli_resource_check(out, rsc, node); } /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if rc != pcmk_rc_ok here? */ if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } /*! * \internal * \brief Allocate a scheduler data object and initialize it from the CIB * * We transform the queried CIB XML to the latest schema version before using it * to populate the scheduler data. * * \param[out] scheduler Where to store scheduler data * \param[in] cib_conn CIB connection * \param[in] out Output object for new scheduler data object * \param[out] cib_xml_orig Where to store queried CIB XML from before any * schema upgrades * * \return Standard Pacemaker return code * * \note \p *scheduler and \p *cib_xml_orig must be \c NULL when this function * is called. * \note The caller is responsible for freeing \p *scheduler using * \c pcmk_free_scheduler. */ static int initialize_scheduler_data(pcmk_scheduler_t **scheduler, cib_t *cib_conn, pcmk__output_t *out, xmlNode **cib_xml_orig) { int rc = pcmk_rc_ok; pcmk__assert((scheduler != NULL) && (*scheduler == NULL) && (cib_conn != NULL) && (out != NULL) && (cib_xml_orig != NULL) && (*cib_xml_orig == NULL)); *scheduler = pcmk_new_scheduler(); if (*scheduler == NULL) { return ENOMEM; } pcmk__set_scheduler_flags(*scheduler, pcmk__sched_no_counts); (*scheduler)->priv->out = out; rc = update_scheduler_input(out, *scheduler, cib_conn, cib_xml_orig); if (rc != pcmk_rc_ok) { pcmk_free_scheduler(*scheduler); *scheduler = NULL; return rc; } cluster_status(*scheduler); return pcmk_rc_ok; } static crm_exit_t refresh(pcmk__output_t *out, const pcmk_node_t *node, pcmk_ipc_api_t *controld_api) { const char *node_name = NULL; const char *log_node_name = "all nodes"; const char *router_node = NULL; int attr_options = pcmk__node_attr_none; int rc = pcmk_rc_ok; if (node != NULL) { node_name = node->priv->name; log_node_name = pcmk__node_name(node); router_node = node->priv->name; } if (pcmk__is_pacemaker_remote_node(node)) { const pcmk_node_t *conn_host = pcmk__current_node(node->priv->remote); if (conn_host == NULL) { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("No cluster connection to Pacemaker Remote node %s " "detected"), log_node_name); return pcmk_rc2exitc(rc); } router_node = conn_host->priv->name; pcmk__set_node_attr_flags(attr_options, pcmk__node_attr_remote); } if (controld_api == NULL) { out->info(out, "Dry run: skipping clean-up of %s due to CIB_file", log_node_name); return CRM_EX_OK; } crm_debug("Re-checking the state of all resources on %s", log_node_name); // @FIXME We shouldn't discard rc here rc = pcmk__attrd_api_clear_failures(NULL, node_name, NULL, NULL, NULL, NULL, attr_options); /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if pcmk_controld_api_reprobe() doesn't return pcmk_rc_ok? */ if (pcmk_controld_api_reprobe(controld_api, node_name, router_node) == pcmk_rc_ok) { start_mainloop(controld_api); return exit_code; } return pcmk_rc2exitc(rc); } static void refresh_resource(pcmk__output_t *out, pcmk_resource_t *rsc, pcmk_node_t *node, pcmk_ipc_api_t *controld_api) { int rc = pcmk_rc_ok; if (options.force == FALSE) { rsc = uber_parent(rsc); } crm_debug("Re-checking the state of %s (%s requested) on %s", rsc->id, options.rsc_id, ((node != NULL)? pcmk__node_name(node) : "all nodes")); rc = cli_resource_delete(controld_api, rsc, node, NULL, 0, false, options.force); if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) { // Show any reasons why resource might stay stopped cli_resource_check(out, rsc, node); } /* @FIXME The mainloop functions in this file set exit_code. What happens to * exit_code if rc != pcmk_rc_ok here? */ if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } /*! * \internal * \brief Check whether a command-line resource configuration was given * * \return \c true if \c --class, \c --provider, or \c --agent was specified, or * \c false otherwise */ static inline bool has_cmdline_config(void) { return ((options.class != NULL) || (options.provider != NULL) || (options.agent != NULL)); } static void validate_cmdline_config(void) { bool is_ocf = pcmk__str_eq(options.class, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none); // Sanity check before throwing any errors if (!has_cmdline_config()) { return; } // Cannot use both --resource and command-line resource configuration if (options.rsc_id != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--class, --agent, and --provider cannot be used with " "-r/--resource")); return; } /* Check whether command supports command-line resource configuration * * @FIXME According to the help text, these options can only be used with * --validate. The --force-* commands are documented for resources that are * configured in Pacemaker. So this is a bug. We have two choices: * * Throw an error if --force-* commands are used with these options. * * Document that --force-* commands can be used with these options. * * An error seems safer. If a user really wants to run a non-trivial * resource action based on CLI parameters, they can do so by executing the * resource agent directly. It's unsafe to do so if Pacemaker is managing * the resource that's specified via --class, --option, etc. * * On the other hand, besides safety concerns, running other actions is * exactly the same as running a validate action, and the implementation is * already in place. */ if (options.rsc_cmd != cmd_execute_agent) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--class, --agent, and --provider can only be used with " "--validate and --force-*")); return; } // Check for a valid combination of --class, --agent, and --provider if (is_ocf) { if ((options.provider == NULL) || (options.agent == NULL)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--provider and --agent are required with " "--class=ocf")); return; } } else { if (options.provider != NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--provider is supported only with --class=ocf")); return; } // Either --class or --agent was given if (options.agent == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--agent is required with --class")); return; } if (options.class == NULL) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("--class is required with --agent")); return; } } // Check whether agent exists if (pcmk__str_eq(options.class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_none)) { if (!stonith_agent_exists(options.agent, 0)) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s is not a known stonith agent"), options.agent); return; } } else if (!resources_agent_exists(options.class, options.provider, options.agent)) { if (is_ocf) { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s:%s:%s is not a known resource agent"), options.class, options.provider, options.agent); } else { g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE, _("%s:%s is not a known resource agent"), options.class, options.agent); } return; } if (options.cmdline_params == NULL) { options.cmdline_params = pcmk__strkey_table(free, free); } } static crm_exit_t handle_ban(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (node == NULL) { rc = ban_or_move(out, rsc, cib_conn, options.move_lifetime); } else { rc = cli_resource_ban(out, options.rsc_id, node->priv->name, options.move_lifetime, cib_conn, options.promoted_role_only, PCMK_ROLE_PROMOTED); } if (rc == EINVAL) { return CRM_EX_USAGE; } return pcmk_rc2exitc(rc); } static crm_exit_t handle_cleanup(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { if (rsc == NULL) { int rc = cli_cleanup_all(controld_api, node, options.operation, options.interval_spec, scheduler); if (rc == pcmk_rc_ok) { start_mainloop(controld_api); } } else { cleanup(out, rsc, node, controld_api); } /* @FIXME Both of the blocks above are supposed to set exit_code via * start_mainloop(). But if cli_cleanup_all() or cli_resource_delete() * fails, we never start the mainloop. It looks as if we exit with CRM_EX_OK * in those cases. */ return exit_code; } static crm_exit_t handle_clear(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; GList *before = NULL; GList *after = NULL; GList *remaining = NULL; int rc = pcmk_rc_ok; if (!out->is_quiet(out)) { before = build_constraint_list(scheduler->input); } if (options.clear_expired) { rc = cli_resource_clear_all_expired(scheduler->input, cib_conn, options.rsc_id, node_name, options.promoted_role_only); } else if (node != NULL) { rc = cli_resource_clear(options.rsc_id, node_name, NULL, cib_conn, true, options.force); } else { rc = cli_resource_clear(options.rsc_id, NULL, scheduler->nodes, cib_conn, true, options.force); } if (!out->is_quiet(out)) { xmlNode *cib_xml = NULL; rc = cib_conn->cmds->query(cib_conn, NULL, &cib_xml, cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, _("Could not get modified CIB: %s"), pcmk_rc_str(rc)); g_list_free(before); pcmk__xml_free(cib_xml); return pcmk_rc2exitc(rc); } scheduler->input = cib_xml; cluster_status(scheduler); after = build_constraint_list(scheduler->input); remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp); for (const GList *iter = remaining; iter != NULL; iter = iter->next) { const char *constraint = iter->data; out->info(out, "Removing constraint: %s", constraint); } g_list_free(before); g_list_free(after); g_list_free(remaining); } return pcmk_rc2exitc(rc); } static crm_exit_t handle_colocations(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = out->message(out, "locations-and-colocations", rsc, options.recursive, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t handle_cts(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { g_list_foreach(scheduler->priv->resources, (GFunc) cli_resource_print_cts, out); cli_resource_print_cts_constraints(scheduler); return CRM_EX_OK; } static crm_exit_t handle_delete(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { /* rsc_id was already checked for NULL much earlier when validating command * line arguments */ int rc = pcmk_rc_ok; if (options.rsc_type == NULL) { crm_exit_t ec = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, ec, _("You need to specify a resource type with -t")); return ec; } rc = pcmk__resource_delete(cib_conn, cib_sync_call, options.rsc_id, options.rsc_type); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, _("Could not delete resource %s: %s"), options.rsc_id, pcmk_rc_str(rc)); } return pcmk_rc2exitc(rc); } static crm_exit_t handle_delete_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = cli_resource_delete_attribute(rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, cib_conn, cib_xml_orig, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t handle_digests(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk__resource_digests(out, rsc, node, options.override_params); return pcmk_rc2exitc(rc); } static crm_exit_t handle_execute_agent(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { if (has_cmdline_config()) { return cli_resource_execute_from_params(out, NULL, options.class, options.provider, options.agent, options.operation, options.cmdline_params, options.override_params, options.timeout_ms, args->verbosity, options.force, options.check_level); } return cli_resource_execute(rsc, options.rsc_id, options.operation, options.override_params, options.timeout_ms, cib_conn, args->verbosity, options.force, options.check_level); } static crm_exit_t handle_fail(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = cli_resource_fail(controld_api, rsc, options.rsc_id, node); if (rc == pcmk_rc_ok) { // start_mainloop() sets exit_code start_mainloop(controld_api); return exit_code; } return pcmk_rc2exitc(rc);; } static crm_exit_t handle_get_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { unsigned int count = 0; GHashTable *params = NULL; pcmk_node_t *current = rsc->priv->fns->active_node(rsc, &count, NULL); bool free_params = true; const char *value = NULL; int rc = pcmk_rc_ok; if (count > 1) { out->err(out, "%s is active on more than one node, returning the default " "value for %s", rsc->id, pcmk__s(options.prop_name, "unspecified property")); current = NULL; } crm_debug("Looking up %s in %s", options.prop_name, rsc->id); if (pcmk__str_eq(options.attr_set_type, PCMK_XE_INSTANCE_ATTRIBUTES, pcmk__str_none)) { params = pe_rsc_params(rsc, current, scheduler); free_params = false; value = g_hash_table_lookup(params, options.prop_name); } else if (pcmk__str_eq(options.attr_set_type, PCMK_XE_META_ATTRIBUTES, pcmk__str_none)) { params = pcmk__strkey_table(free, free); get_meta_attributes(params, rsc, NULL, scheduler); value = g_hash_table_lookup(params, options.prop_name); } else if (pcmk__str_eq(options.attr_set_type, ATTR_SET_ELEMENT, pcmk__str_none)) { value = crm_element_value(rsc->priv->xml, options.prop_name); free_params = false; } else { const pcmk_rule_input_t rule_input = { .now = scheduler->priv->now, }; params = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_UTILIZATION, &rule_input, params, NULL, scheduler); value = g_hash_table_lookup(params, options.prop_name); } rc = out->message(out, "attribute-list", rsc, options.prop_name, value); if (free_params) { g_hash_table_destroy(params); } return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_active_ops(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; int rc = cli_resource_print_operations(options.rsc_id, node_name, true, scheduler); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_agents(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk__list_agents(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_all_ops(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { const char *node_name = (node != NULL)? node->priv->name : NULL; int rc = cli_resource_print_operations(options.rsc_id, node_name, false, scheduler); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_alternatives(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk__list_alternatives(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_instances(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = out->message(out, "resource-names-list", scheduler->priv->resources); if (rc == pcmk_rc_no_output) { // @COMPAT It seems wrong to return an error because there no resources return CRM_EX_NOSUCH; } return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_options(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { crm_exit_t ec = CRM_EX_OK; int rc = pcmk_rc_ok; switch (options.opt_list) { case pcmk__opt_fencing: rc = pcmk__list_fencing_params(out, options.all); return pcmk_rc2exitc(rc); case pcmk__opt_primitive: rc = pcmk__list_primitive_meta(out, options.all); return pcmk_rc2exitc(rc); default: ec = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, ec, "Bug: Invalid option list type"); return ec; } } static crm_exit_t handle_list_providers(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk__list_providers(out, options.agent_spec); return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_resources(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { GList *all = g_list_prepend(NULL, (gpointer) "*"); int rc = out->message(out, "resource-list", scheduler, pcmk_show_inactive_rscs |pcmk_show_rsc_only |pcmk_show_pending, true, all, all, false); g_list_free(all); if (rc == pcmk_rc_no_output) { // @COMPAT It seems wrong to return an error because there no resources return CRM_EX_NOSUCH; } return pcmk_rc2exitc(rc); } static crm_exit_t handle_list_standards(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk__list_standards(out); return pcmk_rc2exitc(rc); } static crm_exit_t handle_locate(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { GList *nodes = cli_resource_search(rsc, options.rsc_id); int rc = out->message(out, "resource-search-list", nodes, options.rsc_id); g_list_free_full(nodes, free); return pcmk_rc2exitc(rc); } static crm_exit_t handle_metadata(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; char *standard = NULL; char *provider = NULL; char *type = NULL; char *metadata = NULL; lrmd_t *lrmd_conn = NULL; rc = lrmd__new(&lrmd_conn, NULL, NULL, 0); if (rc != pcmk_rc_ok) { g_set_error(&error, PCMK__RC_ERROR, rc, _("Could not create executor connection")); lrmd_api_delete(lrmd_conn); return pcmk_rc2exitc(rc); } rc = crm_parse_agent_spec(options.agent_spec, &standard, &provider, &type); rc = pcmk_legacy2rc(rc); if (rc == pcmk_rc_ok) { rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard, provider, type, &metadata, 0); rc = pcmk_legacy2rc(rc); if (metadata != NULL) { out->output_xml(out, PCMK_XE_METADATA, metadata); free(metadata); } else { /* We were given a validly formatted spec, but it doesn't necessarily * match up with anything that exists. Use ENXIO as the return code * here because that maps to an exit code of CRM_EX_NOSUCH, which * probably is the most common reason to get here. */ rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("Metadata query for %s failed: %s"), options.agent_spec, pcmk_rc_str(rc)); } } else { rc = ENXIO; g_set_error(&error, PCMK__RC_ERROR, rc, _("'%s' is not a valid agent specification"), options.agent_spec); } lrmd_api_delete(lrmd_conn); return pcmk_rc2exitc(rc); } static crm_exit_t handle_move(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (node == NULL) { rc = ban_or_move(out, rsc, cib_conn, options.move_lifetime); } else { rc = cli_resource_move(rsc, options.rsc_id, node, options.move_lifetime, cib_conn, options.promoted_role_only, options.force); } if (rc == EINVAL) { return CRM_EX_USAGE; } return pcmk_rc2exitc(rc); } static crm_exit_t handle_query_xml(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = cli_resource_print(rsc, true); return pcmk_rc2exitc(rc); } static crm_exit_t handle_query_xml_raw(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = cli_resource_print(rsc, false); return pcmk_rc2exitc(rc); } static crm_exit_t handle_refresh(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { if (rsc == NULL) { return refresh(out, node, controld_api); } refresh_resource(out, rsc, node, controld_api); /* @FIXME Both of the calls above are supposed to set exit_code via * start_mainloop(). But there appear to be cases in which we can return * from refresh() or refresh_resource() without starting the mainloop or * returning an error code. It looks as if we exit with CRM_EX_OK in those * cases. */ return exit_code; } static crm_exit_t handle_restart(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { /* We don't pass scheduler because rsc needs to stay valid for the entire * lifetime of cli_resource_restart(), but it will reset and update the * scheduler data multiple times, so it needs to use its own copy. */ int rc = cli_resource_restart(out, rsc, node, options.move_lifetime, options.timeout_ms, cib_conn, options.promoted_role_only, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t handle_set_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = pcmk_rc_ok; if (pcmk__str_empty(options.prop_value)) { crm_exit_t ec = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, ec, _("You need to supply a value with the -v option")); return ec; } rc = cli_resource_update_attribute(rsc, options.rsc_id, options.prop_set, options.attr_set_type, options.prop_id, options.prop_name, options.prop_value, options.recursive, cib_conn, cib_xml_orig, options.force); return pcmk_rc2exitc(rc); } static crm_exit_t handle_wait(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = wait_till_stable(out, options.timeout_ms, cib_conn); return pcmk_rc2exitc(rc); } static crm_exit_t handle_why(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, pcmk_scheduler_t *scheduler, pcmk_ipc_api_t *controld_api, xmlNode *cib_xml_orig) { int rc = out->message(out, "resource-reasons-list", scheduler->priv->resources, rsc, node); return pcmk_rc2exitc(rc); } static const crm_resource_cmd_info_t crm_resource_command_info[] = { [cmd_ban] = { handle_ban, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_cleanup] = { handle_cleanup, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_scheduler, }, [cmd_clear] = { handle_clear, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource // Unless options.clear_expired |crm_rsc_requires_scheduler, }, [cmd_colocations] = { handle_colocations, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_cts] = { handle_cts, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_delete] = { handle_delete, crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource, }, [cmd_delete_param] = { handle_delete_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_digests] = { handle_digests, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_node |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_execute_agent] = { handle_execute_agent, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_fail] = { handle_fail, crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_node |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_get_param] = { handle_get_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_list_active_ops] = { handle_list_active_ops, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_agents] = { handle_list_agents, 0, }, [cmd_list_all_ops] = { handle_list_all_ops, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_alternatives] = { handle_list_alternatives, 0, }, [cmd_list_instances] = { handle_list_instances, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_options] = { handle_list_options, 0, }, [cmd_list_providers] = { handle_list_providers, 0, }, [cmd_list_resources] = { handle_list_resources, crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, [cmd_list_standards] = { handle_list_standards, 0, }, [cmd_locate] = { handle_locate, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_metadata] = { handle_metadata, 0, }, [cmd_move] = { handle_move, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_query_xml] = { handle_query_xml, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_query_xml_raw] = { handle_query_xml_raw, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_refresh] = { handle_refresh, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_controller |crm_rsc_requires_scheduler, }, [cmd_restart] = { handle_restart, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_rejects_clone_instance |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_set_param] = { handle_set_param, crm_rsc_find_match_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_resource |crm_rsc_requires_scheduler, }, [cmd_wait] = { handle_wait, crm_rsc_requires_cib, }, [cmd_why] = { handle_why, crm_rsc_find_match_anon_basename |crm_rsc_find_match_history |crm_rsc_requires_cib |crm_rsc_requires_scheduler, }, }; static GOptionContext * build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id, "Resource ID", "ID" }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder, NULL, NULL }, { NULL } }; const char *description = "Examples:\n\n" "List the available OCF agents:\n\n" "\t# crm_resource --list-agents ocf\n\n" "List the available OCF agents from the linux-ha project:\n\n" "\t# crm_resource --list-agents ocf:heartbeat\n\n" "Move 'myResource' to a specific node:\n\n" "\t# crm_resource --resource myResource --move --node altNode\n\n" "Allow (but not force) 'myResource' to move back to its original " "location:\n\n" "\t# crm_resource --resource myResource --clear\n\n" "Stop 'myResource' (and anything that depends on it):\n\n" "\t# crm_resource --resource myResource --set-parameter " PCMK_META_TARGET_ROLE "--meta --parameter-value Stopped\n\n" "Tell the cluster not to manage 'myResource' (the cluster will not " "attempt to start or stop the\n" "resource under any circumstances; useful when performing maintenance " "tasks on a resource):\n\n" "\t# crm_resource --resource myResource --set-parameter " PCMK_META_IS_MANAGED "--meta --parameter-value false\n\n" "Erase the operation history of 'myResource' on 'aNode' (the cluster " "will 'forget' the existing\n" "resource state, including any errors, and attempt to recover the" "resource; useful when a resource\n" "had failed permanently and has been repaired by an administrator):\n\n" "\t# crm_resource --resource myResource --cleanup --node aNode\n\n"; context = pcmk__build_arg_context(args, "text (default), xml", group, NULL); g_option_context_set_description(context, description); /* 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, "queries", "Queries:", "Show query help", query_entries); pcmk__add_arg_group(context, "commands", "Commands:", "Show command help", command_entries); pcmk__add_arg_group(context, "locations", "Locations:", "Show location help", location_entries); pcmk__add_arg_group(context, "advanced", "Advanced:", "Show advanced option help", advanced_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } int main(int argc, char **argv) { const crm_resource_cmd_info_t *command_info = NULL; pcmk_resource_t *rsc = NULL; pcmk_node_t *node = NULL; cib_t *cib_conn = NULL; pcmk_scheduler_t *scheduler = NULL; pcmk_ipc_api_t *controld_api = NULL; xmlNode *cib_xml_orig = NULL; uint32_t find_flags = 0; int rc = pcmk_rc_ok; GOptionGroup *output_group = NULL; gchar **processed_args = NULL; GOptionContext *context = NULL; /* * Parse command line arguments */ args = pcmk__new_common_args(SUMMARY); processed_args = pcmk__cmdline_preproc(argv, "GHINSTdginpstuvx"); 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_resource", args->verbosity); rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_ERROR; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error creating output format %s: %s"), args->output_ty, pcmk_rc_str(rc)); goto done; } pe__register_messages(out); crm_resource_register_messages(out); lrmd__register_messages(out); pcmk__register_lib_messages(out); out->quiet = args->quiet; crm_log_args(argc, argv); /* * Validate option combinations */ // --expired without --clear/-U doesn't make sense if (options.clear_expired && (options.rsc_cmd != cmd_clear)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--expired requires --clear or -U")); goto done; } if (options.remainder != NULL) { // Commands that use positional arguments will create override_params if (options.override_params == NULL) { GString *msg = g_string_sized_new(128); guint len = g_strv_length(options.remainder); g_string_append(msg, "non-option ARGV-elements:"); for (int i = 0; i < len; i++) { g_string_append_printf(msg, "\n[%d of %u] %s", i + 1, len, options.remainder[i]); } exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg->str); g_string_free(msg, TRUE); goto done; } for (gchar **arg = options.remainder; *arg != NULL; arg++) { gchar *name = NULL; gchar *value = NULL; int rc = pcmk__scan_nvpair(*arg, &name, &value); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error parsing '%s' as a name=value pair"), *arg); goto done; } g_hash_table_insert(options.override_params, name, value); } } if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) { switch (options.rsc_cmd) { /* These are the only commands that have historically used the * elements in their XML schema. For all others, use the simple list * argument. */ case cmd_get_param: case cmd_list_instances: case cmd_list_standards: pcmk__output_enable_list_element(out); break; default: break; } } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) { switch (options.rsc_cmd) { case cmd_colocations: case cmd_list_resources: pcmk__output_text_set_fancy(out, true); break; default: break; } } if (args->version) { out->version(out, false); goto done; } // Ensure command is in valid range and has a handler function if ((options.rsc_cmd >= 0) && (options.rsc_cmd <= cmd_max)) { command_info = &crm_resource_command_info[options.rsc_cmd]; } if ((command_info == NULL) || (command_info->fn == NULL)) { exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Bug: Unimplemented command: %d"), (int) options.rsc_cmd); goto done; } /* If a command-line resource agent specification was given, validate it. * Otherwise, ensure --option was not given. */ if (has_cmdline_config()) { validate_cmdline_config(); if (error != NULL) { exit_code = CRM_EX_USAGE; goto done; } } else if (options.cmdline_params != NULL) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("--option must be used with --validate and without -r")); g_hash_table_destroy(options.cmdline_params); goto done; } // Ensure --resource is set if it's required if (pcmk_is_set(command_info->flags, crm_rsc_requires_resource) && !has_cmdline_config() && !options.clear_expired && (options.rsc_id == NULL)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Must supply a resource ID with -r/--resource")); goto done; } // Ensure --node is set if it's required if (pcmk_is_set(command_info->flags, crm_rsc_requires_node) && (options.host_uname == NULL)) { exit_code = CRM_EX_USAGE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Must supply a node name with -N/--node")); goto done; } // Establish a connection to the CIB if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_cib) && !has_cmdline_config()) { cib_conn = cib_new(); if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) { exit_code = CRM_EX_DISCONNECT; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Could not create CIB connection")); goto done; } rc = cib__signon_attempts(cib_conn, cib_command, 5); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Could not connect to the CIB: %s"), pcmk_rc_str(rc)); goto done; } } // Populate scheduler data from CIB query if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_scheduler) && !has_cmdline_config()) { rc = initialize_scheduler_data(&scheduler, cib_conn, out, &cib_xml_orig); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); goto done; } } // Establish a connection to the controller if needed if (pcmk_is_set(command_info->flags, crm_rsc_requires_controller) && (getenv("CIB_file") == NULL)) { rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error connecting to the controller: %s"), pcmk_rc_str(rc)); goto done; } pcmk_register_ipc_callback(controld_api, controller_event_callback, &exit_code); 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, _("Error connecting to %s: %s"), pcmk_ipc_name(controld_api, true), pcmk_rc_str(rc)); goto done; } } /* Find node if --node was given. * * @TODO Consider stricter validation. Currently we ignore the --node * argument for commands that don't require scheduler data, since we have no * way to find the node in that case. This is really a usage error, but we * don't validate strictly. We allow multiple commands (and in some cases * their options like --node) to be specified, and we use the last one in * case of conflicts. * * This isn't universally true. --expired results in a usage error unless * the final command is --clear. */ if (options.host_uname != NULL) { node = pcmk_find_node(scheduler, options.host_uname); if (node == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Node '%s' not found"), options.host_uname); goto done; } } /* Find resource if --resource was given and any find flags are set. * * @TODO Consider stricter validation. See comment above for --node. * @TODO Setter macro for tracing? */ if (pcmk_is_set(command_info->flags, crm_rsc_find_match_anon_basename)) { find_flags |= pcmk_rsc_match_anon_basename; } if (pcmk_is_set(command_info->flags, crm_rsc_find_match_basename)) { find_flags |= pcmk_rsc_match_basename; } if (pcmk_is_set(command_info->flags, crm_rsc_find_match_history)) { find_flags |= pcmk_rsc_match_history; } if ((find_flags != 0) && (options.rsc_id != NULL)) { pcmk__assert(scheduler != NULL); rsc = pe_find_resource_with_flags(scheduler->priv->resources, options.rsc_id, find_flags); if (rsc == NULL) { exit_code = CRM_EX_NOSUCH; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Resource '%s' not found"), options.rsc_id); goto done; } if (pcmk_is_set(command_info->flags, crm_rsc_rejects_clone_instance) && pcmk__is_clone(rsc->priv->parent) && (strchr(options.rsc_id, ':') != NULL)) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Cannot operate on clone resource instance '%s'"), options.rsc_id); goto done; } } exit_code = command_info->fn(rsc, node, cib_conn, scheduler, controld_api, cib_xml_orig); done: // For CRM_EX_USAGE, error is already set satisfactorily if ((exit_code != CRM_EX_OK) && (exit_code != CRM_EX_USAGE)) { if (error != NULL) { char *msg = crm_strdup_printf("%s\nError performing operation: %s", error->message, crm_exit_str(exit_code)); g_clear_error(&error); g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg); free(msg); } else { g_set_error(&error, PCMK__EXITC_ERROR, exit_code, _("Error performing operation: %s"), crm_exit_str(exit_code)); } } g_free(options.host_uname); g_free(options.interval_spec); g_free(options.move_lifetime); g_free(options.operation); g_free(options.prop_id); free(options.prop_name); g_free(options.prop_set); g_free(options.prop_value); g_free(options.rsc_id); g_free(options.rsc_type); free(options.agent_spec); g_free(options.agent); g_free(options.class); g_free(options.provider); if (options.override_params != NULL) { g_hash_table_destroy(options.override_params); } g_strfreev(options.remainder); // Don't destroy options.cmdline_params here. See comment in option_cb(). g_strfreev(processed_args); g_option_context_free(context); pcmk__xml_free(cib_xml_orig); cib__clean_up_connection(&cib_conn); pcmk_free_ipc_api(controld_api); pcmk_free_scheduler(scheduler); if (mainloop != NULL) { g_main_loop_unref(mainloop); } 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); } diff --git a/tools/crm_resource_ban.c b/tools/crm_resource_ban.c index cec7d9de58..30ad1aefe5 100644 --- a/tools/crm_resource_ban.c +++ b/tools/crm_resource_ban.c @@ -1,518 +1,522 @@ /* * Copyright 2004-2025 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 // xmlXPathObject, etc. #include static char * parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime) { char *later_s = NULL; crm_time_t *now = NULL; crm_time_t *later = NULL; crm_time_t *duration = NULL; if (move_lifetime == NULL) { return NULL; } duration = crm_time_parse_duration(move_lifetime); if (duration == NULL) { out->err(out, "Invalid duration specified: %s\n" "Please refer to https://en.wikipedia.org/wiki/ISO_8601#Durations " "for examples of valid durations", move_lifetime); return NULL; } now = crm_time_new(NULL); later = crm_time_add(now, duration); if (later == NULL) { out->err(out, "Unable to add %s to current time\n" "Please report to " PACKAGE_BUGREPORT " as possible bug", move_lifetime); crm_time_free(now); crm_time_free(duration); return NULL; } crm_time_log(LOG_INFO, "now ", now, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "later ", later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_INFO, "duration", duration, crm_time_log_date | crm_time_log_timeofday); later_s = crm_time_as_string(later, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); out->info(out, "Migration will take effect until: %s", later_s); crm_time_free(duration); crm_time_free(later); crm_time_free(now); return later_s; } // \return Standard Pacemaker return code int cli_resource_ban(pcmk__output_t *out, const char *rsc_id, const char *host, const char *move_lifetime, cib_t *cib_conn, gboolean promoted_role_only, const char *promoted_role) { char *later_s = NULL; int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; later_s = parse_cli_lifetime(out, move_lifetime); if(move_lifetime && later_s == NULL) { return EINVAL; } fragment = pcmk__xe_create(NULL, PCMK_XE_CONSTRAINTS); location = pcmk__xe_create(fragment, PCMK_XE_RSC_LOCATION); pcmk__xe_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); out->info(out, "WARNING: Creating " PCMK_XE_RSC_LOCATION " constraint '%s' with " "a score of " PCMK_VALUE_MINUS_INFINITY " for resource %s on %s." "\n\tThis will prevent %s from %s on %s until the constraint is " "removed using the clear option or by editing the CIB with an " "appropriate tool.\n" "\tThis will be the case even if %s is the last node in the " "cluster", pcmk__xe_id(location), rsc_id, host, rsc_id, (promoted_role_only? "being promoted" : "running"), host, host); crm_xml_add(location, PCMK_XA_RSC, rsc_id); if(promoted_role_only) { crm_xml_add(location, PCMK_XA_ROLE, promoted_role); } else { crm_xml_add(location, PCMK_XA_ROLE, PCMK_ROLE_STARTED); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, PCMK_XE_NODE, host); crm_xml_add(location, PCMK_XA_SCORE, PCMK_VALUE_MINUS_INFINITY); } else { xmlNode *rule = pcmk__xe_create(location, PCMK_XE_RULE); xmlNode *expr = pcmk__xe_create(rule, PCMK_XE_EXPRESSION); pcmk__xe_set_id(rule, "cli-ban-%s-on-%s-rule", rsc_id, host); crm_xml_add(rule, PCMK_XA_SCORE, PCMK_VALUE_MINUS_INFINITY); crm_xml_add(rule, PCMK_XA_BOOLEAN_OP, PCMK_VALUE_AND); pcmk__xe_set_id(expr, "cli-ban-%s-on-%s-expr", rsc_id, host); crm_xml_add(expr, PCMK_XA_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, PCMK_XA_OPERATION, PCMK_VALUE_EQ); crm_xml_add(expr, PCMK_XA_VALUE, host); crm_xml_add(expr, PCMK_XA_TYPE, PCMK_VALUE_STRING); expr = pcmk__xe_create(rule, PCMK_XE_DATE_EXPRESSION); pcmk__xe_set_id(expr, "cli-ban-%s-on-%s-lifetime", rsc_id, host); crm_xml_add(expr, PCMK_XA_OPERATION, PCMK_VALUE_LT); crm_xml_add(expr, PCMK_XA_END, later_s); } crm_log_xml_notice(fragment, "Modify"); rc = cib_conn->cmds->modify(cib_conn, PCMK_XE_CONSTRAINTS, fragment, cib_sync_call); rc = pcmk_legacy2rc(rc); pcmk__xml_free(fragment); free(later_s); if ((rc != pcmk_rc_ok) && promoted_role_only && (strcmp(promoted_role, PCMK_ROLE_PROMOTED) == 0)) { int banrc = cli_resource_ban(out, rsc_id, host, move_lifetime, cib_conn, promoted_role_only, PCMK__ROLE_PROMOTED_LEGACY); if (banrc == pcmk_rc_ok) { rc = banrc; } } return rc; } // \return Standard Pacemaker return code int cli_resource_prefer(pcmk__output_t *out,const char *rsc_id, const char *host, const char *move_lifetime, cib_t *cib_conn, gboolean promoted_role_only, const char *promoted_role) { char *later_s = parse_cli_lifetime(out, move_lifetime); int rc = pcmk_rc_ok; xmlNode *location = NULL; xmlNode *fragment = NULL; if(move_lifetime && later_s == NULL) { return EINVAL; } if(cib_conn == NULL) { free(later_s); return ENOTCONN; } fragment = pcmk__xe_create(NULL, PCMK_XE_CONSTRAINTS); location = pcmk__xe_create(fragment, PCMK_XE_RSC_LOCATION); pcmk__xe_set_id(location, "cli-prefer-%s", rsc_id); crm_xml_add(location, PCMK_XA_RSC, rsc_id); if(promoted_role_only) { crm_xml_add(location, PCMK_XA_ROLE, promoted_role); } else { crm_xml_add(location, PCMK_XA_ROLE, PCMK_ROLE_STARTED); } if (later_s == NULL) { /* Short form */ crm_xml_add(location, PCMK_XE_NODE, host); crm_xml_add(location, PCMK_XA_SCORE, PCMK_VALUE_INFINITY); } else { xmlNode *rule = pcmk__xe_create(location, PCMK_XE_RULE); xmlNode *expr = pcmk__xe_create(rule, PCMK_XE_EXPRESSION); pcmk__xe_set_id(rule, "cli-prefer-rule-%s", rsc_id); crm_xml_add(rule, PCMK_XA_SCORE, PCMK_VALUE_INFINITY); crm_xml_add(rule, PCMK_XA_BOOLEAN_OP, PCMK_VALUE_AND); pcmk__xe_set_id(expr, "cli-prefer-expr-%s", rsc_id); crm_xml_add(expr, PCMK_XA_ATTRIBUTE, CRM_ATTR_UNAME); crm_xml_add(expr, PCMK_XA_OPERATION, PCMK_VALUE_EQ); crm_xml_add(expr, PCMK_XA_VALUE, host); crm_xml_add(expr, PCMK_XA_TYPE, PCMK_VALUE_STRING); expr = pcmk__xe_create(rule, PCMK_XE_DATE_EXPRESSION); pcmk__xe_set_id(expr, "cli-prefer-lifetime-end-%s", rsc_id); crm_xml_add(expr, PCMK_XA_OPERATION, PCMK_VALUE_LT); crm_xml_add(expr, PCMK_XA_END, later_s); } crm_log_xml_info(fragment, "Modify"); rc = cib_conn->cmds->modify(cib_conn, PCMK_XE_CONSTRAINTS, fragment, cib_sync_call); rc = pcmk_legacy2rc(rc); pcmk__xml_free(fragment); free(later_s); if ((rc != pcmk_rc_ok) && promoted_role_only && (strcmp(promoted_role, PCMK_ROLE_PROMOTED) == 0)) { int preferrc = cli_resource_prefer(out, rsc_id, host, move_lifetime, cib_conn, promoted_role_only, PCMK__ROLE_PROMOTED_LEGACY); if (preferrc == pcmk_rc_ok) { rc = preferrc; } } return rc; } /* Nodes can be specified two different ways in the CIB, so we have two different * functions to try clearing out any constraints on them: * * (1) The node could be given by attribute=/value= in an expression XML node. * That's what resource_clear_node_in_expr handles. That XML looks like this: * * * * * * * * * (2) The node could be given by node= in a PCMK_XE_RSC_LOCATION XML node. * That's what resource_clear_node_in_location handles. That XML looks like * this: * * * * \return Standard Pacemaker return code */ static int resource_clear_node_in_expr(const char *rsc_id, const char *host, cib_t *cib_conn) { int rc = pcmk_rc_ok; char *xpath_string = NULL; #define XPATH_FMT \ "//" PCMK_XE_RSC_LOCATION "[@" PCMK_XA_ID "='cli-prefer-%s']" \ "[" PCMK_XE_RULE \ "[@" PCMK_XA_ID "='cli-prefer-rule-%s']" \ "/" PCMK_XE_EXPRESSION \ "[@" PCMK_XA_ATTRIBUTE "='" CRM_ATTR_UNAME "' " \ "and @" PCMK_XA_VALUE "='%s']" \ "]" xpath_string = crm_strdup_printf(XPATH_FMT, rsc_id, rsc_id, host); rc = cib_conn->cmds->remove(cib_conn, xpath_string, NULL, cib_xpath|cib_sync_call); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free(xpath_string); return rc; } // \return Standard Pacemaker return code static int resource_clear_node_in_location(const char *rsc_id, const char *host, cib_t * cib_conn, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = pcmk__xe_create(NULL, PCMK_XE_CONSTRAINTS); if (clear_ban_constraints == TRUE) { location = pcmk__xe_create(fragment, PCMK_XE_RSC_LOCATION); pcmk__xe_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); } location = pcmk__xe_create(fragment, PCMK_XE_RSC_LOCATION); pcmk__xe_set_id(location, "cli-prefer-%s", rsc_id); if (force == FALSE) { crm_xml_add(location, PCMK_XE_NODE, host); } crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, PCMK_XE_CONSTRAINTS, fragment, cib_sync_call); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } pcmk__xml_free(fragment); return rc; } // \return Standard Pacemaker return code int cli_resource_clear(const char *rsc_id, const char *host, GList *allnodes, cib_t * cib_conn, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; if(cib_conn == NULL) { return ENOTCONN; } if (host) { rc = resource_clear_node_in_expr(rsc_id, host, cib_conn); /* rc does not tell us whether the previous operation did anything, only * whether it failed or not. Thus, as long as it did not fail, we need * to try the second clear method. */ if (rc == pcmk_rc_ok) { rc = resource_clear_node_in_location(rsc_id, host, cib_conn, clear_ban_constraints, force); } } else { GList *n = allnodes; /* Iterate over all nodes, attempting to clear the constraint from each. * On the first error, abort. */ for(; n; n = n->next) { pcmk_node_t *target = n->data; rc = cli_resource_clear(rsc_id, target->priv->name, NULL, cib_conn, clear_ban_constraints, force); if (rc != pcmk_rc_ok) { break; } } } return rc; } static void build_clear_xpath_string(GString *buf, const xmlNode *constraint_node, const char *rsc, const char *node, bool promoted_role_only) { const char *cons_id = pcmk__xe_id(constraint_node); const char *cons_rsc = crm_element_value(constraint_node, PCMK_XA_RSC); GString *rsc_role_substr = NULL; const char *promoted_role_rule = "@" PCMK_XA_ROLE "='" PCMK_ROLE_PROMOTED "' or @" PCMK_XA_ROLE "='" PCMK__ROLE_PROMOTED_LEGACY "'"; pcmk__assert(buf != NULL); g_string_truncate(buf, 0); if (!pcmk__starts_with(cons_id, "cli-ban-") && !pcmk__starts_with(cons_id, "cli-prefer-")) { return; } g_string_append(buf, "//" PCMK_XE_RSC_LOCATION); if ((node != NULL) || (rsc != NULL) || promoted_role_only) { g_string_append_c(buf, '['); if (node != NULL) { pcmk__g_strcat(buf, "@" PCMK_XE_NODE "='", node, "'", NULL); if (promoted_role_only || (rsc != NULL)) { g_string_append(buf, " and "); } } if ((rsc != NULL) && promoted_role_only) { rsc_role_substr = g_string_sized_new(64); pcmk__g_strcat(rsc_role_substr, "@" PCMK_XA_RSC "='", rsc, "' " "and (" , promoted_role_rule, ")", NULL); } else if (rsc != NULL) { rsc_role_substr = g_string_sized_new(64); pcmk__g_strcat(rsc_role_substr, "@" PCMK_XA_RSC "='", rsc, "'", NULL); } else if (promoted_role_only) { rsc_role_substr = g_string_sized_new(64); g_string_append(rsc_role_substr, promoted_role_rule); } if (rsc_role_substr != NULL) { g_string_append(buf, rsc_role_substr->str); } g_string_append_c(buf, ']'); } if (node != NULL) { g_string_append(buf, "|//" PCMK_XE_RSC_LOCATION); if (rsc_role_substr != NULL) { pcmk__g_strcat(buf, "[", rsc_role_substr, "]", NULL); } pcmk__g_strcat(buf, "/" PCMK_XE_RULE "[" PCMK_XE_EXPRESSION "[@" PCMK_XA_ATTRIBUTE "='" CRM_ATTR_UNAME "' " "and @" PCMK_XA_VALUE "='", node, "']]", NULL); } g_string_append(buf, "//" PCMK_XE_DATE_EXPRESSION "[@" PCMK_XA_ID "='"); if (pcmk__starts_with(cons_id, "cli-ban-")) { pcmk__g_strcat(buf, cons_id, "-lifetime']", NULL); } else { // starts with "cli-prefer-" pcmk__g_strcat(buf, "cli-prefer-lifetime-end-", cons_rsc, "']", NULL); } if (rsc_role_substr != NULL) { g_string_free(rsc_role_substr, TRUE); } } // \return Standard Pacemaker return code int cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, const char *rsc, const char *node, gboolean promoted_role_only) { GString *buf = NULL; xmlXPathObject *xpathObj = NULL; xmlNode *cib_constraints = NULL; crm_time_t *now = crm_time_new(NULL); int num_results = 0; int rc = pcmk_rc_ok; cib_constraints = pcmk_find_cib_element(root, PCMK_XE_CONSTRAINTS); xpathObj = pcmk__xpath_search(cib_constraints->doc, "//" PCMK_XE_RSC_LOCATION); num_results = pcmk__xpath_num_results(xpathObj); for (int i = 0; i < num_results; i++) { xmlNode *constraint_node = pcmk__xpath_result(xpathObj, i); xmlNode *date_expr_node = NULL; crm_time_t *end = NULL; int rc = pcmk_rc_ok; + if (constraint_node == NULL) { + continue; + } + if (buf == NULL) { buf = g_string_sized_new(1024); } build_clear_xpath_string(buf, constraint_node, rsc, node, promoted_role_only); if (buf->len == 0) { continue; } date_expr_node = get_xpath_object((const char *) buf->str, constraint_node, LOG_DEBUG); if (date_expr_node == NULL) { continue; } /* And then finally, see if the date expression is expired. If so, * clear the constraint. */ rc = pcmk__xe_get_datetime(date_expr_node, PCMK_XA_END, &end); if (rc != pcmk_rc_ok) { crm_trace("Date expression %s has invalid " PCMK_XA_END ": %s", pcmk__s(pcmk__xe_id(date_expr_node), "without ID"), pcmk_rc_str(rc)); continue; // Treat as unexpired } if (crm_time_compare(now, end) == 1) { xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = pcmk__xe_create(NULL, PCMK_XE_CONSTRAINTS); location = pcmk__xe_create(fragment, PCMK_XE_RSC_LOCATION); pcmk__xe_set_id(location, "%s", pcmk__xe_id(constraint_node)); crm_log_xml_info(fragment, "Delete"); rc = cib_conn->cmds->remove(cib_conn, PCMK_XE_CONSTRAINTS, fragment, cib_sync_call); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { goto done; } pcmk__xml_free(fragment); } crm_time_free(end); } done: if (buf != NULL) { g_string_free(buf, TRUE); } xmlXPathFreeObject(xpathObj); crm_time_free(now); return rc; }