From c16fa5f8b9ca80305f6f6c8b7372802b4d7300cf Mon Sep 17 00:00:00 2001 From: Klaus Wenninger Date: Tue, 18 Aug 2026 15:54:02 +0200 Subject: [PATCH 1/4] Test: scheduler: promoted state with promoted state with attribute Add testcase. Previous fix attribute based colocation didn't adhere the attribute with promoted state with promoted state. --- cts/cts-scheduler.in | 1 + ...motable-colocation-with-node-attribute.dot | 28 +++ ...motable-colocation-with-node-attribute.exp | 175 ++++++++++++++++++ ...able-colocation-with-node-attribute.scores | 81 ++++++++ ...ble-colocation-with-node-attribute.summary | 45 +++++ ...motable-colocation-with-node-attribute.xml | 155 ++++++++++++++++ 6 files changed, 485 insertions(+) create mode 100644 cts/scheduler/dot/promotable-colocation-with-node-attribute.dot create mode 100644 cts/scheduler/exp/promotable-colocation-with-node-attribute.exp create mode 100644 cts/scheduler/scores/promotable-colocation-with-node-attribute.scores create mode 100644 cts/scheduler/summary/promotable-colocation-with-node-attribute.summary create mode 100644 cts/scheduler/xml/promotable-colocation-with-node-attribute.xml diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in index 066c39b2375..8b5c7a65fb7 100644 --- a/cts/cts-scheduler.in +++ b/cts/cts-scheduler.in @@ -639,6 +639,7 @@ TESTS = [ [ "no_quorum_demote", "Promotable demotion and primitive stop with no-quorum-policy=\"demote\"" ], [ "no-promote-on-unrunnable-guest", "Don't select bundle instance for promotion when container can't run" ], [ "leftover-pending-monitor", "Prevent a leftover pending monitor from causing unexpected stop of other instances" ], + [ "promotable-colocation-with-node-attribute", "Promote dependent clone on nodes belonging to a site that has a primary clone promoted" ], ], [ [ "history-1", "Correctly parse stateful-1 resource state" ], diff --git a/cts/scheduler/dot/promotable-colocation-with-node-attribute.dot b/cts/scheduler/dot/promotable-colocation-with-node-attribute.dot new file mode 100644 index 00000000000..89d066f893c --- /dev/null +++ b/cts/scheduler/dot/promotable-colocation-with-node-attribute.dot @@ -0,0 +1,28 @@ + digraph "g" { +"dependent-clone_demote_0" -> "dependent-clone_demoted_0" [ style = bold] +"dependent-clone_demote_0" -> "dependent-rsc_demote_0 node3" [ style = bold] +"dependent-clone_demote_0" [ style=bold color="green" fontcolor="orange"] +"dependent-clone_demoted_0" -> "dependent-clone_promote_0" [ style = bold] +"dependent-clone_demoted_0" [ style=bold color="green" fontcolor="orange"] +"dependent-clone_promote_0" -> "dependent-rsc_promote_0 node1" [ style = bold] +"dependent-clone_promote_0" -> "dependent-rsc_promote_0 node2" [ style = bold] +"dependent-clone_promote_0" [ style=bold color="green" fontcolor="orange"] +"dependent-clone_promoted_0" [ style=bold color="green" fontcolor="orange"] +"dependent-rsc_demote_0 node3" -> "dependent-clone_demoted_0" [ style = bold] +"dependent-rsc_demote_0 node3" -> "dependent-rsc_monitor_11000 node3" [ style = bold] +"dependent-rsc_demote_0 node3" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_monitor_10000 node1" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_monitor_10000 node2" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_monitor_11000 node3" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_monitor_11000 node4" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_promote_0 node1" -> "dependent-clone_promoted_0" [ style = bold] +"dependent-rsc_promote_0 node1" -> "dependent-rsc_monitor_10000 node1" [ style = bold] +"dependent-rsc_promote_0 node1" [ style=bold color="green" fontcolor="black"] +"dependent-rsc_promote_0 node2" -> "dependent-clone_promoted_0" [ style = bold] +"dependent-rsc_promote_0 node2" -> "dependent-rsc_monitor_10000 node2" [ style = bold] +"dependent-rsc_promote_0 node2" [ style=bold color="green" fontcolor="black"] +"primary-rsc_monitor_10000 node1" [ style=bold color="green" fontcolor="black"] +"primary-rsc_monitor_11000 node2" [ style=bold color="green" fontcolor="black"] +"primary-rsc_monitor_11000 node3" [ style=bold color="green" fontcolor="black"] +"primary-rsc_monitor_11000 node4" [ style=bold color="green" fontcolor="black"] +} diff --git a/cts/scheduler/exp/promotable-colocation-with-node-attribute.exp b/cts/scheduler/exp/promotable-colocation-with-node-attribute.exp new file mode 100644 index 00000000000..d3de3a8844b --- /dev/null +++ b/cts/scheduler/exp/promotable-colocation-with-node-attribute.exp @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/scores/promotable-colocation-with-node-attribute.scores b/cts/scheduler/scores/promotable-colocation-with-node-attribute.scores new file mode 100644 index 00000000000..023ee779107 --- /dev/null +++ b/cts/scheduler/scores/promotable-colocation-with-node-attribute.scores @@ -0,0 +1,81 @@ + +dependent-rsc:0 promotion score on node1: INFINITY +dependent-rsc:1 promotion score on node2: INFINITY +dependent-rsc:2 promotion score on node3: -INFINITY +dependent-rsc:3 promotion score on node4: -INFINITY +pcmk__clone_assign: dependent-clone allocation score on node1: 0 +pcmk__clone_assign: dependent-clone allocation score on node2: 0 +pcmk__clone_assign: dependent-clone allocation score on node3: 0 +pcmk__clone_assign: dependent-clone allocation score on node4: 0 +pcmk__clone_assign: dependent-rsc:0 allocation score on node1: 6 +pcmk__clone_assign: dependent-rsc:0 allocation score on node2: 0 +pcmk__clone_assign: dependent-rsc:0 allocation score on node3: 0 +pcmk__clone_assign: dependent-rsc:0 allocation score on node4: 0 +pcmk__clone_assign: dependent-rsc:1 allocation score on node1: 0 +pcmk__clone_assign: dependent-rsc:1 allocation score on node2: 6 +pcmk__clone_assign: dependent-rsc:1 allocation score on node3: 0 +pcmk__clone_assign: dependent-rsc:1 allocation score on node4: 0 +pcmk__clone_assign: dependent-rsc:2 allocation score on node1: 0 +pcmk__clone_assign: dependent-rsc:2 allocation score on node2: 0 +pcmk__clone_assign: dependent-rsc:2 allocation score on node3: 11 +pcmk__clone_assign: dependent-rsc:2 allocation score on node4: 0 +pcmk__clone_assign: dependent-rsc:3 allocation score on node1: 0 +pcmk__clone_assign: dependent-rsc:3 allocation score on node2: 0 +pcmk__clone_assign: dependent-rsc:3 allocation score on node3: 0 +pcmk__clone_assign: dependent-rsc:3 allocation score on node4: 6 +pcmk__clone_assign: primary-clone allocation score on node1: 0 +pcmk__clone_assign: primary-clone allocation score on node2: 0 +pcmk__clone_assign: primary-clone allocation score on node3: 0 +pcmk__clone_assign: primary-clone allocation score on node4: 0 +pcmk__clone_assign: primary-rsc:0 allocation score on node1: 11 +pcmk__clone_assign: primary-rsc:0 allocation score on node2: 0 +pcmk__clone_assign: primary-rsc:0 allocation score on node3: 0 +pcmk__clone_assign: primary-rsc:0 allocation score on node4: 0 +pcmk__clone_assign: primary-rsc:1 allocation score on node1: 0 +pcmk__clone_assign: primary-rsc:1 allocation score on node2: 6 +pcmk__clone_assign: primary-rsc:1 allocation score on node3: 0 +pcmk__clone_assign: primary-rsc:1 allocation score on node4: 0 +pcmk__clone_assign: primary-rsc:2 allocation score on node1: 0 +pcmk__clone_assign: primary-rsc:2 allocation score on node2: 0 +pcmk__clone_assign: primary-rsc:2 allocation score on node3: 6 +pcmk__clone_assign: primary-rsc:2 allocation score on node4: 0 +pcmk__clone_assign: primary-rsc:3 allocation score on node1: 0 +pcmk__clone_assign: primary-rsc:3 allocation score on node2: 0 +pcmk__clone_assign: primary-rsc:3 allocation score on node3: 0 +pcmk__clone_assign: primary-rsc:3 allocation score on node4: 6 +pcmk__primitive_assign: dependent-rsc:0 allocation score on node1: 6 +pcmk__primitive_assign: dependent-rsc:0 allocation score on node2: 0 +pcmk__primitive_assign: dependent-rsc:0 allocation score on node3: -INFINITY +pcmk__primitive_assign: dependent-rsc:0 allocation score on node4: 0 +pcmk__primitive_assign: dependent-rsc:1 allocation score on node1: -INFINITY +pcmk__primitive_assign: dependent-rsc:1 allocation score on node2: 6 +pcmk__primitive_assign: dependent-rsc:1 allocation score on node3: -INFINITY +pcmk__primitive_assign: dependent-rsc:1 allocation score on node4: 0 +pcmk__primitive_assign: dependent-rsc:2 allocation score on node1: 0 +pcmk__primitive_assign: dependent-rsc:2 allocation score on node2: 0 +pcmk__primitive_assign: dependent-rsc:2 allocation score on node3: 11 +pcmk__primitive_assign: dependent-rsc:2 allocation score on node4: 0 +pcmk__primitive_assign: dependent-rsc:3 allocation score on node1: -INFINITY +pcmk__primitive_assign: dependent-rsc:3 allocation score on node2: -INFINITY +pcmk__primitive_assign: dependent-rsc:3 allocation score on node3: -INFINITY +pcmk__primitive_assign: dependent-rsc:3 allocation score on node4: 6 +pcmk__primitive_assign: primary-rsc:0 allocation score on node1: 11 +pcmk__primitive_assign: primary-rsc:0 allocation score on node2: 0 +pcmk__primitive_assign: primary-rsc:0 allocation score on node3: 0 +pcmk__primitive_assign: primary-rsc:0 allocation score on node4: 0 +pcmk__primitive_assign: primary-rsc:1 allocation score on node1: -INFINITY +pcmk__primitive_assign: primary-rsc:1 allocation score on node2: 6 +pcmk__primitive_assign: primary-rsc:1 allocation score on node3: 0 +pcmk__primitive_assign: primary-rsc:1 allocation score on node4: 0 +pcmk__primitive_assign: primary-rsc:2 allocation score on node1: -INFINITY +pcmk__primitive_assign: primary-rsc:2 allocation score on node2: -INFINITY +pcmk__primitive_assign: primary-rsc:2 allocation score on node3: 6 +pcmk__primitive_assign: primary-rsc:2 allocation score on node4: 0 +pcmk__primitive_assign: primary-rsc:3 allocation score on node1: -INFINITY +pcmk__primitive_assign: primary-rsc:3 allocation score on node2: -INFINITY +pcmk__primitive_assign: primary-rsc:3 allocation score on node3: -INFINITY +pcmk__primitive_assign: primary-rsc:3 allocation score on node4: 6 +primary-rsc:0 promotion score on node1: 10 +primary-rsc:1 promotion score on node2: 5 +primary-rsc:2 promotion score on node3: 5 +primary-rsc:3 promotion score on node4: 5 diff --git a/cts/scheduler/summary/promotable-colocation-with-node-attribute.summary b/cts/scheduler/summary/promotable-colocation-with-node-attribute.summary new file mode 100644 index 00000000000..30e81c8f371 --- /dev/null +++ b/cts/scheduler/summary/promotable-colocation-with-node-attribute.summary @@ -0,0 +1,45 @@ +Current cluster status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: primary-clone [primary-rsc] (promotable): + * Promoted: [ node1 ] + * Unpromoted: [ node2 node3 node4 ] + * Clone Set: dependent-clone [dependent-rsc] (promotable): + * Promoted: [ node3 ] + * Unpromoted: [ node1 node2 node4 ] + +Transition Summary: + * Promote dependent-rsc:0 ( Unpromoted -> Promoted node1 ) + * Promote dependent-rsc:1 ( Unpromoted -> Promoted node2 ) + * Demote dependent-rsc:2 ( Promoted -> Unpromoted node3 ) + +Executing Cluster Transition: + * Resource action: primary-rsc monitor=10000 on node1 + * Resource action: primary-rsc monitor=11000 on node2 + * Resource action: primary-rsc monitor=11000 on node3 + * Resource action: primary-rsc monitor=11000 on node4 + * Resource action: dependent-rsc monitor=11000 on node4 + * Pseudo action: dependent-clone_demote_0 + * Resource action: dependent-rsc demote on node3 + * Pseudo action: dependent-clone_demoted_0 + * Pseudo action: dependent-clone_promote_0 + * Resource action: dependent-rsc promote on node1 + * Resource action: dependent-rsc promote on node2 + * Resource action: dependent-rsc monitor=11000 on node3 + * Pseudo action: dependent-clone_promoted_0 + * Resource action: dependent-rsc monitor=10000 on node1 + * Resource action: dependent-rsc monitor=10000 on node2 + +Revised Cluster Status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: primary-clone [primary-rsc] (promotable): + * Promoted: [ node1 ] + * Unpromoted: [ node2 node3 node4 ] + * Clone Set: dependent-clone [dependent-rsc] (promotable): + * Promoted: [ node1 node2 ] + * Unpromoted: [ node3 node4 ] diff --git a/cts/scheduler/xml/promotable-colocation-with-node-attribute.xml b/cts/scheduler/xml/promotable-colocation-with-node-attribute.xml new file mode 100644 index 00000000000..328cfbc901c --- /dev/null +++ b/cts/scheduler/xml/promotable-colocation-with-node-attribute.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9f62d96c36db96b721248c2b7f268d319559a900 Mon Sep 17 00:00:00 2001 From: Klaus Wenninger Date: Tue, 18 Aug 2026 15:55:21 +0200 Subject: [PATCH 2/4] Fix: scheduler: promoted state with promoted state with attribute Previously attribute based colocation didn't adhere the attribute with promoted state with promoted state. --- lib/pacemaker/libpacemaker_private.h | 5 +- lib/pacemaker/pcmk_sched_bundle.c | 2 +- lib/pacemaker/pcmk_sched_clone.c | 2 +- lib/pacemaker/pcmk_sched_instances.c | 76 ++++++++++++++++++--------- lib/pacemaker/pcmk_sched_probes.c | 2 +- lib/pacemaker/pcmk_sched_promotable.c | 3 +- 6 files changed, 60 insertions(+), 30 deletions(-) diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h index 9e8a02ef0f6..e68c4256cd3 100644 --- a/lib/pacemaker/libpacemaker_private.h +++ b/lib/pacemaker/libpacemaker_private.h @@ -949,13 +949,14 @@ void pcmk__create_instance_actions(pcmk_resource_t *rsc, GList *instances); G_GNUC_INTERNAL bool pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node, enum rsc_role_e role, - bool current); + bool current, const char *node_attribute); G_GNUC_INTERNAL pcmk_resource_t *pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc, const pcmk_resource_t *rsc, enum rsc_role_e role, - bool current); + bool current, + const char *node_attribute); G_GNUC_INTERNAL uint32_t pcmk__instance_update_ordered_actions(pcmk_action_t *first, diff --git a/lib/pacemaker/pcmk_sched_bundle.c b/lib/pacemaker/pcmk_sched_bundle.c index f1accf24474..ddb9aa58e61 100644 --- a/lib/pacemaker/pcmk_sched_bundle.c +++ b/lib/pacemaker/pcmk_sched_bundle.c @@ -376,7 +376,7 @@ match_replica_container(const pcmk__bundle_replica_t *replica, void *user_data) struct match_data *match_data = user_data; if (pcmk__instance_matches(replica->container, match_data->node, - pcmk_role_unknown, false)) { + pcmk_role_unknown, false, NULL)) { match_data->container = replica->container; return false; // Match found, don't bother searching further replicas } diff --git a/lib/pacemaker/pcmk_sched_clone.c b/lib/pacemaker/pcmk_sched_clone.c index 12604a6e836..a46c4bd341a 100644 --- a/lib/pacemaker/pcmk_sched_clone.c +++ b/lib/pacemaker/pcmk_sched_clone.c @@ -296,7 +296,7 @@ pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent, primary_instance = pcmk__find_compatible_instance(dependent, primary, pcmk_role_unknown, - false); + false, NULL); if (primary_instance != NULL) { pcmk__rsc_debug(primary, "Interleaving %s with %s", dependent->id, primary_instance->id); diff --git a/lib/pacemaker/pcmk_sched_instances.c b/lib/pacemaker/pcmk_sched_instances.c index b231c0c5fb0..db315f42347 100644 --- a/lib/pacemaker/pcmk_sched_instances.c +++ b/lib/pacemaker/pcmk_sched_instances.c @@ -1063,18 +1063,22 @@ free_instance_list(const pcmk_resource_t *rsc, GList *list) * \internal * \brief Check whether an instance is compatible with a role and node * - * \param[in] instance Clone instance or bundle replica container - * \param[in] node Instance must match this node - * \param[in] role If not pcmk_role_unknown, instance must match this role - * \param[in] current If true, compare instance's original node and role, - * otherwise compare assigned next node and role + * \param[in] instance Clone instance or bundle replica container + * \param[in] node Instance must match this node + * \param[in] role If not pcmk_role_unknown, instance must match this role + * \param[in] current If true, compare instance's original node and role, + * otherwise compare assigned next node and role + * \param[in] node_attribute If not NULL, instance's node must have the same value + * for this attribute as \p node (instead of requiring + * the exact same node) * * \return true if \p instance is compatible with \p node and \p role, * otherwise false */ bool pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node, - enum rsc_role_e role, bool current) + enum rsc_role_e role, bool current, + const char *node_attribute) { pcmk_node_t *instance_node = NULL; @@ -1101,7 +1105,25 @@ pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node, return false; } - if (!pcmk__same_node(instance_node, node)) { + if (node_attribute != NULL) { + // Compare by node attribute value instead of node identity + const char *instance_value = pcmk__colocation_node_attr(instance_node, + node_attribute, + instance); + const char *target_value = pcmk__colocation_node_attr(node, + node_attribute, + instance); + + if (!pcmk__str_eq(instance_value, target_value, pcmk__str_casei)) { + pcmk__rsc_trace(instance, + "%s is not a compatible instance " + "(instance has %s=%s, target node has %s=%s)", + instance->id, node_attribute, + pcmk__s(instance_value, ""), + node_attribute, pcmk__s(target_value, "")); + return false; + } + } else if (!pcmk__same_node(instance_node, node)) { pcmk__rsc_trace(instance, "%s is not a compatible instance " "(assigned to %s not %s)", @@ -1120,12 +1142,14 @@ pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node, * \internal * \brief Find an instance that matches a given resource by node and role * - * \param[in] match_rsc Resource that instance must match (for logging only) - * \param[in] rsc Clone or bundle resource to check for matching instance - * \param[in] node Instance must match this node - * \param[in] role If not pcmk_role_unknown, instance must match this role - * \param[in] current If true, compare instance's original node and role, - * otherwise compare assigned next node and role + * \param[in] match_rsc Resource that instance must match (for logging only) + * \param[in] rsc Clone or bundle resource to check for matching instance + * \param[in] node Instance must match this node + * \param[in] role If not pcmk_role_unknown, instance must match this role + * \param[in] current If true, compare instance's original node and role, + * otherwise compare assigned next node and role + * \param[in] node_attribute If not NULL, match instances by this node attribute + * instead of by node identity * * \return \p rsc instance matching \p node and \p role if any, otherwise NULL */ @@ -1133,7 +1157,7 @@ static pcmk_resource_t * find_compatible_instance_on_node(const pcmk_resource_t *match_rsc, const pcmk_resource_t *rsc, const pcmk_node_t *node, enum rsc_role_e role, - bool current) + bool current, const char *node_attribute) { GList *instances = NULL; @@ -1141,7 +1165,8 @@ find_compatible_instance_on_node(const pcmk_resource_t *match_rsc, for (GList *iter = instances; iter != NULL; iter = iter->next) { pcmk_resource_t *instance = (pcmk_resource_t *) iter->data; - if (pcmk__instance_matches(instance, node, role, current)) { + if (pcmk__instance_matches(instance, node, role, current, + node_attribute)) { pcmk__rsc_trace(match_rsc, "Found %s %s instance %s compatible with %s on %s", display_role(role), rsc->id, instance->id, @@ -1163,11 +1188,13 @@ find_compatible_instance_on_node(const pcmk_resource_t *match_rsc, * \internal * \brief Find a clone instance or bundle container compatible with a resource * - * \param[in] match_rsc Resource that instance must match - * \param[in] rsc Clone or bundle resource to check for matching instance - * \param[in] role If not pcmk_role_unknown, instance must match this role - * \param[in] current If true, compare instance's original node and role, - * otherwise compare assigned next node and role + * \param[in] match_rsc Resource that instance must match + * \param[in] rsc Clone or bundle resource to check for matching instance + * \param[in] role If not pcmk_role_unknown, instance must match this role + * \param[in] current If true, compare instance's original node and role, + * otherwise compare assigned next node and role + * \param[in] node_attribute If not NULL, match instances by this node attribute + * instead of by node identity * * \return Compatible (by \p role and \p match_rsc location) instance of \p rsc * if any, otherwise NULL @@ -1175,7 +1202,7 @@ find_compatible_instance_on_node(const pcmk_resource_t *match_rsc, pcmk_resource_t * pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc, const pcmk_resource_t *rsc, enum rsc_role_e role, - bool current) + bool current, const char *node_attribute) { pcmk_resource_t *instance = NULL; GList *nodes = NULL; @@ -1185,7 +1212,7 @@ pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc, node = match_rsc->fns->location(match_rsc, NULL, current); if (node != NULL) { return find_compatible_instance_on_node(match_rsc, rsc, node, role, - current); + current, node_attribute); } // Otherwise check for an instance matching any of match_rsc's allowed nodes @@ -1195,7 +1222,8 @@ pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc, iter = iter->next) { instance = find_compatible_instance_on_node(match_rsc, rsc, (pcmk_node_t *) iter->data, - role, current); + role, current, + node_attribute); } if (instance == NULL) { @@ -1402,7 +1430,7 @@ update_interleaved_actions(pcmk_action_t *first, pcmk_action_t *then, first_instance = pcmk__find_compatible_instance(then_instance, first->rsc, pcmk_role_unknown, - current); + current, NULL); if (first_instance == NULL) { // No instance can be interleaved if (unassign_if_mandatory(first, then, then_instance, type, diff --git a/lib/pacemaker/pcmk_sched_probes.c b/lib/pacemaker/pcmk_sched_probes.c index 56820f173c9..a18a29c0b39 100644 --- a/lib/pacemaker/pcmk_sched_probes.c +++ b/lib/pacemaker/pcmk_sched_probes.c @@ -600,7 +600,7 @@ add_restart_orderings_for_probe(pcmk_action_t *probe, pcmk_action_t *after) compatible_rsc = pcmk__find_compatible_instance(probe->rsc, after->rsc, pcmk_role_unknown, - false); + false, NULL); } } diff --git a/lib/pacemaker/pcmk_sched_promotable.c b/lib/pacemaker/pcmk_sched_promotable.c index 852fcab3bea..84b338bebad 100644 --- a/lib/pacemaker/pcmk_sched_promotable.c +++ b/lib/pacemaker/pcmk_sched_promotable.c @@ -1274,7 +1274,8 @@ pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary, // Look for a primary instance where dependent will be primary_instance = pcmk__find_compatible_instance(dependent, primary, colocation->primary_role, - false); + false, + colocation->node_attribute); if (primary_instance != NULL) { // Add primary instance's priority to dependent's From ec2a25a4923ba78fb576e03faee4955e9f7c9332 Mon Sep 17 00:00:00 2001 From: Klaus Wenninger Date: Mon, 17 Aug 2026 15:01:46 +0200 Subject: [PATCH 3/4] Test: scheduler: check for self-colocation of promotable role with attribute --- cts/cts-scheduler.in | 6 + ...-self-colocation-with-node-attribute-2.dot | 16 ++ ...-self-colocation-with-node-attribute-3.dot | 28 +++ ...le-self-colocation-with-node-attribute.dot | 16 ++ ...-self-colocation-with-node-attribute-2.exp | 95 +++++++++++ ...-self-colocation-with-node-attribute-3.exp | 159 ++++++++++++++++++ ...le-self-colocation-with-node-attribute.exp | 95 +++++++++++ ...lf-colocation-with-node-attribute-2.scores | 41 +++++ ...lf-colocation-with-node-attribute-3.scores | 41 +++++ ...self-colocation-with-node-attribute.scores | 41 +++++ ...f-colocation-with-node-attribute-2.summary | 30 ++++ ...f-colocation-with-node-attribute-3.summary | 37 ++++ ...elf-colocation-with-node-attribute.summary | 30 ++++ ...-self-colocation-with-node-attribute-2.xml | 120 +++++++++++++ ...-self-colocation-with-node-attribute-3.xml | 120 +++++++++++++ ...le-self-colocation-with-node-attribute.xml | 128 ++++++++++++++ 16 files changed, 1003 insertions(+) create mode 100644 cts/scheduler/dot/promotable-self-colocation-with-node-attribute-2.dot create mode 100644 cts/scheduler/dot/promotable-self-colocation-with-node-attribute-3.dot create mode 100644 cts/scheduler/dot/promotable-self-colocation-with-node-attribute.dot create mode 100644 cts/scheduler/exp/promotable-self-colocation-with-node-attribute-2.exp create mode 100644 cts/scheduler/exp/promotable-self-colocation-with-node-attribute-3.exp create mode 100644 cts/scheduler/exp/promotable-self-colocation-with-node-attribute.exp create mode 100644 cts/scheduler/scores/promotable-self-colocation-with-node-attribute-2.scores create mode 100644 cts/scheduler/scores/promotable-self-colocation-with-node-attribute-3.scores create mode 100644 cts/scheduler/scores/promotable-self-colocation-with-node-attribute.scores create mode 100644 cts/scheduler/summary/promotable-self-colocation-with-node-attribute-2.summary create mode 100644 cts/scheduler/summary/promotable-self-colocation-with-node-attribute-3.summary create mode 100644 cts/scheduler/summary/promotable-self-colocation-with-node-attribute.summary create mode 100644 cts/scheduler/xml/promotable-self-colocation-with-node-attribute-2.xml create mode 100644 cts/scheduler/xml/promotable-self-colocation-with-node-attribute-3.xml create mode 100644 cts/scheduler/xml/promotable-self-colocation-with-node-attribute.xml diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in index 8b5c7a65fb7..ff9ea63cb63 100644 --- a/cts/cts-scheduler.in +++ b/cts/cts-scheduler.in @@ -748,6 +748,12 @@ TESTS = [ "Verify ordered sets work with symmetrical=true" ], [ "clbz5007-promotable-colocation", "Verify use of colocation scores other than INFINITY and -INFINITY work on multi-state resources" ], + [ "promotable-self-colocation-with-node-attribute", + "Group promotions on a single site via self-colocation with node attribute" ], + [ "promotable-self-colocation-with-node-attribute-2", + "Group promotions on siteB (not siteA) via self-colocation with node attribute" ], + [ "promotable-self-colocation-with-node-attribute-3", + "Correct wrong-site promotions via self-colocation with node attribute" ], [ "bug-5038", "Prevent restart of anonymous clones when clone-max decreases" ], [ "bug-5025-1", "Automatically clean up failcount after resource config change with reload" ], [ "bug-5025-2", "Make sure clear failcount action isn't set when config does not change" ], diff --git a/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-2.dot b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-2.dot new file mode 100644 index 00000000000..72003b9a82e --- /dev/null +++ b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-2.dot @@ -0,0 +1,16 @@ + digraph "g" { +"site-clone_promote_0" -> "site-rsc_promote_0 node3" [ style = bold] +"site-clone_promote_0" -> "site-rsc_promote_0 node4" [ style = bold] +"site-clone_promote_0" [ style=bold color="green" fontcolor="orange"] +"site-clone_promoted_0" [ style=bold color="green" fontcolor="orange"] +"site-rsc_monitor_10000 node3" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_10000 node4" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node1" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node2" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node3" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node3" -> "site-rsc_monitor_10000 node3" [ style = bold] +"site-rsc_promote_0 node3" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node4" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node4" -> "site-rsc_monitor_10000 node4" [ style = bold] +"site-rsc_promote_0 node4" [ style=bold color="green" fontcolor="black"] +} diff --git a/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-3.dot b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-3.dot new file mode 100644 index 00000000000..24c52ef753c --- /dev/null +++ b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute-3.dot @@ -0,0 +1,28 @@ + digraph "g" { +"site-clone_demote_0" -> "site-clone_demoted_0" [ style = bold] +"site-clone_demote_0" -> "site-rsc_demote_0 node3" [ style = bold] +"site-clone_demote_0" -> "site-rsc_demote_0 node4" [ style = bold] +"site-clone_demote_0" [ style=bold color="green" fontcolor="orange"] +"site-clone_demoted_0" -> "site-clone_promote_0" [ style = bold] +"site-clone_demoted_0" [ style=bold color="green" fontcolor="orange"] +"site-clone_promote_0" -> "site-rsc_promote_0 node1" [ style = bold] +"site-clone_promote_0" -> "site-rsc_promote_0 node2" [ style = bold] +"site-clone_promote_0" [ style=bold color="green" fontcolor="orange"] +"site-clone_promoted_0" [ style=bold color="green" fontcolor="orange"] +"site-rsc_demote_0 node3" -> "site-clone_demoted_0" [ style = bold] +"site-rsc_demote_0 node3" -> "site-rsc_monitor_11000 node3" [ style = bold] +"site-rsc_demote_0 node3" [ style=bold color="green" fontcolor="black"] +"site-rsc_demote_0 node4" -> "site-clone_demoted_0" [ style = bold] +"site-rsc_demote_0 node4" -> "site-rsc_monitor_11000 node4" [ style = bold] +"site-rsc_demote_0 node4" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_10000 node1" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_10000 node2" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node3" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node4" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node1" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node1" -> "site-rsc_monitor_10000 node1" [ style = bold] +"site-rsc_promote_0 node1" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node2" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node2" -> "site-rsc_monitor_10000 node2" [ style = bold] +"site-rsc_promote_0 node2" [ style=bold color="green" fontcolor="black"] +} diff --git a/cts/scheduler/dot/promotable-self-colocation-with-node-attribute.dot b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute.dot new file mode 100644 index 00000000000..b5685f2697f --- /dev/null +++ b/cts/scheduler/dot/promotable-self-colocation-with-node-attribute.dot @@ -0,0 +1,16 @@ + digraph "g" { +"site-clone_promote_0" -> "site-rsc_promote_0 node1" [ style = bold] +"site-clone_promote_0" -> "site-rsc_promote_0 node2" [ style = bold] +"site-clone_promote_0" [ style=bold color="green" fontcolor="orange"] +"site-clone_promoted_0" [ style=bold color="green" fontcolor="orange"] +"site-rsc_monitor_10000 node1" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_10000 node2" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node3" [ style=bold color="green" fontcolor="black"] +"site-rsc_monitor_11000 node4" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node1" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node1" -> "site-rsc_monitor_10000 node1" [ style = bold] +"site-rsc_promote_0 node1" [ style=bold color="green" fontcolor="black"] +"site-rsc_promote_0 node2" -> "site-clone_promoted_0" [ style = bold] +"site-rsc_promote_0 node2" -> "site-rsc_monitor_10000 node2" [ style = bold] +"site-rsc_promote_0 node2" [ style=bold color="green" fontcolor="black"] +} diff --git a/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-2.exp b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-2.exp new file mode 100644 index 00000000000..a9bed76ec72 --- /dev/null +++ b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-2.exp @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-3.exp b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-3.exp new file mode 100644 index 00000000000..c2c75295f60 --- /dev/null +++ b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute-3.exp @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/exp/promotable-self-colocation-with-node-attribute.exp b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute.exp new file mode 100644 index 00000000000..ccdae1f5483 --- /dev/null +++ b/cts/scheduler/exp/promotable-self-colocation-with-node-attribute.exp @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-2.scores b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-2.scores new file mode 100644 index 00000000000..27475c0d823 --- /dev/null +++ b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-2.scores @@ -0,0 +1,41 @@ + +pcmk__clone_assign: site-clone allocation score on node1: 0 +pcmk__clone_assign: site-clone allocation score on node2: 0 +pcmk__clone_assign: site-clone allocation score on node3: 0 +pcmk__clone_assign: site-clone allocation score on node4: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node1: 7 +pcmk__clone_assign: site-rsc:0 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node2: 8 +pcmk__clone_assign: site-rsc:1 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node3: 11 +pcmk__clone_assign: site-rsc:2 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node4: 9 +pcmk__primitive_assign: site-rsc:0 allocation score on node1: 7 +pcmk__primitive_assign: site-rsc:0 allocation score on node2: -INFINITY +pcmk__primitive_assign: site-rsc:0 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:0 allocation score on node4: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node1: 0 +pcmk__primitive_assign: site-rsc:1 allocation score on node2: 8 +pcmk__primitive_assign: site-rsc:1 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node4: -INFINITY +pcmk__primitive_assign: site-rsc:2 allocation score on node1: 0 +pcmk__primitive_assign: site-rsc:2 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:2 allocation score on node3: 11 +pcmk__primitive_assign: site-rsc:2 allocation score on node4: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node1: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:3 allocation score on node4: 9 +site-rsc:0 promotion score on node1: -INFINITY +site-rsc:1 promotion score on node2: -INFINITY +site-rsc:2 promotion score on node3: 10 +site-rsc:3 promotion score on node4: 8 diff --git a/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-3.scores b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-3.scores new file mode 100644 index 00000000000..26027b4053b --- /dev/null +++ b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute-3.scores @@ -0,0 +1,41 @@ + +pcmk__clone_assign: site-clone allocation score on node1: 0 +pcmk__clone_assign: site-clone allocation score on node2: 0 +pcmk__clone_assign: site-clone allocation score on node3: 0 +pcmk__clone_assign: site-clone allocation score on node4: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node1: 11 +pcmk__clone_assign: site-rsc:0 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node2: 7 +pcmk__clone_assign: site-rsc:1 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node3: 9 +pcmk__clone_assign: site-rsc:2 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node4: 8 +pcmk__primitive_assign: site-rsc:0 allocation score on node1: 11 +pcmk__primitive_assign: site-rsc:0 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:0 allocation score on node3: 0 +pcmk__primitive_assign: site-rsc:0 allocation score on node4: 0 +pcmk__primitive_assign: site-rsc:1 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node2: 7 +pcmk__primitive_assign: site-rsc:1 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node4: -INFINITY +pcmk__primitive_assign: site-rsc:2 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:2 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:2 allocation score on node3: 9 +pcmk__primitive_assign: site-rsc:2 allocation score on node4: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:3 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:3 allocation score on node4: 8 +site-rsc:0 promotion score on node1: 10 +site-rsc:1 promotion score on node2: 6 +site-rsc:2 promotion score on node3: -INFINITY +site-rsc:3 promotion score on node4: -INFINITY diff --git a/cts/scheduler/scores/promotable-self-colocation-with-node-attribute.scores b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute.scores new file mode 100644 index 00000000000..26027b4053b --- /dev/null +++ b/cts/scheduler/scores/promotable-self-colocation-with-node-attribute.scores @@ -0,0 +1,41 @@ + +pcmk__clone_assign: site-clone allocation score on node1: 0 +pcmk__clone_assign: site-clone allocation score on node2: 0 +pcmk__clone_assign: site-clone allocation score on node3: 0 +pcmk__clone_assign: site-clone allocation score on node4: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node1: 11 +pcmk__clone_assign: site-rsc:0 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:0 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node2: 7 +pcmk__clone_assign: site-rsc:1 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:1 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:2 allocation score on node3: 9 +pcmk__clone_assign: site-rsc:2 allocation score on node4: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node1: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node2: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node3: 0 +pcmk__clone_assign: site-rsc:3 allocation score on node4: 8 +pcmk__primitive_assign: site-rsc:0 allocation score on node1: 11 +pcmk__primitive_assign: site-rsc:0 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:0 allocation score on node3: 0 +pcmk__primitive_assign: site-rsc:0 allocation score on node4: 0 +pcmk__primitive_assign: site-rsc:1 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node2: 7 +pcmk__primitive_assign: site-rsc:1 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:1 allocation score on node4: -INFINITY +pcmk__primitive_assign: site-rsc:2 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:2 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:2 allocation score on node3: 9 +pcmk__primitive_assign: site-rsc:2 allocation score on node4: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node1: -INFINITY +pcmk__primitive_assign: site-rsc:3 allocation score on node2: 0 +pcmk__primitive_assign: site-rsc:3 allocation score on node3: -INFINITY +pcmk__primitive_assign: site-rsc:3 allocation score on node4: 8 +site-rsc:0 promotion score on node1: 10 +site-rsc:1 promotion score on node2: 6 +site-rsc:2 promotion score on node3: -INFINITY +site-rsc:3 promotion score on node4: -INFINITY diff --git a/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-2.summary b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-2.summary new file mode 100644 index 00000000000..8085bba0baa --- /dev/null +++ b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-2.summary @@ -0,0 +1,30 @@ +Current cluster status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Unpromoted: [ node1 node2 node3 node4 ] + +Transition Summary: + * Promote site-rsc:2 ( Unpromoted -> Promoted node3 ) + * Promote site-rsc:3 ( Unpromoted -> Promoted node4 ) + +Executing Cluster Transition: + * Resource action: site-rsc monitor=11000 on node1 + * Resource action: site-rsc monitor=11000 on node2 + * Pseudo action: site-clone_promote_0 + * Resource action: site-rsc promote on node3 + * Resource action: site-rsc promote on node4 + * Pseudo action: site-clone_promoted_0 + * Resource action: site-rsc monitor=10000 on node3 + * Resource action: site-rsc monitor=10000 on node4 + +Revised Cluster Status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Promoted: [ node3 node4 ] + * Unpromoted: [ node1 node2 ] diff --git a/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-3.summary b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-3.summary new file mode 100644 index 00000000000..4efbf725eb1 --- /dev/null +++ b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute-3.summary @@ -0,0 +1,37 @@ +Current cluster status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Promoted: [ node3 node4 ] + * Unpromoted: [ node1 node2 ] + +Transition Summary: + * Promote site-rsc:0 ( Unpromoted -> Promoted node1 ) + * Promote site-rsc:1 ( Unpromoted -> Promoted node2 ) + * Demote site-rsc:2 ( Promoted -> Unpromoted node3 ) + * Demote site-rsc:3 ( Promoted -> Unpromoted node4 ) + +Executing Cluster Transition: + * Pseudo action: site-clone_demote_0 + * Resource action: site-rsc demote on node3 + * Resource action: site-rsc demote on node4 + * Pseudo action: site-clone_demoted_0 + * Pseudo action: site-clone_promote_0 + * Resource action: site-rsc promote on node1 + * Resource action: site-rsc promote on node2 + * Resource action: site-rsc monitor=11000 on node3 + * Resource action: site-rsc monitor=11000 on node4 + * Pseudo action: site-clone_promoted_0 + * Resource action: site-rsc monitor=10000 on node1 + * Resource action: site-rsc monitor=10000 on node2 + +Revised Cluster Status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Promoted: [ node1 node2 ] + * Unpromoted: [ node3 node4 ] diff --git a/cts/scheduler/summary/promotable-self-colocation-with-node-attribute.summary b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute.summary new file mode 100644 index 00000000000..c5d37e062a6 --- /dev/null +++ b/cts/scheduler/summary/promotable-self-colocation-with-node-attribute.summary @@ -0,0 +1,30 @@ +Current cluster status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Unpromoted: [ node1 node2 node3 node4 ] + +Transition Summary: + * Promote site-rsc:0 ( Unpromoted -> Promoted node1 ) + * Promote site-rsc:1 ( Unpromoted -> Promoted node2 ) + +Executing Cluster Transition: + * Resource action: site-rsc monitor=11000 on node3 + * Resource action: site-rsc monitor=11000 on node4 + * Pseudo action: site-clone_promote_0 + * Resource action: site-rsc promote on node1 + * Resource action: site-rsc promote on node2 + * Pseudo action: site-clone_promoted_0 + * Resource action: site-rsc monitor=10000 on node1 + * Resource action: site-rsc monitor=10000 on node2 + +Revised Cluster Status: + * Node List: + * Online: [ node1 node2 node3 node4 ] + + * Full List of Resources: + * Clone Set: site-clone [site-rsc] (promotable): + * Promoted: [ node1 node2 ] + * Unpromoted: [ node3 node4 ] diff --git a/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-2.xml b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-2.xml new file mode 100644 index 00000000000..383f75abaf3 --- /dev/null +++ b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-2.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-3.xml b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-3.xml new file mode 100644 index 00000000000..f2503b3a45e --- /dev/null +++ b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute-3.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/xml/promotable-self-colocation-with-node-attribute.xml b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute.xml new file mode 100644 index 00000000000..a35ab18f157 --- /dev/null +++ b/cts/scheduler/xml/promotable-self-colocation-with-node-attribute.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8a25a6e2816177ce772f9aac9866c49e94879d1d Mon Sep 17 00:00:00 2001 From: Klaus Wenninger Date: Mon, 17 Aug 2026 14:58:29 +0200 Subject: [PATCH 4/4] Feature: scheduler: allow self-colocation of promotable role with attribute --- lib/pacemaker/pcmk_sched_promotable.c | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/lib/pacemaker/pcmk_sched_promotable.c b/lib/pacemaker/pcmk_sched_promotable.c index 84b338bebad..87954ea828f 100644 --- a/lib/pacemaker/pcmk_sched_promotable.c +++ b/lib/pacemaker/pcmk_sched_promotable.c @@ -340,6 +340,121 @@ add_sort_index_to_node_score(gpointer data, gpointer user_data) pcmk__node_name(node), pcmk_readable_score(node->weight)); } +/*! + * \internal + * \brief Apply self-colocation to group promotion by node attribute + * + * When a promotable clone colocates its own promoted role with itself + * (i.e. \c rsc and \c with-rsc reference the same promotable clone, both with + * the Promoted role), group promoted instances by the colocation's node + * attribute value, so that all promoted instances will be on nodes sharing the + * same value for that attribute. + * + * This allows multi-site clusters to ensure all promoted instances are on a + * single site without requiring a separate leading primitive resource. + * + * \param[in,out] clone Promotable clone + * \param[in] colocation Self-colocation constraint + */ +static void +apply_site_colocation(pcmk_resource_t *clone, + const pcmk__colocation_t *colocation) +{ + const char *attr = colocation->node_attribute; + GHashTableIter iter; + pcmk_node_t *node = NULL; + GHashTable *site_scores = NULL; + const char *best_site = NULL; + int best_score = -PCMK_SCORE_INFINITY; + gpointer key = NULL; + gpointer val = NULL; + + pcmk__rsc_trace(clone, + "Applying self-colocation %s to group %s promotion " + "by node attribute %s", + colocation->id, clone->id, attr); + + // Compute aggregate promotion score per unique attribute value + site_scores = pcmk__strikey_table(NULL, NULL); + + g_hash_table_iter_init(&iter, clone->allowed_nodes); + while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { + const char *value = pcmk__colocation_node_attr(node, attr, clone); + gpointer lookup = NULL; + int current = 0; + int new_score = 0; + + if (value == NULL) { + continue; + } + if (node->weight <= -PCMK_SCORE_INFINITY) { + continue; // Don't let banned nodes drag down their site + } + + lookup = g_hash_table_lookup(site_scores, value); + current = GPOINTER_TO_INT(lookup); + new_score = pcmk__add_scores(current, node->weight); + g_hash_table_insert(site_scores, (gpointer) value, + GINT_TO_POINTER(new_score)); + } + + // Find the site with the highest aggregate score + g_hash_table_iter_init(&iter, site_scores); + while (g_hash_table_iter_next(&iter, &key, &val)) { + int score = GPOINTER_TO_INT(val); + + pcmk__rsc_trace(clone, "Site %s=%s aggregate promotion score: %s", + attr, (const char *) key, + pcmk_readable_score(score)); + if (score > best_score) { + best_score = score; + best_site = (const char *) key; + } + } + + g_hash_table_destroy(site_scores); + + if (best_site == NULL) { + pcmk__rsc_trace(clone, + "No eligible site found for self-colocation %s", + colocation->id); + return; + } + + pcmk__rsc_debug(clone, + "Self-colocation %s favors promoting %s " + "on site %s=%s (score %s)", + colocation->id, clone->id, attr, best_site, + pcmk_readable_score(best_score)); + + // Apply preference for the winning site + g_hash_table_iter_init(&iter, clone->allowed_nodes); + while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { + const char *value = pcmk__colocation_node_attr(node, attr, clone); + bool matches = pcmk__str_eq(value, best_site, pcmk__str_casei); + + if (!matches && (colocation->score >= PCMK_SCORE_INFINITY)) { + node->weight = -PCMK_SCORE_INFINITY; + pcmk__rsc_trace(clone, + "Banned %s from promotion of %s " + "(not on preferred site %s=%s)", + pcmk__node_name(node), clone->id, + attr, best_site); + + } else if (matches && (colocation->score > 0) + && (colocation->score < PCMK_SCORE_INFINITY)) { + node->weight = pcmk__add_scores(colocation->score, + node->weight); + pcmk__rsc_trace(clone, + "Added %s to %s score on %s for site colocation " + "(now %s)", + pcmk_readable_score(colocation->score), + clone->id, pcmk__node_name(node), + pcmk_readable_score(node->weight)); + } + } +} + /*! * \internal * \brief Apply colocation to primary's node scores if for promoted role @@ -362,6 +477,14 @@ apply_coloc_to_primary(gpointer data, gpointer user_data) return; } + // Self-colocation: group promotion by node attribute value + if (dependent == clone) { + if (colocation->score > 0) { + apply_site_colocation(clone, colocation); + } + return; + } + pcmk__rsc_trace(clone, "Applying colocation %s (%s with promoted %s) @%s", colocation->id, colocation->dependent->id, colocation->primary->id,