diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c index 06b310b773..a0fa2e40b0 100644 --- a/lib/pacemaker/pcmk_sched_location.c +++ b/lib/pacemaker/pcmk_sched_location.c @@ -1,679 +1,678 @@ /* * Copyright 2004-2022 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 "libpacemaker_private.h" static int get_node_score(const char *rule, const char *score, bool raw, pe_node_t *node, pe_resource_t *rsc) { int score_f = 0; if (score == NULL) { pe_err("Rule %s: no score specified. Assuming 0.", rule); } else if (raw) { score_f = char2score(score); } else { const char *attr_score = pe_node_attribute_calculated(node, score, rsc); if (attr_score == NULL) { crm_debug("Rule %s: %s did not have a value for %s", rule, pe__node_name(node), score); score_f = -INFINITY; } else { crm_debug("Rule %s: %s had value %s for %s", rule, pe__node_name(node), attr_score, score); score_f = char2score(attr_score); } } return score_f; } static pe__location_t * generate_location_rule(pe_resource_t *rsc, xmlNode *rule_xml, const char *discovery, crm_time_t *next_change, pe_working_set_t *data_set, pe_re_match_data_t *re_match_data) { const char *rule_id = NULL; const char *score = NULL; const char *boolean = NULL; const char *role = NULL; GList *gIter = NULL; GList *match_L = NULL; bool do_and = true; bool accept = true; bool raw_score = true; bool score_allocated = false; pe__location_t *location_rule = NULL; rule_xml = expand_idref(rule_xml, data_set->input); if (rule_xml == NULL) { return NULL; } rule_id = crm_element_value(rule_xml, XML_ATTR_ID); boolean = crm_element_value(rule_xml, XML_RULE_ATTR_BOOLEAN_OP); role = crm_element_value(rule_xml, XML_RULE_ATTR_ROLE); crm_trace("Processing rule: %s", rule_id); if ((role != NULL) && (text2role(role) == RSC_ROLE_UNKNOWN)) { pe_err("Bad role specified for %s: %s", rule_id, role); return NULL; } score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE); if (score == NULL) { score = crm_element_value(rule_xml, XML_RULE_ATTR_SCORE_ATTRIBUTE); if (score != NULL) { raw_score = false; } } if (pcmk__str_eq(boolean, "or", pcmk__str_casei)) { do_and = false; } location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL, data_set); if (location_rule == NULL) { return NULL; } if ((re_match_data != NULL) && (re_match_data->nregs > 0) && (re_match_data->pmatch[0].rm_so != -1) && !raw_score) { char *result = pe_expand_re_matches(score, re_match_data); if (result != NULL) { score = result; score_allocated = true; } } if (role != NULL) { crm_trace("Setting role filter: %s", role); location_rule->role_filter = text2role(role); if (location_rule->role_filter == RSC_ROLE_UNPROMOTED) { /* Any promotable clone cannot be promoted without being in the * unpromoted role first. Ergo, any constraint for the unpromoted * role applies to every role. */ location_rule->role_filter = RSC_ROLE_UNKNOWN; } } if (do_and) { GList *gIter = NULL; match_L = pcmk__copy_node_list(data_set->nodes, true); for (gIter = match_L; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; node->weight = get_node_score(rule_id, score, raw_score, node, rsc); } } for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { int score_f = 0; pe_node_t *node = (pe_node_t *) gIter->data; pe_match_data_t match_data = { .re = re_match_data, .params = pe_rsc_params(rsc, node, data_set), .meta = rsc->meta, }; accept = pe_test_rule(rule_xml, node->details->attrs, RSC_ROLE_UNKNOWN, data_set->now, next_change, &match_data); crm_trace("Rule %s %s on %s", ID(rule_xml), accept? "passed" : "failed", pe__node_name(node)); score_f = get_node_score(rule_id, score, raw_score, node, rsc); if (accept) { pe_node_t *local = pe_find_node_id(match_L, node->details->id); if ((local == NULL) && do_and) { continue; } else if (local == NULL) { local = pe__copy_node(node); match_L = g_list_append(match_L, local); } if (!do_and) { local->weight = pcmk__add_scores(local->weight, score_f); } crm_trace("%s now has weight %d", pe__node_name(node), local->weight); } else if (do_and && !accept) { // Remove it pe_node_t *delete = pe_find_node_id(match_L, node->details->id); if (delete != NULL) { match_L = g_list_remove(match_L, delete); crm_trace("%s did not match", pe__node_name(node)); } free(delete); } } if (score_allocated) { free((char *)score); } location_rule->node_list_rh = match_L; if (location_rule->node_list_rh == NULL) { crm_trace("No matching nodes for rule %s", rule_id); return NULL; } crm_trace("%s: %d nodes matched", rule_id, g_list_length(location_rule->node_list_rh)); return location_rule; } static void unpack_rsc_location(xmlNode *xml_obj, pe_resource_t *rsc, const char *role, const char *score, pe_working_set_t *data_set, pe_re_match_data_t *re_match_data) { pe__location_t *location = NULL; const char *rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *node = crm_element_value(xml_obj, XML_CIB_TAG_NODE); const char *discovery = crm_element_value(xml_obj, XML_LOCATION_ATTR_DISCOVERY); if (rsc == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not exist", id, rsc_id); return; } if (score == NULL) { score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE); } if ((node != NULL) && (score != NULL)) { int score_i = char2score(score); pe_node_t *match = pe_find_node(data_set->nodes, node); if (!match) { return; } location = pcmk__new_location(id, rsc, score_i, discovery, match, data_set); } else { bool empty = true; crm_time_t *next_change = crm_time_new_undefined(); /* This loop is logically parallel to pe_evaluate_rules(), except * instead of checking whether any rule is active, we set up location * constraints for each active rule. */ for (xmlNode *rule_xml = first_named_child(xml_obj, XML_TAG_RULE); rule_xml != NULL; rule_xml = crm_next_same_xml(rule_xml)) { empty = false; crm_trace("Unpacking %s/%s", id, ID(rule_xml)); generate_location_rule(rsc, rule_xml, discovery, next_change, data_set, re_match_data); } if (empty) { pcmk__config_err("Ignoring constraint '%s' because it contains " "no rules", id); } /* If there is a point in the future when the evaluation of a rule will * change, make sure the scheduler is re-run by that time. */ if (crm_time_is_defined(next_change)) { time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(t, data_set); } crm_time_free(next_change); return; } if (role == NULL) { role = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE); } if ((location != NULL) && (role != NULL)) { if (text2role(role) == RSC_ROLE_UNKNOWN) { pe_err("Invalid constraint %s: Bad role %s", id, role); return; } else { enum rsc_role_e r = text2role(role); switch(r) { case RSC_ROLE_UNKNOWN: case RSC_ROLE_STARTED: case RSC_ROLE_UNPROMOTED: /* Applies to all */ location->role_filter = RSC_ROLE_UNKNOWN; break; default: location->role_filter = r; break; } } } } static void unpack_simple_location(xmlNode *xml_obj, pe_working_set_t *data_set) { const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); if (value) { pe_resource_t *rsc; rsc = pcmk__find_constraint_resource(data_set->resources, value); unpack_rsc_location(xml_obj, rsc, NULL, NULL, data_set, NULL); } value = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE_PATTERN); if (value) { regex_t *r_patt = calloc(1, sizeof(regex_t)); bool invert = false; GList *rIter = NULL; if (value[0] == '!') { value++; invert = true; } - if (regcomp(r_patt, value, REG_EXTENDED)) { + if (regcomp(r_patt, value, REG_EXTENDED) != 0) { pcmk__config_err("Ignoring constraint '%s' because " XML_LOC_ATTR_SOURCE_PATTERN " has invalid value '%s'", id, value); - regfree(r_patt); free(r_patt); return; } for (rIter = data_set->resources; rIter; rIter = rIter->next) { pe_resource_t *r = rIter->data; int nregs = 0; regmatch_t *pmatch = NULL; int status; if(r_patt->re_nsub > 0) { nregs = r_patt->re_nsub + 1; } else { nregs = 1; } pmatch = calloc(nregs, sizeof(regmatch_t)); status = regexec(r_patt, r->id, nregs, pmatch, 0); if (!invert && (status == 0)) { pe_re_match_data_t re_match_data = { .string = r->id, .nregs = nregs, .pmatch = pmatch }; crm_debug("'%s' matched '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, data_set, &re_match_data); } else if (invert && (status != 0)) { crm_debug("'%s' is an inverted match of '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, data_set, NULL); } else { crm_trace("'%s' does not match '%s' for %s", r->id, value, id); } free(pmatch); } regfree(r_patt); free(r_patt); } } // \return Standard Pacemaker return code static int unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pe_working_set_t *data_set) { const char *id = NULL; const char *rsc_id = NULL; const char *state = NULL; pe_resource_t *rsc = NULL; pe_tag_t *tag = NULL; xmlNode *rsc_set = NULL; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = ID(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID, crm_element_name(xml_obj)); 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, data_set); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded rsc_location"); return pcmk_rc_ok; } rsc_id = crm_element_value(xml_obj, XML_LOC_ATTR_SOURCE); if (rsc_id == NULL) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(data_set, 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 is referenced return pcmk_rc_ok; } state = crm_element_value(xml_obj, XML_RULE_ATTR_ROLE); *expanded_xml = copy_xml(xml_obj); // Convert template/tag reference in "rsc" into resource_set under constraint if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_LOC_ATTR_SOURCE, false, data_set)) { free_xml(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (rsc_set != NULL) { if (state != NULL) { // Move "rsc-role" into converted resource_set as "role" attribute crm_xml_add(rsc_set, "role", state); xml_remove_prop(*expanded_xml, XML_RULE_ATTR_ROLE); } crm_log_xml_trace(*expanded_xml, "Expanded rsc_location"); } else { // No sets free_xml(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int unpack_location_set(xmlNode *location, xmlNode *set, pe_working_set_t *data_set) { xmlNode *xml_rsc = NULL; pe_resource_t *resource = NULL; const char *set_id; const char *role; const char *local_score; CRM_CHECK(set != NULL, return EINVAL); set_id = ID(set); if (set_id == NULL) { pcmk__config_err("Ignoring " XML_CONS_TAG_RSC_SET " without " XML_ATTR_ID " in constraint '%s'", pcmk__s(ID(location), "(missing ID)")); return pcmk_rc_unpack_error; } role = crm_element_value(set, "role"); local_score = crm_element_value(set, XML_RULE_ATTR_SCORE); for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF); xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) { resource = pcmk__find_constraint_resource(data_set->resources, ID(xml_rsc)); if (resource == NULL) { pcmk__config_err("%s: No resource found for %s", set_id, ID(xml_rsc)); return pcmk_rc_unpack_error; } unpack_rsc_location(location, resource, role, local_score, data_set, NULL); } return pcmk_rc_ok; } void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set) { xmlNode *set = NULL; bool any_sets = false; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; if (unpack_location_tags(xml_obj, &expanded_xml, data_set) != pcmk_rc_ok) { return; } if (expanded_xml) { orig_xml = xml_obj; xml_obj = expanded_xml; } for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL; set = crm_next_same_xml(set)) { any_sets = true; set = expand_idref(set, data_set->input); if ((set == NULL) // Configuration error, message already logged || (unpack_location_set(xml_obj, set, data_set) != pcmk_rc_ok)) { if (expanded_xml) { free_xml(expanded_xml); } return; } } if (expanded_xml) { free_xml(expanded_xml); xml_obj = orig_xml; } if (!any_sets) { unpack_simple_location(xml_obj, data_set); } } /*! * \internal * \brief Add a new location constraint to a cluster working set * * \param[in] id XML ID of location constraint * \param[in] rsc Resource in location constraint * \param[in] node_weight Constraint score * \param[in] discover_mode Resource discovery option for constraint * \param[in] node Node in location constraint (or NULL if rule-based) * \param[in] data_set Cluster working set to add constraint to * * \return Newly allocated location constraint * \note The result will be added to \p data_set and should not be freed * separately. */ pe__location_t * pcmk__new_location(const char *id, pe_resource_t *rsc, int node_weight, const char *discover_mode, pe_node_t *node, pe_working_set_t *data_set) { pe__location_t *new_con = NULL; if (id == NULL) { pe_err("Invalid constraint: no ID specified"); return NULL; } else if (rsc == NULL) { pe_err("Invalid constraint %s: no resource specified", id); return NULL; } else if (node == NULL) { CRM_CHECK(node_weight == 0, return NULL); } new_con = calloc(1, sizeof(pe__location_t)); if (new_con != NULL) { new_con->id = strdup(id); new_con->rsc_lh = rsc; new_con->node_list_rh = NULL; new_con->role_filter = RSC_ROLE_UNKNOWN; if (pcmk__str_eq(discover_mode, "always", pcmk__str_null_matches|pcmk__str_casei)) { new_con->discover_mode = pe_discover_always; } else if (pcmk__str_eq(discover_mode, "never", pcmk__str_casei)) { new_con->discover_mode = pe_discover_never; } else if (pcmk__str_eq(discover_mode, "exclusive", pcmk__str_casei)) { new_con->discover_mode = pe_discover_exclusive; rsc->exclusive_discover = TRUE; } else { pe_err("Invalid " XML_LOCATION_ATTR_DISCOVERY " value %s " "in location constraint", discover_mode); } if (node != NULL) { pe_node_t *copy = pe__copy_node(node); copy->weight = node_weight; new_con->node_list_rh = g_list_prepend(NULL, copy); } data_set->placement_constraints = g_list_prepend(data_set->placement_constraints, new_con); rsc->rsc_location = g_list_prepend(rsc->rsc_location, new_con); } return new_con; } /*! * \internal * \brief Apply all location constraints * * \param[in] data_set Cluster working set */ void pcmk__apply_locations(pe_working_set_t *data_set) { for (GList *iter = data_set->placement_constraints; iter != NULL; iter = iter->next) { pe__location_t *location = iter->data; location->rsc_lh->cmds->apply_location(location->rsc_lh, location); } } /*! * \internal * \brief Apply a location constraint to a resource's allowed node scores * * \param[in,out] rsc Resource to apply constraint to * \param[in,out] location Location constraint to apply * * \note This does not consider the resource's children, so the resource's * apply_location() method should be used instead in most cases. */ void pcmk__apply_location(pe_resource_t *rsc, pe__location_t *location) { bool need_role = false; CRM_CHECK((rsc != NULL) && (location != NULL), return); // If a role was specified, ensure constraint is applicable need_role = (location->role_filter > RSC_ROLE_UNKNOWN); if (need_role && (location->role_filter != rsc->next_role)) { pe_rsc_trace(rsc, "Not applying %s to %s because role will be %s not %s", location->id, rsc->id, role2text(rsc->next_role), role2text(location->role_filter)); return; } if (location->node_list_rh == NULL) { pe_rsc_trace(rsc, "Not applying %s to %s because no nodes match", location->id, rsc->id); return; } pe_rsc_trace(rsc, "Applying %s%s%s to %s", location->id, (need_role? " for role " : ""), (need_role? role2text(location->role_filter) : ""), rsc->id); for (GList *gIter = location->node_list_rh; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; pe_node_t *weighted_node = NULL; weighted_node = (pe_node_t *) pe_hash_table_lookup(rsc->allowed_nodes, node->details->id); if (weighted_node == NULL) { pe_rsc_trace(rsc, "* = %d on %s", node->weight, pe__node_name(node)); weighted_node = pe__copy_node(node); g_hash_table_insert(rsc->allowed_nodes, (gpointer) weighted_node->details->id, weighted_node); } else { pe_rsc_trace(rsc, "* + %d on %s", node->weight, pe__node_name(node)); weighted_node->weight = pcmk__add_scores(weighted_node->weight, node->weight); } if (weighted_node->rsc_discover_mode < location->discover_mode) { if (location->discover_mode == pe_discover_exclusive) { rsc->exclusive_discover = TRUE; } /* exclusive > never > always... always is default */ weighted_node->rsc_discover_mode = location->discover_mode; } } }