diff --git a/cts/schemas/test-3/ref/multiple-location-rules.ref-2 b/cts/schemas/test-3/ref/multiple-location-rules.ref-2
index b9e0233243..7b72e5629e 100644
--- a/cts/schemas/test-3/ref/multiple-location-rules.ref-2
+++ b/cts/schemas/test-3/ref/multiple-location-rules.ref-2
@@ -1,121 +1,155 @@
-
+
+
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/cts/schemas/test-3/ref/multiple-location-rules.ref-99 b/cts/schemas/test-3/ref/multiple-location-rules.ref-99
index e99b327917..28f9636a6e 100644
--- a/cts/schemas/test-3/ref/multiple-location-rules.ref-99
+++ b/cts/schemas/test-3/ref/multiple-location-rules.ref-99
@@ -1,113 +1,131 @@
-
+
+
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c
index 6aeb344375..f2d047165f 100644
--- a/lib/pacemaker/pcmk_sched_location.c
+++ b/lib/pacemaker/pcmk_sched_location.c
@@ -1,726 +1,728 @@
/*
* 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"
static int
get_node_score(const char *rule, const char *score, bool raw,
pcmk_node_t *node, pcmk_resource_t *rsc)
{
int score_f = 0;
if (score == NULL) {
pcmk__config_warn("Rule %s: no score specified (assuming 0)", rule);
} else if (raw) {
score_f = char2score(score);
} else {
const char *target = NULL;
const char *attr_score = NULL;
target = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
attr_score = pcmk__node_attr(node, score, target,
pcmk__rsc_node_current);
if (attr_score == NULL) {
crm_debug("Rule %s: %s did not have a value for %s",
rule, pcmk__node_name(node), score);
score_f = -PCMK_SCORE_INFINITY;
} else {
crm_debug("Rule %s: %s had value %s for %s",
rule, pcmk__node_name(node), attr_score, score);
score_f = char2score(attr_score);
}
}
return score_f;
}
/*!
* \internal
* \brief Parse a role configuration for a location constraint
*
* \param[in] role_spec Role specification
* \param[out] role Where to store parsed role
*
* \return true if role specification is valid, otherwise false
*/
static bool
parse_location_role(const char *role_spec, enum rsc_role_e *role)
{
if (role_spec == NULL) {
*role = pcmk_role_unknown;
return true;
}
*role = pcmk_parse_role(role_spec);
switch (*role) {
case pcmk_role_unknown:
return false;
case pcmk_role_started:
case pcmk_role_unpromoted:
/* Any promotable clone instance cannot be promoted without being in
* the unpromoted role first. Therefore, any constraint for the
* started or unpromoted role applies to every role.
*/
*role = pcmk_role_unknown;
break;
default:
break;
}
return true;
}
/*!
* \internal
* \brief Generate a location constraint from a rule
*
* \param[in,out] rsc Resource that constraint is for
* \param[in] rule_xml Rule XML (sub-element of location constraint)
* \param[in] discovery Value of \c PCMK_XA_RESOURCE_DISCOVERY for
* constraint
* \param[out] next_change Where to set when rule evaluation will change
* \param[in,out] rule_input Values used to evaluate rule criteria
* (node-specific values will be overwritten by
* this function)
*
* \return true if rule is valid, otherwise false
*/
static bool
generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml,
const char *discovery, crm_time_t *next_change,
pcmk_rule_input_t *rule_input)
{
const char *rule_id = NULL;
const char *score = NULL;
const char *boolean = NULL;
const char *role_spec = NULL;
GList *iter = NULL;
bool raw_score = true;
bool score_allocated = false;
pcmk__location_t *location_rule = NULL;
enum rsc_role_e role = pcmk_role_unknown;
enum pcmk__combine combine = pcmk__combine_unknown;
rule_xml = pcmk__xe_resolve_idref(rule_xml, rsc->priv->scheduler->input);
if (rule_xml == NULL) {
return false; // Error already logged
}
rule_id = crm_element_value(rule_xml, PCMK_XA_ID);
if (rule_id == NULL) {
pcmk__config_err("Ignoring " PCMK_XE_RULE " without " PCMK_XA_ID
" in location constraint");
return false;
}
boolean = crm_element_value(rule_xml, PCMK_XA_BOOLEAN_OP);
role_spec = crm_element_value(rule_xml, PCMK_XA_ROLE);
if (parse_location_role(role_spec, &role)) {
crm_trace("Setting rule %s role filter to %s", rule_id, role_spec);
} else {
pcmk__config_err("Ignoring rule %s: Invalid " PCMK_XA_ROLE " '%s'",
rule_id, role_spec);
return false;
}
crm_trace("Processing location constraint rule %s", rule_id);
score = crm_element_value(rule_xml, PCMK_XA_SCORE);
if (score == NULL) {
score = crm_element_value(rule_xml, PCMK_XA_SCORE_ATTRIBUTE);
if (score != NULL) {
raw_score = false;
}
}
combine = pcmk__parse_combine(boolean);
switch (combine) {
case pcmk__combine_and:
case pcmk__combine_or:
break;
default: // Not possible with schema validation enabled
pcmk__config_err("Ignoring location constraint rule %s "
"because '%s' is not a valid " PCMK_XA_BOOLEAN_OP,
rule_id, boolean);
return false;
}
location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL);
CRM_CHECK(location_rule != NULL, return NULL);
location_rule->role_filter = role;
if ((rule_input->rsc_id != NULL) && (rule_input->rsc_id_nmatches > 0)
&& !raw_score) {
char *result = pcmk__replace_submatches(score, rule_input->rsc_id,
rule_input->rsc_id_submatches,
rule_input->rsc_id_nmatches);
if (result != NULL) {
score = result;
score_allocated = true;
}
}
for (iter = rsc->priv->scheduler->nodes;
iter != NULL; iter = iter->next) {
pcmk_node_t *node = iter->data;
rule_input->node_attrs = node->priv->attrs;
rule_input->rsc_params = pe_rsc_params(rsc, node,
rsc->priv->scheduler);
if (pcmk_evaluate_rule(rule_xml, rule_input,
next_change) == pcmk_rc_ok) {
pcmk_node_t *local = pe__copy_node(node);
location_rule->nodes = g_list_prepend(location_rule->nodes, local);
local->assign->score = get_node_score(rule_id, score, raw_score,
node, rsc);
crm_trace("%s has score %s after %s", pcmk__node_name(node),
pcmk_readable_score(local->assign->score), rule_id);
}
}
if (score_allocated) {
free((char *)score);
}
if (location_rule->nodes == NULL) {
crm_trace("No matching nodes for location constraint rule %s", rule_id);
} else {
crm_trace("Location constraint rule %s matched %d nodes",
rule_id, g_list_length(location_rule->nodes));
}
return true;
}
static void
unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc,
const char *role_spec, const char *score,
char *rsc_id_match, int rsc_id_nmatches,
regmatch_t *rsc_id_submatches)
{
const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
const char *node = crm_element_value(xml_obj, PCMK_XA_NODE);
const char *discovery = crm_element_value(xml_obj,
PCMK_XA_RESOURCE_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, PCMK_XA_SCORE);
}
if ((node != NULL) && (score != NULL)) {
int score_i = char2score(score);
pcmk_node_t *match = pcmk_find_node(rsc->priv->scheduler, node);
enum rsc_role_e role = pcmk_role_unknown;
pcmk__location_t *location = NULL;
if (match == NULL) {
crm_info("Ignoring location constraint %s "
"because '%s' is not a known node",
pcmk__s(id, "without ID"), node);
return;
}
if (role_spec == NULL) {
role_spec = crm_element_value(xml_obj, PCMK_XA_ROLE);
}
if (parse_location_role(role_spec, &role)) {
crm_trace("Setting location constraint %s role filter: %s",
id, role_spec);
} else { // Not possible with schema validation enabled
pcmk__config_err("Ignoring location constraint %s "
"because '%s' is not a valid " PCMK_XA_ROLE,
id, role_spec);
return;
}
location = pcmk__new_location(id, rsc, score_i, discovery, match);
if (location == NULL) {
return; // Error already logged
}
location->role_filter = role;
} else {
bool empty = true;
crm_time_t *next_change = crm_time_new_undefined();
pcmk_rule_input_t rule_input = {
.now = rsc->priv->scheduler->priv->now,
.rsc_meta = rsc->priv->meta,
.rsc_id = rsc_id_match,
.rsc_id_submatches = rsc_id_submatches,
.rsc_id_nmatches = rsc_id_nmatches,
};
/* This loop is logically parallel to pcmk__evaluate_rules(), except
* instead of checking whether any rule is active, we set up location
* constraints for each active rule.
*
* @COMPAT When we can break backward compatibility, limit location
* constraints to a single rule, for consistency with other contexts.
* Since a rule may contain other rules, this does not prohibit any
* existing use cases.
+ *
+ * The schema already limits location constraints to a single rule.
*/
for (xmlNode *rule_xml = pcmk__xe_first_child(xml_obj, PCMK_XE_RULE,
NULL, NULL);
rule_xml != NULL; rule_xml = pcmk__xe_next_same(rule_xml)) {
if (generate_location_rule(rsc, rule_xml, discovery, next_change,
&rule_input)) {
if (empty) {
empty = false;
continue;
}
pcmk__warn_once(pcmk__wo_location_rules,
"Support for multiple " PCMK_XE_RULE
" elements in a location constraint is "
"deprecated and will be removed in a future "
"release (use a single new rule combining the "
"previous rules with " PCMK_XA_BOOLEAN_OP
" set to '" PCMK_VALUE_OR "' instead)");
}
}
if (empty) {
pcmk__config_err("Ignoring constraint '%s' because it contains "
"no valid 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, rsc->priv->scheduler,
"location rule evaluation");
}
crm_time_free(next_change);
}
}
static void
unpack_simple_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
const char *value = crm_element_value(xml_obj, PCMK_XA_RSC);
if (value) {
pcmk_resource_t *rsc;
rsc = pcmk__find_constraint_resource(scheduler->priv->resources, value);
unpack_rsc_location(xml_obj, rsc, NULL, NULL, NULL, 0, NULL);
}
value = crm_element_value(xml_obj, PCMK_XA_RSC_PATTERN);
if (value) {
regex_t regex;
bool invert = false;
if (value[0] == '!') {
value++;
invert = true;
}
if (regcomp(®ex, value, REG_EXTENDED) != 0) {
pcmk__config_err("Ignoring constraint '%s' because "
PCMK_XA_RSC_PATTERN
" has invalid value '%s'", id, value);
return;
}
for (GList *iter = scheduler->priv->resources;
iter != NULL; iter = iter->next) {
pcmk_resource_t *r = iter->data;
int nregs = 0;
regmatch_t *pmatch = NULL;
int status;
if (regex.re_nsub > 0) {
nregs = regex.re_nsub + 1;
} else {
nregs = 1;
}
pmatch = pcmk__assert_alloc(nregs, sizeof(regmatch_t));
status = regexec(®ex, r->id, nregs, pmatch, 0);
if (!invert && (status == 0)) {
crm_debug("'%s' matched '%s' for %s", r->id, value, id);
unpack_rsc_location(xml_obj, r, NULL, NULL, r->id, nregs,
pmatch);
} 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, NULL, 0, NULL);
} else {
crm_trace("'%s' does not match '%s' for %s", r->id, value, id);
}
free(pmatch);
}
regfree(®ex);
}
}
// \return Standard Pacemaker return code
static int
unpack_location_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 " PCMK_XE_RSC_LOCATION);
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 is referenced
return pcmk_rc_ok;
}
state = crm_element_value(xml_obj, PCMK_XA_ROLE);
*expanded_xml = pcmk__xml_copy(NULL, xml_obj);
/* Convert any template or tag reference into constraint
* 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
* PCMK_XA_ROLE attribute
*/
crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_ROLE);
}
crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION);
} else {
// No sets
pcmk__xml_free(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
// \return Standard Pacemaker return code
static int
unpack_location_set(xmlNode *location, xmlNode *set,
pcmk_scheduler_t *scheduler)
{
xmlNode *xml_rsc = NULL;
pcmk_resource_t *resource = NULL;
const char *set_id;
const char *role;
const char *local_score;
CRM_CHECK(set != 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 " in constraint '%s'",
pcmk__s(pcmk__xe_id(location), "(missing ID)"));
return pcmk_rc_unpack_error;
}
role = crm_element_value(set, PCMK_XA_ROLE);
local_score = crm_element_value(set, PCMK_XA_SCORE);
for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
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;
}
unpack_rsc_location(location, resource, role, local_score, NULL, 0,
NULL);
}
return pcmk_rc_ok;
}
void
pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
xmlNode *set = NULL;
bool any_sets = false;
xmlNode *orig_xml = NULL;
xmlNode *expanded_xml = NULL;
if (unpack_location_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) {
return;
}
if (expanded_xml) {
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)) {
any_sets = true;
set = pcmk__xe_resolve_idref(set, scheduler->input);
if ((set == NULL) // Configuration error, message already logged
|| (unpack_location_set(xml_obj, set, scheduler) != pcmk_rc_ok)) {
if (expanded_xml) {
pcmk__xml_free(expanded_xml);
}
return;
}
}
if (expanded_xml) {
pcmk__xml_free(expanded_xml);
xml_obj = orig_xml;
}
if (!any_sets) {
unpack_simple_location(xml_obj, scheduler);
}
}
/*!
* \internal
* \brief Add a new location constraint to scheduler data
*
* \param[in] id XML ID of location constraint
* \param[in,out] rsc Resource in location constraint
* \param[in] node_score Constraint score
* \param[in] probe_mode When resource should be probed on node
* \param[in] node Node in constraint (or NULL if rule-based)
*
* \return Newly allocated location constraint on success, otherwise NULL
* \note The result will be added to the cluster (via \p rsc) and should not be
* freed separately.
*/
pcmk__location_t *
pcmk__new_location(const char *id, pcmk_resource_t *rsc,
int node_score, const char *probe_mode, pcmk_node_t *node)
{
pcmk__location_t *new_con = NULL;
CRM_CHECK((node != NULL) || (node_score == 0), return NULL);
if (id == NULL) {
pcmk__config_err("Invalid constraint: no ID specified");
return NULL;
}
if (rsc == NULL) {
pcmk__config_err("Invalid constraint %s: no resource specified", id);
return NULL;
}
new_con = pcmk__assert_alloc(1, sizeof(pcmk__location_t));
new_con->id = pcmk__str_copy(id);
new_con->rsc = rsc;
new_con->nodes = NULL;
new_con->role_filter = pcmk_role_unknown;
if (pcmk__str_eq(probe_mode, PCMK_VALUE_ALWAYS,
pcmk__str_null_matches|pcmk__str_casei)) {
new_con->probe_mode = pcmk__probe_always;
} else if (pcmk__str_eq(probe_mode, PCMK_VALUE_NEVER, pcmk__str_casei)) {
new_con->probe_mode = pcmk__probe_never;
} else if (pcmk__str_eq(probe_mode, PCMK_VALUE_EXCLUSIVE,
pcmk__str_casei)) {
new_con->probe_mode = pcmk__probe_exclusive;
pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes);
} else {
pcmk__config_err("Invalid " PCMK_XA_RESOURCE_DISCOVERY " value %s "
"in location constraint", probe_mode);
}
if (node != NULL) {
pcmk_node_t *copy = pe__copy_node(node);
copy->assign->score = node_score;
new_con->nodes = g_list_prepend(NULL, copy);
}
rsc->priv->scheduler->priv->location_constraints =
g_list_prepend(rsc->priv->scheduler->priv->location_constraints,
new_con);
rsc->priv->location_constraints =
g_list_prepend(rsc->priv->location_constraints, new_con);
return new_con;
}
/*!
* \internal
* \brief Apply all location constraints
*
* \param[in,out] scheduler Scheduler data
*/
void
pcmk__apply_locations(pcmk_scheduler_t *scheduler)
{
for (GList *iter = scheduler->priv->location_constraints;
iter != NULL; iter = iter->next) {
pcmk__location_t *location = iter->data;
location->rsc->priv->cmds->apply_location(location->rsc, 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(pcmk_resource_t *rsc, pcmk__location_t *location)
{
bool need_role = false;
CRM_ASSERT((rsc != NULL) && (location != NULL));
// If a role was specified, ensure constraint is applicable
need_role = (location->role_filter > pcmk_role_unknown);
if (need_role && (location->role_filter != rsc->priv->next_role)) {
pcmk__rsc_trace(rsc,
"Not applying %s to %s because role will be %s not %s",
location->id, rsc->id,
pcmk_role_text(rsc->priv->next_role),
pcmk_role_text(location->role_filter));
return;
}
if (location->nodes == NULL) {
pcmk__rsc_trace(rsc, "Not applying %s to %s because no nodes match",
location->id, rsc->id);
return;
}
for (GList *iter = location->nodes; iter != NULL; iter = iter->next) {
pcmk_node_t *node = iter->data;
pcmk_node_t *allowed_node = NULL;
allowed_node = g_hash_table_lookup(rsc->priv->allowed_nodes,
node->priv->id);
pcmk__rsc_trace(rsc, "Applying %s%s%s to %s score on %s: %c %s",
location->id,
(need_role? " for role " : ""),
(need_role? pcmk_role_text(location->role_filter) : ""),
rsc->id, pcmk__node_name(node),
((allowed_node == NULL)? '=' : '+'),
pcmk_readable_score(node->assign->score));
if (allowed_node == NULL) {
allowed_node = pe__copy_node(node);
g_hash_table_insert(rsc->priv->allowed_nodes,
(gpointer) allowed_node->priv->id,
allowed_node);
} else {
allowed_node->assign->score =
pcmk__add_scores(allowed_node->assign->score,
node->assign->score);
}
if (allowed_node->assign->probe_mode < location->probe_mode) {
if (location->probe_mode == pcmk__probe_exclusive) {
pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes);
}
/* exclusive > never > always... always is default */
allowed_node->assign->probe_mode = location->probe_mode;
}
}
}
diff --git a/xml/constraints-4.0.rng b/xml/constraints-4.0.rng
index cab0d089e2..d43557f09f 100644
--- a/xml/constraints-4.0.rng
+++ b/xml/constraints-4.0.rng
@@ -1,287 +1,281 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
group
listed
stop
demote
fence
freeze
always
never
exclusive
start
promote
demote
stop
Stopped
Started
Promoted
Unpromoted
Master
Slave
Optional
Mandatory
Serialize
diff --git a/xml/upgrade-3.10-2.xsl b/xml/upgrade-3.10-2.xsl
index 54cec91a32..5217ff914e 100644
--- a/xml/upgrade-3.10-2.xsl
+++ b/xml/upgrade-3.10-2.xsl
@@ -1,98 +1,168 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+