diff --git a/daemons/controld/controld_te_events.c b/daemons/controld/controld_te_events.c index 70c3442498..6f71b80441 100644 --- a/daemons/controld/controld_te_events.c +++ b/daemons/controld/controld_te_events.c @@ -1,611 +1,611 @@ /* * Copyright 2004-2024 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 /*! * \internal * \brief Action numbers of outside events processed in current update diff * * This table is to be used as a set. It should be empty when the transitioner * begins processing a CIB update diff. It ensures that if there are multiple * events (for example, "_last_0" and "_last_failure_0") for the same action, * only one of them updates the failcount. Events that originate outside the * cluster can't be confirmed, since they're not in the transition graph. */ static GHashTable *outside_events = NULL; /*! * \internal * \brief Empty the hash table containing action numbers of outside events */ void controld_remove_all_outside_events(void) { if (outside_events != NULL) { g_hash_table_remove_all(outside_events); } } /*! * \internal * \brief Destroy the hash table containing action numbers of outside events */ void controld_destroy_outside_events_table(void) { if (outside_events != NULL) { g_hash_table_destroy(outside_events); outside_events = NULL; } } /*! * \internal * \brief Add an outside event's action number to a set * * \return Standard Pacemaker return code. Specifically, \p pcmk_rc_ok if the * event was not already in the set, or \p pcmk_rc_already otherwise. */ static int record_outside_event(gint action_num) { if (outside_events == NULL) { outside_events = g_hash_table_new(NULL, NULL); } if (g_hash_table_add(outside_events, GINT_TO_POINTER(action_num))) { return pcmk_rc_ok; } return pcmk_rc_already; } gboolean fail_incompletable_actions(pcmk__graph_t *graph, const char *down_node) { const char *target_uuid = NULL; const char *router = NULL; const char *router_uuid = NULL; xmlNode *last_action = NULL; GList *gIter = NULL; GList *gIter2 = NULL; if (graph == NULL || graph->complete) { return FALSE; } gIter = graph->synapses; for (; gIter != NULL; gIter = gIter->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) gIter->data; if (pcmk_any_flags_set(synapse->flags, pcmk__synapse_confirmed|pcmk__synapse_failed)) { /* We've already been here */ continue; } gIter2 = synapse->actions; for (; gIter2 != NULL; gIter2 = gIter2->next) { pcmk__graph_action_t *action = (pcmk__graph_action_t *) gIter2->data; if ((action->type == pcmk__pseudo_graph_action) || pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) { continue; } else if (action->type == pcmk__cluster_graph_action) { const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION); if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_casei)) { continue; } } target_uuid = crm_element_value(action->xml, PCMK__META_ON_NODE_UUID); router = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE); if (router) { crm_node_t *node = pcmk__get_node(0, router, NULL, pcmk__node_search_cluster); if (node) { router_uuid = node->uuid; } } if (pcmk__str_eq(target_uuid, down_node, pcmk__str_casei) || pcmk__str_eq(router_uuid, down_node, pcmk__str_casei)) { pcmk__set_graph_action_flags(action, pcmk__graph_action_failed); pcmk__set_synapse_flags(synapse, pcmk__synapse_failed); last_action = action->xml; stop_te_timer(action); pcmk__update_graph(graph, action); if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) { crm_notice("Action %d (%s) was pending on %s (offline)", action->id, crm_element_value(action->xml, PCMK__XA_OPERATION_KEY), down_node); } else { crm_info("Action %d (%s) is scheduled for %s (offline)", action->id, crm_element_value(action->xml, PCMK__XA_OPERATION_KEY), down_node); } } } } if (last_action != NULL) { crm_info("Node %s shutdown resulted in un-runnable actions", down_node); abort_transition(INFINITY, pcmk__graph_restart, "Node failure", last_action); return TRUE; } return FALSE; } /*! * \internal * \brief Update failure-related node attributes if warranted * * \param[in] event XML describing operation that (maybe) failed * \param[in] event_node_uuid Node that event occurred on * \param[in] rc Actual operation return code * \param[in] target_rc Expected operation return code * \param[in] do_update If TRUE, do update regardless of operation type * \param[in] ignore_failures If TRUE, update last failure but not fail count * * \return TRUE if this was not a direct nack, success or lrm status refresh */ static gboolean update_failcount(const xmlNode *event, const char *event_node_uuid, int rc, int target_rc, gboolean do_update, gboolean ignore_failures) { guint interval_ms = 0; char *task = NULL; char *rsc_id = NULL; const char *value = NULL; const char *id = crm_element_value(event, PCMK__XA_OPERATION_KEY); const char *on_uname = crm_peer_uname(event_node_uuid); const char *origin = crm_element_value(event, PCMK_XA_CRM_DEBUG_ORIGIN); // Nothing needs to be done for success or status refresh if (rc == target_rc) { return FALSE; } else if (pcmk__str_eq(origin, "build_active_RAs", pcmk__str_casei)) { crm_debug("No update for %s (rc=%d) on %s: Old failure from lrm status refresh", id, rc, on_uname); return FALSE; } /* Sanity check */ CRM_CHECK(on_uname != NULL, return TRUE); CRM_CHECK(parse_op_key(id, &rsc_id, &task, &interval_ms), crm_err("Couldn't parse: %s", pcmk__xe_id(event)); goto bail); /* Decide whether update is necessary and what value to use */ if ((interval_ms > 0) || pcmk__str_eq(task, PCMK_ACTION_PROMOTE, pcmk__str_none) || pcmk__str_eq(task, PCMK_ACTION_DEMOTE, pcmk__str_none)) { do_update = TRUE; } else if (pcmk__str_eq(task, PCMK_ACTION_START, pcmk__str_none)) { do_update = TRUE; value = pcmk__s(controld_globals.transition_graph->failed_start_offset, - CRM_INFINITY_S); + PCMK_VALUE_INFINITY); } else if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_none)) { do_update = TRUE; value = pcmk__s(controld_globals.transition_graph->failed_stop_offset, - CRM_INFINITY_S); + PCMK_VALUE_INFINITY); } if (do_update) { pcmk__attrd_query_pair_t *fail_pair = NULL; pcmk__attrd_query_pair_t *last_pair = NULL; char *fail_name = NULL; char *last_name = NULL; GList *attrs = NULL; uint32_t opts = pcmk__node_attr_none; char *now = pcmk__ttoa(time(NULL)); // Fail count will be either incremented or set to infinity if (!pcmk_str_is_infinity(value)) { value = PCMK_XA_VALUE "++"; } if (g_hash_table_lookup(crm_remote_peer_cache, event_node_uuid)) { opts |= pcmk__node_attr_remote; } crm_info("Updating %s for %s on %s after failed %s: rc=%d (update=%s, time=%s)", (ignore_failures? "last failure" : "failcount"), rsc_id, on_uname, task, rc, value, now); /* Update the fail count, if we're not ignoring failures */ if (!ignore_failures) { fail_pair = calloc(1, sizeof(pcmk__attrd_query_pair_t)); CRM_ASSERT(fail_pair != NULL); fail_name = pcmk__failcount_name(rsc_id, task, interval_ms); fail_pair->name = fail_name; fail_pair->value = value; fail_pair->node = on_uname; attrs = g_list_prepend(attrs, fail_pair); } /* Update the last failure time (even if we're ignoring failures, * so that failure can still be detected and shown, e.g. by crm_mon) */ last_pair = calloc(1, sizeof(pcmk__attrd_query_pair_t)); CRM_ASSERT(last_pair != NULL); last_name = pcmk__lastfailure_name(rsc_id, task, interval_ms); last_pair->name = last_name; last_pair->value = now; last_pair->node = on_uname; attrs = g_list_prepend(attrs, last_pair); update_attrd_list(attrs, opts); free(fail_name); free(fail_pair); free(last_name); free(last_pair); g_list_free(attrs); free(now); } bail: free(rsc_id); free(task); return TRUE; } pcmk__graph_action_t * controld_get_action(int id) { for (GList *item = controld_globals.transition_graph->synapses; item != NULL; item = item->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) item->data; for (GList *item2 = synapse->actions; item2; item2 = item2->next) { pcmk__graph_action_t *action = (pcmk__graph_action_t *) item2->data; if (action->id == id) { return action; } } } return NULL; } pcmk__graph_action_t * get_cancel_action(const char *id, const char *node) { GList *gIter = NULL; GList *gIter2 = NULL; gIter = controld_globals.transition_graph->synapses; for (; gIter != NULL; gIter = gIter->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) gIter->data; gIter2 = synapse->actions; for (; gIter2 != NULL; gIter2 = gIter2->next) { const char *task = NULL; const char *target = NULL; pcmk__graph_action_t *action = (pcmk__graph_action_t *) gIter2->data; task = crm_element_value(action->xml, PCMK_XA_OPERATION); if (!pcmk__str_eq(PCMK_ACTION_CANCEL, task, pcmk__str_casei)) { continue; } task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY); if (!pcmk__str_eq(task, id, pcmk__str_casei)) { crm_trace("Wrong key %s for %s on %s", task, id, node); continue; } target = crm_element_value(action->xml, PCMK__META_ON_NODE_UUID); if (node && !pcmk__str_eq(target, node, pcmk__str_casei)) { crm_trace("Wrong node %s for %s on %s", target, id, node); continue; } crm_trace("Found %s on %s", id, node); return action; } } return NULL; } bool confirm_cancel_action(const char *id, const char *node_id) { const char *op_key = NULL; const char *node_name = NULL; pcmk__graph_action_t *cancel = get_cancel_action(id, node_id); if (cancel == NULL) { return FALSE; } op_key = crm_element_value(cancel->xml, PCMK__XA_OPERATION_KEY); node_name = crm_element_value(cancel->xml, PCMK__META_ON_NODE); stop_te_timer(cancel); te_action_confirmed(cancel, controld_globals.transition_graph); crm_info("Cancellation of %s on %s confirmed (action %d)", op_key, node_name, cancel->id); return TRUE; } /* downed nodes are listed like: ... */ #define XPATH_DOWNED "//" PCMK__XE_DOWNED \ "/" PCMK_XE_NODE "[@" PCMK_XA_ID "='%s']" /*! * \brief Find a transition event that would have made a specified node down * * \param[in] target UUID of node to match * * \return Matching event if found, NULL otherwise */ pcmk__graph_action_t * match_down_event(const char *target) { pcmk__graph_action_t *match = NULL; xmlXPathObjectPtr xpath_ret = NULL; GList *gIter, *gIter2; char *xpath = crm_strdup_printf(XPATH_DOWNED, target); for (gIter = controld_globals.transition_graph->synapses; gIter != NULL && match == NULL; gIter = gIter->next) { for (gIter2 = ((pcmk__graph_synapse_t * ) gIter->data)->actions; gIter2 != NULL && match == NULL; gIter2 = gIter2->next) { match = (pcmk__graph_action_t *) gIter2->data; if (pcmk_is_set(match->flags, pcmk__graph_action_executed)) { xpath_ret = xpath_search(match->xml, xpath); if (numXpathResults(xpath_ret) < 1) { match = NULL; } freeXpathObject(xpath_ret); } else { // Only actions that were actually started can match match = NULL; } } } free(xpath); if (match != NULL) { crm_debug("Shutdown action %d (%s) found for node %s", match->id, crm_element_value(match->xml, PCMK__XA_OPERATION_KEY), target); } else { crm_debug("No reason to expect node %s to be down", target); } return match; } void process_graph_event(xmlNode *event, const char *event_node) { int rc = -1; // Actual result int target_rc = -1; // Expected result int status = -1; // Executor status int callid = -1; // Executor call ID int transition_num = -1; // Transition number int action_num = -1; // Action number within transition char *update_te_uuid = NULL; bool ignore_failures = FALSE; const char *id = NULL; const char *desc = NULL; const char *magic = NULL; const char *uname = NULL; CRM_ASSERT(event != NULL); /* */ magic = crm_element_value(event, PCMK__XA_TRANSITION_KEY); if (magic == NULL) { /* non-change */ return; } crm_element_value_int(event, PCMK__XA_OP_STATUS, &status); if (status == PCMK_EXEC_PENDING) { return; } id = crm_element_value(event, PCMK__XA_OPERATION_KEY); crm_element_value_int(event, PCMK__XA_RC_CODE, &rc); crm_element_value_int(event, PCMK__XA_CALL_ID, &callid); rc = pcmk__effective_rc(rc); if (decode_transition_key(magic, &update_te_uuid, &transition_num, &action_num, &target_rc) == FALSE) { // decode_transition_key() already logged the bad key crm_err("Can't process action %s result: Incompatible versions? " CRM_XS " call-id=%d", id, callid); abort_transition(INFINITY, pcmk__graph_restart, "Bad event", event); return; } if (transition_num == -1) { // E.g. crm_resource --fail if (record_outside_event(action_num) != pcmk_rc_ok) { crm_debug("Outside event with transition key '%s' has already been " "processed", magic); goto bail; } desc = "initiated outside of the cluster"; abort_transition(INFINITY, pcmk__graph_restart, "Unexpected event", event); } else if ((action_num < 0) || !pcmk__str_eq(update_te_uuid, controld_globals.te_uuid, pcmk__str_none)) { desc = "initiated by a different DC"; abort_transition(INFINITY, pcmk__graph_restart, "Foreign event", event); } else if ((controld_globals.transition_graph->id != transition_num) || controld_globals.transition_graph->complete) { // Action is not from currently active transition guint interval_ms = 0; if (parse_op_key(id, NULL, NULL, &interval_ms) && (interval_ms != 0)) { /* Recurring actions have the transition number they were first * scheduled in. */ if (status == PCMK_EXEC_CANCELLED) { confirm_cancel_action(id, get_node_id(event)); goto bail; } desc = "arrived after initial scheduling"; abort_transition(INFINITY, pcmk__graph_restart, "Change in recurring result", event); } else if (controld_globals.transition_graph->id != transition_num) { desc = "arrived really late"; abort_transition(INFINITY, pcmk__graph_restart, "Old event", event); } else { desc = "arrived late"; abort_transition(INFINITY, pcmk__graph_restart, "Inactive graph", event); } } else { // Event is result of an action from currently active transition pcmk__graph_action_t *action = controld_get_action(action_num); if (action == NULL) { // Should never happen desc = "unknown"; abort_transition(INFINITY, pcmk__graph_restart, "Unknown event", event); } else if (pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) { /* Nothing further needs to be done if the action has already been * confirmed. This can happen e.g. when processing both an * "xxx_last_0" or "xxx_last_failure_0" record as well as the main * history record, which would otherwise result in incorrectly * bumping the fail count twice. */ crm_log_xml_debug(event, "Event already confirmed:"); goto bail; } else { /* An action result needs to be confirmed. * (This is the only case where desc == NULL.) */ if (pcmk__str_eq(crm_meta_value(action->params, PCMK_META_ON_FAIL), PCMK_VALUE_IGNORE, pcmk__str_casei)) { ignore_failures = TRUE; } else if (rc != target_rc) { pcmk__set_graph_action_flags(action, pcmk__graph_action_failed); } stop_te_timer(action); te_action_confirmed(action, controld_globals.transition_graph); if (pcmk_is_set(action->flags, pcmk__graph_action_failed)) { abort_transition(action->synapse->priority + 1, pcmk__graph_restart, "Event failed", event); } } } if (id == NULL) { id = "unknown action"; } uname = crm_element_value(event, PCMK__META_ON_NODE); if (uname == NULL) { uname = "unknown node"; } if (status == PCMK_EXEC_INVALID) { // We couldn't attempt the action crm_info("Transition %d action %d (%s on %s): %s", transition_num, action_num, id, uname, pcmk_exec_status_str(status)); } else if (desc && update_failcount(event, event_node, rc, target_rc, (transition_num == -1), FALSE)) { crm_notice("Transition %d action %d (%s on %s): expected '%s' but got '%s' " CRM_XS " target-rc=%d rc=%d call-id=%d event='%s'", transition_num, action_num, id, uname, services_ocf_exitcode_str(target_rc), services_ocf_exitcode_str(rc), target_rc, rc, callid, desc); } else if (desc) { crm_info("Transition %d action %d (%s on %s): %s " CRM_XS " rc=%d target-rc=%d call-id=%d", transition_num, action_num, id, uname, desc, rc, target_rc, callid); } else if (rc == target_rc) { crm_info("Transition %d action %d (%s on %s) confirmed: %s " CRM_XS " rc=%d call-id=%d", transition_num, action_num, id, uname, services_ocf_exitcode_str(rc), rc, callid); } else { update_failcount(event, event_node, rc, target_rc, (transition_num == -1), ignore_failures); crm_notice("Transition %d action %d (%s on %s): expected '%s' but got '%s' " CRM_XS " target-rc=%d rc=%d call-id=%d", transition_num, action_num, id, uname, services_ocf_exitcode_str(target_rc), services_ocf_exitcode_str(rc), target_rc, rc, callid); } bail: free(update_te_uuid); } diff --git a/include/crm/common/options.h b/include/crm/common/options.h index 962e3433ca..a7a3b37a66 100644 --- a/include/crm/common/options.h +++ b/include/crm/common/options.h @@ -1,212 +1,213 @@ /* * Copyright 2024 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. */ #ifndef PCMK__CRM_COMMON_OPTIONS__H # define PCMK__CRM_COMMON_OPTIONS__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief API related to options * \ingroup core */ /* * Cluster options */ #define PCMK_OPT_BATCH_LIMIT "batch-limit" #define PCMK_OPT_CLUSTER_DELAY "cluster-delay" #define PCMK_OPT_CLUSTER_INFRASTRUCTURE "cluster-infrastructure" #define PCMK_OPT_CLUSTER_IPC_LIMIT "cluster-ipc-limit" #define PCMK_OPT_CLUSTER_NAME "cluster-name" #define PCMK_OPT_CLUSTER_RECHECK_INTERVAL "cluster-recheck-interval" #define PCMK_OPT_CONCURRENT_FENCING "concurrent-fencing" #define PCMK_OPT_DC_DEADTIME "dc-deadtime" #define PCMK_OPT_DC_VERSION "dc-version" #define PCMK_OPT_ELECTION_TIMEOUT "election-timeout" #define PCMK_OPT_ENABLE_ACL "enable-acl" #define PCMK_OPT_ENABLE_STARTUP_PROBES "enable-startup-probes" #define PCMK_OPT_FENCE_REACTION "fence-reaction" #define PCMK_OPT_HAVE_WATCHDOG "have-watchdog" #define PCMK_OPT_JOIN_FINALIZATION_TIMEOUT "join-finalization-timeout" #define PCMK_OPT_JOIN_INTEGRATION_TIMEOUT "join-integration-timeout" #define PCMK_OPT_LOAD_THRESHOLD "load-threshold" #define PCMK_OPT_MAINTENANCE_MODE "maintenance-mode" #define PCMK_OPT_MIGRATION_LIMIT "migration-limit" #define PCMK_OPT_NO_QUORUM_POLICY "no-quorum-policy" #define PCMK_OPT_NODE_ACTION_LIMIT "node-action-limit" #define PCMK_OPT_NODE_HEALTH_BASE "node-health-base" #define PCMK_OPT_NODE_HEALTH_GREEN "node-health-green" #define PCMK_OPT_NODE_HEALTH_RED "node-health-red" #define PCMK_OPT_NODE_HEALTH_STRATEGY "node-health-strategy" #define PCMK_OPT_NODE_HEALTH_YELLOW "node-health-yellow" #define PCMK_OPT_NODE_PENDING_TIMEOUT "node-pending-timeout" #define PCMK_OPT_PE_ERROR_SERIES_MAX "pe-error-series-max" #define PCMK_OPT_PE_INPUT_SERIES_MAX "pe-input-series-max" #define PCMK_OPT_PE_WARN_SERIES_MAX "pe-warn-series-max" #define PCMK_OPT_PLACEMENT_STRATEGY "placement-strategy" #define PCMK_OPT_PRIORITY_FENCING_DELAY "priority-fencing-delay" #define PCMK_OPT_SHUTDOWN_ESCALATION "shutdown-escalation" #define PCMK_OPT_SHUTDOWN_LOCK "shutdown-lock" #define PCMK_OPT_SHUTDOWN_LOCK_LIMIT "shutdown-lock-limit" #define PCMK_OPT_START_FAILURE_IS_FATAL "start-failure-is-fatal" #define PCMK_OPT_STARTUP_FENCING "startup-fencing" #define PCMK_OPT_STONITH_ACTION "stonith-action" #define PCMK_OPT_STONITH_ENABLED "stonith-enabled" #define PCMK_OPT_STONITH_MAX_ATTEMPTS "stonith-max-attempts" #define PCMK_OPT_STONITH_TIMEOUT "stonith-timeout" #define PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "stonith-watchdog-timeout" #define PCMK_OPT_STOP_ALL_RESOURCES "stop-all-resources" #define PCMK_OPT_STOP_ORPHAN_ACTIONS "stop-orphan-actions" #define PCMK_OPT_STOP_ORPHAN_RESOURCES "stop-orphan-resources" #define PCMK_OPT_SYMMETRIC_CLUSTER "symmetric-cluster" #define PCMK_OPT_TRANSITION_DELAY "transition-delay" /* * Meta-attributes */ #define PCMK_META_ALLOW_MIGRATE "allow-migrate" #define PCMK_META_ALLOW_UNHEALTHY_NODES "allow-unhealthy-nodes" #define PCMK_META_CLONE_MAX "clone-max" #define PCMK_META_CLONE_MIN "clone-min" #define PCMK_META_CLONE_NODE_MAX "clone-node-max" #define PCMK_META_CONTAINER_ATTRIBUTE_TARGET "container-attribute-target" #define PCMK_META_CRITICAL "critical" #define PCMK_META_ENABLED "enabled" #define PCMK_META_FAILURE_TIMEOUT "failure-timeout" #define PCMK_META_GLOBALLY_UNIQUE "globally-unique" #define PCMK_META_INTERLEAVE "interleave" #define PCMK_META_INTERVAL "interval" #define PCMK_META_IS_MANAGED "is-managed" #define PCMK_META_INTERVAL_ORIGIN "interval-origin" #define PCMK_META_MAINTENANCE "maintenance" #define PCMK_META_MIGRATION_THRESHOLD "migration-threshold" #define PCMK_META_MULTIPLE_ACTIVE "multiple-active" #define PCMK_META_NOTIFY "notify" #define PCMK_META_ON_FAIL "on-fail" #define PCMK_META_ORDERED "ordered" #define PCMK_META_PRIORITY "priority" #define PCMK_META_PROMOTABLE "promotable" #define PCMK_META_PROMOTED_MAX "promoted-max" #define PCMK_META_PROMOTED_NODE_MAX "promoted-node-max" #define PCMK_META_RECORD_PENDING "record-pending" #define PCMK_META_REMOTE_ADDR "remote-addr" #define PCMK_META_REMOTE_ALLOW_MIGRATE "remote-allow-migrate" #define PCMK_META_REMOTE_CONNECT_TIMEOUT "remote-connect-timeout" #define PCMK_META_REMOTE_NODE "remote-node" #define PCMK_META_REMOTE_PORT "remote-port" #define PCMK_META_REQUIRES "requires" #define PCMK_META_RESOURCE_STICKINESS "resource-stickiness" #define PCMK_META_START_DELAY "start-delay" #define PCMK_META_TARGET_ROLE "target-role" #define PCMK_META_TIMEOUT "timeout" #define PCMK_META_TIMESTAMP_FORMAT "timestamp-format" /* * Remote resource instance attributes */ #define PCMK_REMOTE_RA_ADDR "addr" #define PCMK_REMOTE_RA_PORT "port" #define PCMK_REMOTE_RA_RECONNECT_INTERVAL "reconnect_interval" #define PCMK_REMOTE_RA_SERVER "server" /* * Enumerated values */ #define PCMK_VALUE_ALWAYS "always" #define PCMK_VALUE_AND "and" #define PCMK_VALUE_BALANCED "balanced" #define PCMK_VALUE_BLOCK "block" #define PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS "cib-bootstrap-options" #define PCMK_VALUE_CREATE "create" #define PCMK_VALUE_CUSTOM "custom" #define PCMK_VALUE_DATE_SPEC "date_spec" #define PCMK_VALUE_DEFAULT "default" #define PCMK_VALUE_DEFINED "defined" #define PCMK_VALUE_DELETE "delete" #define PCMK_VALUE_DEMOTE "demote" #define PCMK_VALUE_DENY "deny" #define PCMK_VALUE_EQ "eq" #define PCMK_VALUE_EXCLUSIVE "exclusive" #define PCMK_VALUE_FAILED "failed" #define PCMK_VALUE_FALSE "false" #define PCMK_VALUE_FENCE "fence" #define PCMK_VALUE_FENCING "fencing" #define PCMK_VALUE_FREEZE "freeze" #define PCMK_VALUE_GRANTED "granted" #define PCMK_VALUE_GREEN "green" #define PCMK_VALUE_GT "gt" #define PCMK_VALUE_GTE "gte" #define PCMK_VALUE_HOST "host" #define PCMK_VALUE_IGNORE "ignore" +#define PCMK_VALUE_INFINITY "INFINITY" #define PCMK_VALUE_INTEGER "integer" #define PCMK_VALUE_LITERAL "literal" #define PCMK_VALUE_LT "lt" #define PCMK_VALUE_LTE "lte" #define PCMK_VALUE_MANDATORY "Mandatory" #define PCMK_VALUE_MEMBER "member" #define PCMK_VALUE_META "meta" #define PCMK_VALUE_MIGRATE_ON_RED "migrate-on-red" #define PCMK_VALUE_MINIMAL "minimal" #define PCMK_VALUE_MODIFY "modify" #define PCMK_VALUE_MOVE "move" #define PCMK_VALUE_NE "ne" #define PCMK_VALUE_NEVER "never" #define PCMK_VALUE_NONE "none" #define PCMK_VALUE_NOT_DEFINED "not_defined" #define PCMK_VALUE_NOTHING "nothing" #define PCMK_VALUE_NUMBER "number" #define PCMK_VALUE_OFFLINE "offline" #define PCMK_VALUE_ONLINE "online" #define PCMK_VALUE_ONLY_GREEN "only-green" #define PCMK_VALUE_OPTIONAL "Optional" #define PCMK_VALUE_OR "or" #define PCMK_VALUE_PANIC "panic" #define PCMK_VALUE_PARAM "param" #define PCMK_VALUE_PENDING "pending" #define PCMK_VALUE_PROGRESSIVE "progressive" #define PCMK_VALUE_QUORUM "quorum" #define PCMK_VALUE_READ "read" #define PCMK_VALUE_RED "red" #define PCMK_VALUE_REMOTE "remote" #define PCMK_VALUE_RESTART "restart" #define PCMK_VALUE_RESTART_CONTAINER "restart-container" #define PCMK_VALUE_REVOKED "revoked" #define PCMK_VALUE_SERIALIZE "Serialize" #define PCMK_VALUE_STANDBY "standby" #define PCMK_VALUE_STRING "string" #define PCMK_VALUE_STOP "stop" #define PCMK_VALUE_SUCCESS "success" #define PCMK_VALUE_TRUE "true" #define PCMK_VALUE_UNFENCING "unfencing" #define PCMK_VALUE_UNKNOWN "unknown" #define PCMK_VALUE_UTILIZATION "utilization" #define PCMK_VALUE_VERSION "version" #define PCMK_VALUE_WRITE "write" #define PCMK_VALUE_YELLOW "yellow" // @COMPAT This will become a deprecated alias for PCMK_VALUE_FENCE (see T279) #define PCMK_VALUE_FENCE_LEGACY "suicide" #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_OPTIONS__H diff --git a/include/crm/crm.h b/include/crm/crm.h index 000b05d108..9e23b42138 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,177 +1,177 @@ /* * Copyright 2004-2024 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. */ #ifndef PCMK__CRM_CRM__H # define PCMK__CRM_CRM__H # include # include # include # include # include # include #include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ #ifndef PCMK_ALLOW_DEPRECATED /*! * \brief Allow use of deprecated Pacemaker APIs * * By default, external code using Pacemaker headers is allowed to use * deprecated Pacemaker APIs. If PCMK_ALLOW_DEPRECATED is defined to 0 before * including any Pacemaker headers, deprecated APIs will be unusable. It is * strongly recommended to leave this unchanged for production and release * builds, to avoid breakage when users upgrade to new Pacemaker releases that * deprecate more APIs. This should be defined to 0 only for development and * testing builds when desiring to check for usage of currently deprecated APIs. */ #define PCMK_ALLOW_DEPRECATED 1 #endif /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently tested by Pacemaker code: * * >2.1: Operation updates include timing data * >=3.0.5: XML v2 digests are created * >=3.0.8: Peers do not need acks for cancellations * >=3.0.9: DC will send its own shutdown request to all peers * XML v2 patchsets are created by default * >=3.0.13: Fail counts include operation name and interval * >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED * >=3.19.0: DC supports PCMK__CIB_REQUEST_COMMIT_TRANSACT */ # define CRM_FEATURE_SET "3.19.0" /* Pacemaker's CPG protocols use fixed-width binary fields for the sender and * recipient of a CPG message. This imposes an arbitrary limit on cluster node * names. */ //! \brief Maximum length of a Corosync cluster node name (in bytes) #define MAX_NAME 256 # define CRM_META "CRM_meta" extern char *crm_system_name; // How we represent "infinite" scores # define CRM_SCORE_INFINITY 1000000 -# define CRM_INFINITY_S "INFINITY" -# define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S -# define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S +# define CRM_INFINITY_S PCMK_VALUE_INFINITY +# define CRM_PLUS_INFINITY_S "+" PCMK_VALUE_INFINITY +# define CRM_MINUS_INFINITY_S "-" PCMK_VALUE_INFINITY /* @COMPAT API < 2.0.0 Deprecated "infinity" aliases * * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first. * This, of course, complicates any attempt to use the other definition in any * code that includes this header. */ # undef INFINITY # define INFINITY_S "INFINITY" # define MINUS_INFINITY_S "-INFINITY" # define INFINITY 1000000 /* Sub-systems */ # define CRM_SYSTEM_DC "dc" #define CRM_SYSTEM_DCIB "dcib" // Primary instance of CIB manager # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_STONITHD "stonithd" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes // @TODO Replace these with PCMK_NODE_ATTR_* # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_PROTOCOL "#attrd-protocol" # define CRM_ATTR_FEATURE_SET "#feature-set" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN PCMK_ACTION_DO_SHUTDOWN # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_REFRESH "lrm_refresh" //!< Deprecated since 1.1.10 # define CRM_OP_LRM_DELETE PCMK_ACTION_LRM_DELETE # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT PCMK_ACTION_CLEAR_FAILCOUNT # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES PCMK_ACTION_MAINTENANCE_NODES /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # include # include # include # include #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus } #endif #endif diff --git a/lib/common/scores.c b/lib/common/scores.c index e2afb6dec2..a291e6dda2 100644 --- a/lib/common/scores.c +++ b/lib/common/scores.c @@ -1,166 +1,166 @@ /* * Copyright 2004-2024 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include // snprintf(), NULL #include // strcpy(), strdup() #include // size_t int pcmk__score_red = 0; int pcmk__score_green = 0; int pcmk__score_yellow = 0; /*! * \brief Get the integer value of a score string * * Given a string representation of a score, return the integer equivalent. * This accepts infinity strings as well as red, yellow, and green, and * bounds the result to +/-INFINITY. * * \param[in] score Score as string * * \return Integer value corresponding to \p score */ int char2score(const char *score) { if (score == NULL) { return 0; } else if (pcmk_str_is_minus_infinity(score)) { return -CRM_SCORE_INFINITY; } else if (pcmk_str_is_infinity(score)) { return CRM_SCORE_INFINITY; } else if (pcmk__str_eq(score, PCMK_VALUE_RED, pcmk__str_casei)) { return pcmk__score_red; } else if (pcmk__str_eq(score, PCMK_VALUE_YELLOW, pcmk__str_casei)) { return pcmk__score_yellow; } else if (pcmk__str_eq(score, PCMK_VALUE_GREEN, pcmk__str_casei)) { return pcmk__score_green; } else { long long score_ll; pcmk__scan_ll(score, &score_ll, 0LL); if (score_ll > CRM_SCORE_INFINITY) { return CRM_SCORE_INFINITY; } else if (score_ll < -CRM_SCORE_INFINITY) { return -CRM_SCORE_INFINITY; } else { return (int) score_ll; } } } /*! * \brief Return a displayable static string for a score value * * Given a score value, return a pointer to a static string representation of * the score suitable for log messages, output, etc. * * \param[in] score Score to display * * \return Pointer to static memory containing string representation of \p score * \note Subsequent calls to this function will overwrite the returned value, so * it should be used only in a local context such as a printf()-style * statement. */ const char * pcmk_readable_score(int score) { // The longest possible result is "-INFINITY" static char score_s[sizeof(CRM_MINUS_INFINITY_S)]; if (score >= CRM_SCORE_INFINITY) { - strcpy(score_s, CRM_INFINITY_S); + strcpy(score_s, PCMK_VALUE_INFINITY); } else if (score <= -CRM_SCORE_INFINITY) { strcpy(score_s, CRM_MINUS_INFINITY_S); } else { // Range is limited to +/-1000000, so no chance of overflow snprintf(score_s, sizeof(score_s), "%d", score); } return score_s; } /*! * \internal * \brief Add two scores, bounding to +/-INFINITY * * \param[in] score1 First score to add * \param[in] score2 Second score to add * * \note This function does not have context about what the scores mean, so it * does not log any messages. */ int pcmk__add_scores(int score1, int score2) { /* As long as CRM_SCORE_INFINITY is less than half of the maximum integer, * we can ignore the possibility of integer overflow. */ int result = score1 + score2; // First handle the cases where one or both is infinite if ((score1 <= -CRM_SCORE_INFINITY) || (score2 <= -CRM_SCORE_INFINITY)) { return -CRM_SCORE_INFINITY; } if ((score1 >= CRM_SCORE_INFINITY) || (score2 >= CRM_SCORE_INFINITY)) { return CRM_SCORE_INFINITY; } // Bound result to infinity. if (result >= CRM_SCORE_INFINITY) { return CRM_SCORE_INFINITY; } if (result <= -CRM_SCORE_INFINITY) { return -CRM_SCORE_INFINITY; } return result; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include char * score2char(int score) { char *result = strdup(pcmk_readable_score(score)); CRM_ASSERT(result != NULL); return result; } char * score2char_stack(int score, char *buf, size_t len) { CRM_CHECK((buf != NULL) && (len >= sizeof(CRM_MINUS_INFINITY_S)), return NULL); strcpy(buf, pcmk_readable_score(score)); return buf; } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/tests/scores/pcmk_readable_score_test.c b/lib/common/tests/scores/pcmk_readable_score_test.c index ae24159143..6a0275600a 100644 --- a/lib/common/tests/scores/pcmk_readable_score_test.c +++ b/lib/common/tests/scores/pcmk_readable_score_test.c @@ -1,33 +1,33 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2024 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 static void outside_limits(void **state) { assert_string_equal(pcmk_readable_score(CRM_SCORE_INFINITY * 2), - CRM_INFINITY_S); + PCMK_VALUE_INFINITY); assert_string_equal(pcmk_readable_score(-CRM_SCORE_INFINITY * 2), CRM_MINUS_INFINITY_S); } static void inside_limits(void **state) { assert_string_equal(pcmk_readable_score(0), "0"); assert_string_equal(pcmk_readable_score(1024), "1024"); assert_string_equal(pcmk_readable_score(-1024), "-1024"); } PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(outside_limits), cmocka_unit_test(inside_limits)) diff --git a/lib/common/utils.c b/lib/common/utils.c index a6bf4f9a8c..dfa64b21e1 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,553 +1,553 @@ /* * Copyright 2004-2024 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" CRM_TRACE_INIT_DATA(common); gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; char *crm_system_name = NULL; bool pcmk__is_user_in_group(const char *user, const char *group) { struct group *grent; char **gr_mem; if (user == NULL || group == NULL) { return false; } setgrent(); while ((grent = getgrent()) != NULL) { if (grent->gr_mem == NULL) { continue; } if(strcmp(group, grent->gr_name) != 0) { continue; } gr_mem = grent->gr_mem; while (*gr_mem != NULL) { if (!strcmp(user, *gr_mem++)) { endgrent(); return true; } } } endgrent(); return false; } int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) { int rc = pcmk_ok; char *buffer = NULL; struct passwd pwd; struct passwd *pwentry = NULL; buffer = calloc(1, PCMK__PW_BUFFER_LEN); if (buffer == NULL) { return -ENOMEM; } rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry); if (pwentry) { if (uid) { *uid = pwentry->pw_uid; } if (gid) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid); } else { rc = rc? -rc : -EINVAL; crm_info("User %s lookup: %s", name, pcmk_strerror(rc)); } free(buffer); return rc; } /*! * \brief Get user and group IDs of pacemaker daemon user * * \param[out] uid If non-NULL, where to store daemon user ID * \param[out] gid If non-NULL, where to store daemon group ID * * \return pcmk_ok on success, -errno otherwise */ int pcmk_daemon_user(uid_t *uid, gid_t *gid) { static uid_t daemon_uid; static gid_t daemon_gid; static bool found = false; int rc = pcmk_ok; if (!found) { rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); if (rc == pcmk_ok) { found = true; } } if (found) { if (uid) { *uid = daemon_uid; } if (gid) { *gid = daemon_gid; } } return rc; } /*! * \internal * \brief Return the integer equivalent of a portion of a string * * \param[in] text Pointer to beginning of string portion * \param[out] end_text This will point to next character after integer */ static int version_helper(const char *text, const char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if (text != NULL && text[0] != 0) { /* seemingly sacrificing const-correctness -- because while strtol doesn't modify the input, it doesn't want to artificially taint the "end_text" pointer-to-pointer-to-first-char-in-string with constness in case the input wasn't actually constant -- by semantic definition not a single character will get modified so it shall be perfectly safe to make compiler happy with dropping "const" qualifier here */ atoi_result = (int) strtol(text, (char **) end_text, 10); if (errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; const char *ver1_iter, *ver2_iter; if (version1 == NULL && version2 == NULL) { return 0; } else if (version1 == NULL) { return -1; } else if (version2 == NULL) { return 1; } ver1_iter = version1; ver2_iter = version2; while (1) { int digit1 = 0; int digit2 = 0; lpc++; if (ver1_iter == ver2_iter) { break; } if (ver1_iter != NULL) { digit1 = version_helper(ver1_iter, &ver1_iter); } if (ver2_iter != NULL) { digit2 = version_helper(ver2_iter, &ver2_iter); } if (digit1 < digit2) { rc = -1; break; } else if (digit1 > digit2) { rc = 1; break; } if (ver1_iter != NULL && *ver1_iter == '.') { ver1_iter++; } if (ver1_iter != NULL && *ver1_iter == '\0') { ver1_iter = NULL; } if (ver2_iter != NULL && *ver2_iter == '.') { ver2_iter++; } if (ver2_iter != NULL && *ver2_iter == 0) { ver2_iter = NULL; } } if (rc == 0) { crm_trace("%s == %s (%d)", version1, version2, lpc); } else if (rc < 0) { crm_trace("%s < %s (%d)", version1, version2, lpc); } else if (rc > 0) { crm_trace("%s > %s (%d)", version1, version2, lpc); } return rc; } /*! * \internal * \brief Log a failed assertion * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void log_assertion_as(const char *file, const char *function, int line, const char *assert_condition) { if (!pcmk__is_daemon) { crm_enable_stderr(TRUE); // Make sure command-line user sees message } crm_err("%s: Triggered fatal assertion at %s:%d : %s", function, file, line, assert_condition); } /* coverity[+kill] */ /*! * \internal * \brief Log a failed assertion and abort * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion * * \note This does not return */ static _Noreturn void abort_as(const char *file, const char *function, int line, const char *assert_condition) { log_assertion_as(file, function, line, assert_condition); abort(); } /* coverity[+kill] */ /*! * \internal * \brief Handle a failed assertion * * When called by a daemon, fork a child that aborts (to dump core), otherwise * abort the current process. * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void fail_assert_as(const char *file, const char *function, int line, const char *assert_condition) { int status = 0; pid_t pid = 0; if (!pcmk__is_daemon) { abort_as(file, function, line, assert_condition); // does not return } pid = fork(); switch (pid) { case -1: // Fork failed crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d " ": %s", function, file, line, assert_condition); break; case 0: // Child process: just abort to dump core abort(); break; default: // Parent process: wait for child crm_err("%s: Forked child [%d] to record non-fatal assertion at " "%s:%d : %s", function, pid, file, line, assert_condition); crm_write_blackbox(SIGTRAP, NULL); do { if (waitpid(pid, &status, 0) == pid) { return; // Child finished dumping core } } while (errno == EINTR); if (errno == ECHILD) { // crm_mon ignores SIGCHLD crm_trace("Cannot wait on forked child [%d] " "(SIGCHLD is probably ignored)", pid); } else { crm_err("Cannot wait on forked child [%d]: %s", pid, pcmk_rc_str(errno)); } break; } } /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { if (!do_fork) { abort_as(file, function, line, assert_condition); } else if (do_core) { fail_assert_as(file, function, line, assert_condition); } else { log_assertion_as(file, function, line, assert_condition); } } /*! * \internal * \brief Convert the current process to a daemon process * * Fork a child process, exit the parent, create a PID file with the current * process ID, and close the standard input/output/error file descriptors. * Exit instead if a daemon is already running and using the PID file. * * \param[in] name Daemon executable name * \param[in] pidfile File name to use as PID file */ void pcmk__daemonize(const char *name, const char *pidfile) { int rc; pid_t pid; /* Check before we even try... */ rc = pcmk__pidfile_matches(pidfile, 1, name, &pid); if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { crm_err("%s: already running [pid %lld in %s]", name, (long long) pid, pidfile); printf("%s: already running [pid %lld in %s]\n", name, (long long) pid, pidfile); crm_exit(CRM_EX_ERROR); } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR, "fork"); crm_exit(CRM_EX_OSERR); } else if (pid > 0) { crm_exit(CRM_EX_OK); } rc = pcmk__lock_pidfile(pidfile, name); if (rc != pcmk_rc_ok) { crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d", pidfile, name, pcmk_rc_str(rc), rc); printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_rc_str(rc), rc); crm_exit(CRM_EX_ERROR); } umask(S_IWGRP | S_IWOTH | S_IROTH); close(STDIN_FILENO); pcmk__open_devnull(O_RDONLY); // stdin (fd 0) close(STDOUT_FILENO); pcmk__open_devnull(O_WRONLY); // stdout (fd 1) close(STDERR_FILENO); pcmk__open_devnull(O_WRONLY); // stderr (fd 2) } #ifdef HAVE_UUID_UUID_H # include #endif char * crm_generate_uuid(void) { unsigned char uuid[16]; char *buffer = malloc(37); /* Including NUL byte */ CRM_ASSERT(buffer != NULL); uuid_generate(uuid); uuid_unparse(uuid, buffer); return buffer; } #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } #endif /*! * \brief Get the local hostname * * \return Newly allocated string with name, or NULL (and set errno) on error */ char * pcmk_hostname(void) { struct utsname hostinfo; return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename); } bool pcmk_str_is_infinity(const char *s) { - return pcmk__str_any_of(s, CRM_INFINITY_S, CRM_PLUS_INFINITY_S, NULL); + return pcmk__str_any_of(s, PCMK_VALUE_INFINITY, CRM_PLUS_INFINITY_S, NULL); } bool pcmk_str_is_minus_infinity(const char *s) { return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none); } /*! * \internal * \brief Sleep for given milliseconds * * \param[in] ms Time to sleep * * \note The full time might not be slept if a signal is received. */ void pcmk__sleep_ms(unsigned int ms) { // @TODO Impose a sane maximum sleep to avoid hanging a process for long //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP); // Use sleep() for any whole seconds if (ms >= 1000) { sleep(ms / 1000); ms -= ms / 1000; } if (ms == 0) { return; } #if defined(HAVE_NANOSLEEP) // nanosleep() is POSIX-2008, so prefer that { struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) }; nanosleep(&req, NULL); } #elif defined(HAVE_USLEEP) // usleep() is widely available, though considered obsolete usleep((useconds_t) ms); #else // Otherwise use a trick with select() timeout { struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms }; select(0, NULL, NULL, NULL, &tv); } #endif } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include guint crm_parse_interval_spec(const char *input) { long long msec = -1; errno = 0; if (input == NULL) { return 0; } else if (input[0] == 'P') { crm_time_t *period_s = crm_time_parse_duration(input); if (period_s) { msec = 1000 * crm_time_get_seconds(period_s); crm_time_free(period_s); } } else { msec = crm_get_msec(input); } if (msec < 0) { crm_warn("Using 0 instead of '%s'", input); errno = EINVAL; return 0; } return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec; } // LCOV_EXCL_STOP // End deprecated API diff --git a/tools/crm_resource_ban.c b/tools/crm_resource_ban.c index a9ec72be84..a1da9b0def 100644 --- a/tools/crm_resource_ban.c +++ b/tools/crm_resource_ban.c @@ -1,502 +1,502 @@ /* * Copyright 2004-2024 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 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, int cib_options, 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 = create_xml_node(NULL, PCMK_XE_CONSTRAINTS); location = create_xml_node(fragment, PCMK_XE_RSC_LOCATION); crm_xml_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 " CRM_MINUS_INFINITY_S " 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, CRM_MINUS_INFINITY_S); } else { xmlNode *rule = create_xml_node(location, PCMK_XE_RULE); xmlNode *expr = create_xml_node(rule, PCMK_XE_EXPRESSION); crm_xml_set_id(rule, "cli-ban-%s-on-%s-rule", rsc_id, host); crm_xml_add(rule, PCMK_XA_SCORE, CRM_MINUS_INFINITY_S); crm_xml_add(rule, PCMK_XA_BOOLEAN_OP, PCMK_VALUE_AND); crm_xml_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 = create_xml_node(rule, PCMK_XE_DATE_EXPRESSION); crm_xml_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_options); rc = pcmk_legacy2rc(rc); free_xml(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, cib_options, 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, int cib_options, 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 = create_xml_node(NULL, PCMK_XE_CONSTRAINTS); location = create_xml_node(fragment, PCMK_XE_RSC_LOCATION); crm_xml_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, CRM_INFINITY_S); + crm_xml_add(location, PCMK_XA_SCORE, PCMK_VALUE_INFINITY); } else { xmlNode *rule = create_xml_node(location, PCMK_XE_RULE); xmlNode *expr = create_xml_node(rule, PCMK_XE_EXPRESSION); crm_xml_set_id(rule, "cli-prefer-rule-%s", rsc_id); - crm_xml_add(rule, PCMK_XA_SCORE, CRM_INFINITY_S); + crm_xml_add(rule, PCMK_XA_SCORE, PCMK_VALUE_INFINITY); crm_xml_add(rule, PCMK_XA_BOOLEAN_OP, PCMK_VALUE_AND); crm_xml_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 = create_xml_node(rule, PCMK_XE_DATE_EXPRESSION); crm_xml_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_options); rc = pcmk_legacy2rc(rc); free_xml(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, cib_options, 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 cib_options) { 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_options); 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, int cib_options, bool clear_ban_constraints, gboolean force) { int rc = pcmk_rc_ok; xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, PCMK_XE_CONSTRAINTS); if (clear_ban_constraints == TRUE) { location = create_xml_node(fragment, PCMK_XE_RSC_LOCATION); crm_xml_set_id(location, "cli-ban-%s-on-%s", rsc_id, host); } location = create_xml_node(fragment, PCMK_XE_RSC_LOCATION); crm_xml_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_options); if (rc == -ENXIO) { rc = pcmk_rc_ok; } else { rc = pcmk_legacy2rc(rc); } free_xml(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, int cib_options, 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, cib_options); /* 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, cib_options, 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->details->uname, NULL, cib_conn, cib_options, 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 "'"; CRM_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, int cib_options, 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 i; int rc = pcmk_rc_ok; cib_constraints = pcmk_find_cib_element(root, PCMK_XE_CONSTRAINTS); xpathObj = xpath_search(cib_constraints, "//" PCMK_XE_RSC_LOCATION); for (i = 0; i < numXpathResults(xpathObj); i++) { xmlNode *constraint_node = getXpathResult(xpathObj, i); xmlNode *date_expr_node = NULL; crm_time_t *end = NULL; 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. */ end = crm_time_new(crm_element_value(date_expr_node, PCMK_XA_END)); if (crm_time_compare(now, end) == 1) { xmlNode *fragment = NULL; xmlNode *location = NULL; fragment = create_xml_node(NULL, PCMK_XE_CONSTRAINTS); location = create_xml_node(fragment, PCMK_XE_RSC_LOCATION); crm_xml_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_options); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { goto done; } free_xml(fragment); } crm_time_free(end); } done: if (buf != NULL) { g_string_free(buf, TRUE); } freeXpathObject(xpathObj); crm_time_free(now); return rc; }