diff --git a/lib/pacemaker/pcmk_sched_tickets.c b/lib/pacemaker/pcmk_sched_tickets.c index 68cdb5e857..1d85620633 100644 --- a/lib/pacemaker/pcmk_sched_tickets.c +++ b/lib/pacemaker/pcmk_sched_tickets.c @@ -1,550 +1,547 @@ /* * 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 #include "libpacemaker_private.h" enum loss_ticket_policy { loss_ticket_stop, loss_ticket_demote, loss_ticket_fence, loss_ticket_freeze }; typedef struct { const char *id; pcmk_resource_t *rsc; pcmk__ticket_t *ticket; enum loss_ticket_policy loss_policy; int role; } rsc_ticket_t; /*! * \brief Check whether a ticket constraint matches a resource by role * * \param[in] rsc_ticket Ticket constraint * \param[in] rsc Resource to compare with ticket * * \param[in] true if constraint has no role or resource's role matches * constraint's, otherwise false */ static bool ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket) { if ((rsc_ticket->role == pcmk_role_unknown) || (rsc_ticket->role == rsc->priv->orig_role)) { return true; } pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter", pcmk_role_text(rsc_ticket->role)); return false; } /*! * \brief Create location constraints and fencing as needed for a ticket * * \param[in,out] rsc Resource affected by ticket * \param[in] rsc_ticket Ticket */ static void constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket) { GList *iter = NULL; CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return); if (pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted) && !pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_standby)) { return; } if (rsc->priv->children != NULL) { pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id); for (iter = rsc->priv->children; iter != NULL; iter = iter->next) { constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket); } return; } pcmk__rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)", rsc->id, rsc_ticket->ticket->id, rsc_ticket->id, pcmk_role_text(rsc_ticket->role)); if (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted) && (rsc->priv->active_nodes != NULL)) { switch (rsc_ticket->loss_policy) { case loss_ticket_stop: resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__loss_of_ticket__", rsc->priv->scheduler); break; case loss_ticket_demote: // Promotion score will be set to -INFINITY in promotion_order() if (rsc_ticket->role != pcmk_role_promoted) { resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__loss_of_ticket__", rsc->priv->scheduler); } break; case loss_ticket_fence: if (!ticket_role_matches(rsc, rsc_ticket)) { return; } resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__loss_of_ticket__", rsc->priv->scheduler); for (iter = rsc->priv->active_nodes; iter != NULL; iter = iter->next) { pe_fence_node(rsc->priv->scheduler, (pcmk_node_t *) iter->data, "deadman ticket was lost", FALSE); } break; case loss_ticket_freeze: if (!ticket_role_matches(rsc, rsc_ticket)) { return; } if (rsc->priv->active_nodes != NULL) { pcmk__clear_rsc_flags(rsc, pcmk__rsc_managed); pcmk__set_rsc_flags(rsc, pcmk__rsc_blocked); } break; } } else if (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted)) { if ((rsc_ticket->role != pcmk_role_promoted) || (rsc_ticket->loss_policy == loss_ticket_stop)) { resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__no_ticket__", rsc->priv->scheduler); } } else if (pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_standby)) { if ((rsc_ticket->role != pcmk_role_promoted) || (rsc_ticket->loss_policy == loss_ticket_stop)) { resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__ticket_standby__", rsc->priv->scheduler); } } } static void rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk__ticket_t *ticket, - const char *state, const char *loss_policy) + const char *role_spec, const char *loss_policy) { rsc_ticket_t *new_rsc_ticket = NULL; + enum rsc_role_e role = pcmk_role_unknown; if (rsc == NULL) { pcmk__config_err("Ignoring ticket '%s' because resource " "does not exist", id); return; } - - new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t)); - if (new_rsc_ticket == NULL) { + if (pcmk__parse_constraint_role(id, role_spec, &role) != pcmk_rc_ok) { + // Not possible with schema validation enabled (error already logged) return; } - if (pcmk__str_eq(state, PCMK_ROLE_STARTED, - pcmk__str_null_matches|pcmk__str_casei)) { - state = PCMK__ROLE_UNKNOWN; - } - + new_rsc_ticket = pcmk__assert_alloc(1, sizeof(rsc_ticket_t)); new_rsc_ticket->id = id; new_rsc_ticket->ticket = ticket; new_rsc_ticket->rsc = rsc; - new_rsc_ticket->role = pcmk_parse_role(state); + new_rsc_ticket->role = role; if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) { if (pcmk_is_set(rsc->priv->scheduler->flags, pcmk__sched_fencing_enabled)) { new_rsc_ticket->loss_policy = loss_ticket_fence; } else { pcmk__config_err("Resetting '" PCMK_XA_LOSS_POLICY "' " "for ticket '%s' to '" PCMK_VALUE_STOP "' " "because fencing is not configured", ticket->id); loss_policy = PCMK_VALUE_STOP; } } if (new_rsc_ticket->loss_policy == loss_ticket_fence) { crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) { crm_debug("On loss of ticket '%s': Freeze %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); new_rsc_ticket->loss_policy = loss_ticket_freeze; } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) { crm_debug("On loss of ticket '%s': Demote %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); new_rsc_ticket->loss_policy = loss_ticket_demote; } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) { crm_debug("On loss of ticket '%s': Stop %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); new_rsc_ticket->loss_policy = loss_ticket_stop; } else { if (new_rsc_ticket->role == pcmk_role_promoted) { crm_debug("On loss of ticket '%s': Default to demote %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); new_rsc_ticket->loss_policy = loss_ticket_demote; } else { crm_debug("On loss of ticket '%s': Default to stop %s (%s)", new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id, pcmk_role_text(new_rsc_ticket->role)); new_rsc_ticket->loss_policy = loss_ticket_stop; } } pcmk__rsc_trace(rsc, "%s (%s) ==> %s", rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id); rsc->priv->ticket_constraints = g_list_append(rsc->priv->ticket_constraints, new_rsc_ticket); if (!pcmk_is_set(new_rsc_ticket->ticket->flags, pcmk__ticket_granted) || pcmk_is_set(new_rsc_ticket->ticket->flags, pcmk__ticket_standby)) { constraints_for_ticket(rsc, new_rsc_ticket); } } // \return Standard Pacemaker return code static int unpack_rsc_ticket_set(xmlNode *set, pcmk__ticket_t *ticket, const char *loss_policy, pcmk_scheduler_t *scheduler) { const char *set_id = NULL; const char *role = NULL; CRM_CHECK(set != NULL, return EINVAL); CRM_CHECK(ticket != NULL, return EINVAL); set_id = pcmk__xe_id(set); if (set_id == NULL) { pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without " PCMK_XA_ID); return pcmk_rc_unpack_error; } role = crm_element_value(set, PCMK_XA_ROLE); for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { pcmk_resource_t *resource = NULL; resource = pcmk__find_constraint_resource(scheduler->priv->resources, pcmk__xe_id(xml_rsc)); if (resource == NULL) { pcmk__config_err("%s: No resource found for %s", set_id, pcmk__xe_id(xml_rsc)); return pcmk_rc_unpack_error; } pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'", resource->id, ticket->id); rsc_ticket_new(set_id, resource, ticket, role, loss_policy); } return pcmk_rc_ok; } static void unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { const char *id = NULL; const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET); const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY); pcmk__ticket_t *ticket = NULL; const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE); // @COMPAT: Deprecated since 2.1.5 const char *instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE); pcmk_resource_t *rsc = NULL; if (instance != NULL) { pcmk__warn_once(pcmk__wo_coloc_inst, "Support for " PCMK__XA_RSC_INSTANCE " is deprecated " "and will be removed in a future release"); } CRM_CHECK(xml_obj != NULL, return); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return; } if (ticket_str == NULL) { pcmk__config_err("Ignoring constraint '%s' without ticket specified", id); return; } else { ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints, ticket_str); } if (ticket == NULL) { pcmk__config_err("Ignoring constraint '%s' because ticket '%s' " "does not exist", id, ticket_str); return; } if (rsc_id == NULL) { pcmk__config_err("Ignoring constraint '%s' without resource", id); return; } else { rsc = pcmk__find_constraint_resource(scheduler->priv->resources, rsc_id); } if (rsc == NULL) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "does not exist", id, rsc_id); return; } else if ((instance != NULL) && !pcmk__is_clone(rsc)) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "is not a clone but instance '%s' was requested", id, rsc_id, instance); return; } if (instance != NULL) { rsc = find_clone_instance(rsc, instance); if (rsc == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not have an instance '%s'", id, rsc_id, instance); return; } } rsc_ticket_new(id, rsc, ticket, state, loss_policy); } // \return Standard Pacemaker return code static int unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pcmk_scheduler_t *scheduler) { const char *id = NULL; const char *rsc_id = NULL; const char *state = NULL; pcmk_resource_t *rsc = NULL; pcmk__idref_t *tag = NULL; xmlNode *rsc_set = NULL; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return pcmk_rc_unpack_error; } // Check whether there are any resource sets with template or tag references *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket"); return pcmk_rc_ok; } rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); if (rsc_id == NULL) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, rsc_id); return pcmk_rc_unpack_error; } else if (rsc != NULL) { // No template or tag is referenced return pcmk_rc_ok; } state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE); *expanded_xml = pcmk__xml_copy(NULL, xml_obj); /* Convert any template or tag reference in "rsc" into ticket * PCMK_XE_RESOURCE_SET */ if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false, scheduler)) { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (rsc_set != NULL) { if (state != NULL) { /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a * PCMK_XA_ROLE attribute */ crm_xml_add(rsc_set, PCMK_XA_ROLE, state); pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE); } } else { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { xmlNode *set = NULL; bool any_sets = false; const char *id = NULL; const char *ticket_str = NULL; pcmk__ticket_t *ticket = NULL; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return; } if (scheduler->priv->ticket_constraints == NULL) { scheduler->priv->ticket_constraints = pcmk__strkey_table(free, destroy_ticket); } ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET); if (ticket_str == NULL) { pcmk__config_err("Ignoring constraint '%s' without ticket", id); return; } else { ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints, ticket_str); } if (ticket == NULL) { ticket = ticket_new(ticket_str, scheduler); if (ticket == NULL) { return; } } if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) { return; } if (expanded_xml != NULL) { orig_xml = xml_obj; xml_obj = expanded_xml; } for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL); set != NULL; set = pcmk__xe_next_same(set)) { const char *loss_policy = NULL; any_sets = true; set = pcmk__xe_resolve_idref(set, scheduler->input); loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY); if ((set == NULL) // Configuration error, message already logged || (unpack_rsc_ticket_set(set, ticket, loss_policy, scheduler) != pcmk_rc_ok)) { if (expanded_xml != NULL) { pcmk__xml_free(expanded_xml); } return; } } if (expanded_xml) { pcmk__xml_free(expanded_xml); xml_obj = orig_xml; } if (!any_sets) { unpack_simple_rsc_ticket(xml_obj, scheduler); } } /*! * \internal * \brief Ban resource from a node if it doesn't have a promotion ticket * * If a resource has tickets for the promoted role, and the ticket is either not * granted or set to standby, then ban the resource from all nodes. * * \param[in,out] rsc Resource to check */ void pcmk__require_promotion_tickets(pcmk_resource_t *rsc) { for (GList *item = rsc->priv->ticket_constraints; item != NULL; item = item->next) { rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data; if ((rsc_ticket->role == pcmk_role_promoted) && (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted) || pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_standby))) { resource_location(rsc, NULL, -PCMK_SCORE_INFINITY, "__stateful_without_ticket__", rsc->priv->scheduler); } } }