diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in
index c46a5fdf1c..8e78f568b9 100644
--- a/cts/cts-scheduler.in
+++ b/cts/cts-scheduler.in
@@ -1,1728 +1,1732 @@
#!@PYTHON@
""" Regression tests for Pacemaker's scheduler
"""
__copyright__ = "Copyright 2004-2024 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import io
import os
import re
import sys
import stat
import shlex
import shutil
import argparse
import subprocess
import platform
import tempfile
# These imports allow running from a source checkout after running `make`.
# Note that while this doesn't necessarily mean it will successfully run tests,
# but being able to see --help output can be useful.
if os.path.exists("@abs_top_srcdir@/python"):
sys.path.insert(0, "@abs_top_srcdir@/python")
if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
sys.path.insert(0, "@abs_top_builddir@/python")
from pacemaker.buildoptions import BuildOptions
from pacemaker.exitstatus import ExitStatus
DESC = """Regression tests for Pacemaker's scheduler"""
class SchedulerTest:
def __init__(self, name, desc, args=None):
self.name = name
self.desc = desc
if args is None:
self.args = []
else:
self.args = args
class SchedulerTestGroup:
def __init__(self, tests):
self.tests = tests
# Each entry in TESTS is a group of tests, where each test consists of a
# test base name, test description, and additional test arguments.
# Test groups will be separated by newlines in output.
TESTS = [
SchedulerTestGroup([
SchedulerTest("simple1", "Offline"),
SchedulerTest("simple2", "Start"),
SchedulerTest("simple3", "Start 2"),
SchedulerTest("simple4", "Start Failed"),
SchedulerTest("simple6", "Stop Start"),
SchedulerTest("simple7", "Shutdown"),
SchedulerTest("simple11", "Priority (ne)"),
SchedulerTest("simple12", "Priority (eq)"),
SchedulerTest("simple8", "Stickiness"),
]),
SchedulerTestGroup([
SchedulerTest("group1", "Group"),
SchedulerTest("group2", "Group + Native"),
SchedulerTest("group3", "Group + Group"),
SchedulerTest("group4", "Group + Native (nothing)"),
SchedulerTest("group5", "Group + Native (move)"),
SchedulerTest("group6", "Group + Group (move)"),
SchedulerTest("group7", "Group colocation"),
SchedulerTest("group13", "Group colocation (cant run)"),
SchedulerTest("group8", "Group anti-colocation"),
SchedulerTest("group9", "Group recovery"),
SchedulerTest("group10", "Group partial recovery"),
SchedulerTest("group11", "Group target_role"),
SchedulerTest("group14", "Group stop (graph terminated)"),
SchedulerTest("group15", "Negative group colocation"),
SchedulerTest("bug-1573", "Partial stop of a group with two children"),
SchedulerTest("bug-1718", "Mandatory group ordering - Stop group_FUN"),
SchedulerTest("failed-sticky-group", "Move group on last member failure despite infinite stickiness"),
SchedulerTest("failed-sticky-anticolocated-group",
"Move group on last member failure despite infinite stickiness and optional anti-colocation"),
SchedulerTest("bug-lf-2619", "Move group on clone failure"),
SchedulerTest("group-fail", "Ensure stop order is preserved for partially active groups"),
SchedulerTest("group-unmanaged", "No need to restart r115 because r114 is unmanaged"),
SchedulerTest("group-unmanaged-stopped", "Make sure r115 is stopped when r114 fails"),
SchedulerTest("partial-unmanaged-group", "New member in partially unmanaged group"),
SchedulerTest("group-dependents", "Account for the location preferences of things colocated with a group"),
SchedulerTest("group-stop-ordering", "Ensure blocked group member stop does not force other member stops"),
SchedulerTest("colocate-unmanaged-group", "Respect mandatory colocations even if earlier group member is unmanaged"),
SchedulerTest("coloc-with-inner-group-member", "Consider explicit colocations with inner group members"),
SchedulerTest("banned-group-inner-constraints",
"Group banned from current node, inner member constrained"),
]),
SchedulerTestGroup([
SchedulerTest("rsc_dep1", "Must not"),
SchedulerTest("rsc_dep3", "Must"),
SchedulerTest("rsc_dep5", "Must not 3"),
SchedulerTest("rsc_dep7", "Must 3"),
SchedulerTest("rsc_dep10", "Must (but cant)"),
SchedulerTest("rsc_dep2", "Must (running)"),
SchedulerTest("rsc_dep8", "Must (running : alt)"),
SchedulerTest("rsc_dep4", "Must (running + move)"),
SchedulerTest("asymmetric", "Asymmetric - require explicit location constraints"),
]),
SchedulerTestGroup([
SchedulerTest("orphan-0", "Orphan ignore"),
SchedulerTest("orphan-1", "Orphan stop"),
SchedulerTest("orphan-2", "Orphan stop, remove failcount"),
]),
SchedulerTestGroup([
SchedulerTest("params-0", "Params: No change"),
SchedulerTest("params-1", "Params: Changed"),
SchedulerTest("params-2", "Params: Resource definition"),
SchedulerTest("params-3", "Params: Restart instead of reload if start pending"),
SchedulerTest("params-4", "Params: Reload"),
SchedulerTest("params-5", "Params: Restart based on probe digest"),
SchedulerTest("novell-251689", "Resource definition change + target_role=stopped"),
SchedulerTest("bug-lf-2106", "Restart all anonymous clone instances after config change"),
SchedulerTest("params-6", "Params: Detect reload in previously migrated resource"),
SchedulerTest("nvpair-id-ref", "Support id-ref in nvpair with optional name"),
SchedulerTest("not-reschedule-unneeded-monitor",
"Do not reschedule unneeded monitors while resource definitions have changed"),
SchedulerTest("reload-becomes-restart", "Cancel reload if restart becomes required"),
SchedulerTest("restart-with-extra-op-params", "Restart if with extra operation parameters upon changes of any"),
]),
SchedulerTestGroup([
SchedulerTest("target-0", "Target Role : baseline"),
SchedulerTest("target-1", "Target Role : promoted"),
SchedulerTest("target-2", "Target Role : invalid"),
]),
SchedulerTestGroup([
SchedulerTest("base-score", "Set a node's default score for all nodes"),
]),
SchedulerTestGroup([
SchedulerTest("date-1", "Dates", ["-t", "2005-020"]),
SchedulerTest("date-2", "Date Spec - Pass", ["-t", "2005-020T12:30"]),
SchedulerTest("date-3", "Date Spec - Fail", ["-t", "2005-020T11:30"]),
SchedulerTest("origin", "Timing of recurring operations", ["-t", "2014-05-07 00:28:00"]),
SchedulerTest("probe-0", "Probe (anon clone)"),
SchedulerTest("probe-1", "Pending Probe"),
SchedulerTest("probe-2", "Correctly re-probe cloned groups"),
SchedulerTest("probe-3", "Probe (pending node)"),
SchedulerTest("probe-4", "Probe (pending node + stopped resource)"),
SchedulerTest("probe-pending-node", "Probe (pending node + unmanaged resource)"),
SchedulerTest("failed-probe-primitive", "Maskable vs. unmaskable probe failures on primitive resources"),
SchedulerTest("failed-probe-clone", "Maskable vs. unmaskable probe failures on cloned resources"),
SchedulerTest("expired-failed-probe-primitive", "Maskable, expired probe failure on primitive resources"),
SchedulerTest("standby", "Standby"),
SchedulerTest("comments", "Comments"),
]),
SchedulerTestGroup([
SchedulerTest("one-or-more-0", "Everything starts"),
SchedulerTest("one-or-more-1", "Nothing starts because of A"),
SchedulerTest("one-or-more-2", "D can start because of C"),
SchedulerTest("one-or-more-3", "D cannot start because of B and C"),
SchedulerTest("one-or-more-4", "D cannot start because of target-role"),
SchedulerTest("one-or-more-5", "Start A and F even though C and D are stopped"),
SchedulerTest("one-or-more-6", "Leave A running even though B is stopped"),
SchedulerTest("one-or-more-7", "Leave A running even though C is stopped"),
SchedulerTest("bug-5140-require-all-false", "Allow basegrp:0 to stop"),
SchedulerTest("clone-require-all-1", "clone B starts node 3 and 4"),
SchedulerTest("clone-require-all-2", "clone B remains stopped everywhere"),
SchedulerTest("clone-require-all-3", "clone B stops everywhere because A stops everywhere"),
SchedulerTest("clone-require-all-4", "clone B remains on node 3 and 4 with only one instance of A remaining"),
SchedulerTest("clone-require-all-5", "clone B starts on node 1 3 and 4"),
SchedulerTest("clone-require-all-6", "clone B remains active after shutting down instances of A"),
SchedulerTest("clone-require-all-7",
"clone A and B both start at the same time. all instances of A start before B"),
SchedulerTest("clone-require-all-no-interleave-1", "C starts everywhere after A and B"),
SchedulerTest("clone-require-all-no-interleave-2",
"C starts on nodes 1, 2, and 4 with only one active instance of B"),
SchedulerTest("clone-require-all-no-interleave-3",
"C remains active when instance of B is stopped on one node and started on another"),
SchedulerTest("one-or-more-unrunnable-instances", "Avoid dependencies on instances that won't ever be started"),
]),
SchedulerTestGroup([
SchedulerTest("location-date-rules-1", "Use location constraints with ineffective date-based rules"),
SchedulerTest("location-date-rules-2", "Use location constraints with effective date-based rules"),
SchedulerTest("nvpair-date-rules-1", "Use nvpair blocks with a variety of date-based rules"),
SchedulerTest("value-source", "Use location constraints with node attribute expressions using value-source"),
SchedulerTest("rule-dbl-as-auto-number-match",
"Floating-point rule values default to number comparison: match"),
SchedulerTest("rule-dbl-as-auto-number-no-match",
"Floating-point rule values default to number comparison: no match"),
SchedulerTest("rule-dbl-as-integer-match",
"Floating-point rule values set to integer comparison: match"),
SchedulerTest("rule-dbl-as-integer-no-match",
"Floating-point rule values set to integer comparison: no match"),
SchedulerTest("rule-dbl-as-number-match",
"Floating-point rule values set to number comparison: match"),
SchedulerTest("rule-dbl-as-number-no-match",
"Floating-point rule values set to number comparison: no match"),
SchedulerTest("rule-dbl-parse-fail-default-str-match",
"Floating-point rule values fail to parse, default to string "
"comparison: match"),
SchedulerTest("rule-dbl-parse-fail-default-str-no-match",
"Floating-point rule values fail to parse, default to string "
"comparison: no match"),
SchedulerTest("rule-int-as-auto-integer-match",
"Integer rule values default to integer comparison: match"),
SchedulerTest("rule-int-as-auto-integer-no-match",
"Integer rule values default to integer comparison: no match"),
SchedulerTest("rule-int-as-integer-match",
"Integer rule values set to integer comparison: match"),
SchedulerTest("rule-int-as-integer-no-match",
"Integer rule values set to integer comparison: no match"),
SchedulerTest("rule-int-as-number-match",
"Integer rule values set to number comparison: match"),
SchedulerTest("rule-int-as-number-no-match",
"Integer rule values set to number comparison: no match"),
SchedulerTest("rule-int-parse-fail-default-str-match",
"Integer rule values fail to parse, default to string "
"comparison: match"),
SchedulerTest("rule-int-parse-fail-default-str-no-match",
"Integer rule values fail to parse, default to string "
"comparison: no match"),
SchedulerTest("timeout-by-node", "Start timeout varies by node"),
]),
SchedulerTestGroup([
SchedulerTest("order1", "Order start 1"),
SchedulerTest("order2", "Order start 2"),
SchedulerTest("order3", "Order stop"),
SchedulerTest("order4", "Order (multiple)"),
SchedulerTest("order5", "Order (move)"),
SchedulerTest("order6", "Order (move w/ restart)"),
SchedulerTest("order7", "Order (mandatory)"),
SchedulerTest("order-optional", "Order (score=0)"),
SchedulerTest("order-required", "Order (score=INFINITY)"),
SchedulerTest("bug-lf-2171", "Prevent group start when clone is stopped"),
SchedulerTest("order-clone", "Clone ordering should be able to prevent startup of dependent clones"),
SchedulerTest("order-sets", "Ordering for resource sets"),
SchedulerTest("order-serialize", "Serialize resources without inhibiting migration"),
SchedulerTest("order-serialize-set", "Serialize a set of resources without inhibiting migration"),
SchedulerTest("clone-order-primitive", "Order clone start after a primitive"),
SchedulerTest("clone-order-16instances", "Verify ordering of 16 cloned resources"),
SchedulerTest("order-optional-keyword", "Order (optional keyword)"),
SchedulerTest("order-mandatory", "Order (mandatory keyword)"),
SchedulerTest("bug-lf-2493", "Don't imply colocation requirements when applying ordering constraints with clones"),
SchedulerTest("ordered-set-basic-startup", "Constraint set with default order settings"),
SchedulerTest("ordered-set-natural", "Allow natural set ordering"),
SchedulerTest("order-wrong-kind", "Order (error)"),
]),
SchedulerTestGroup([
SchedulerTest("coloc-loop", "Colocation - loop"),
SchedulerTest("coloc-many-one", "Colocation - many-to-one"),
SchedulerTest("coloc-list", "Colocation - many-to-one with list"),
SchedulerTest("coloc-group", "Colocation - groups"),
SchedulerTest("coloc-unpromoted-anti", "Anti-colocation with unpromoted shouldn't prevent promoted colocation"),
SchedulerTest("coloc-attr", "Colocation based on node attributes"),
SchedulerTest("coloc-negative-group", "Negative colocation with a group"),
SchedulerTest("coloc-intra-set", "Intra-set colocation"),
SchedulerTest("bug-lf-2435", "Colocation sets with a negative score"),
SchedulerTest("coloc-clone-stays-active",
"Ensure clones don't get stopped/demoted because a dependent must stop"),
SchedulerTest("coloc_fp_logic", "Verify floating point calculations in colocation are working"),
SchedulerTest("colo_promoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating promoted with primitive"),
SchedulerTest("colo_unpromoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating unpromoted with primitive"),
SchedulerTest("anti-colocation-order",
"cl#5187 - Prevent resources in an anti-colocation from even temporarily running on a same node"),
SchedulerTest("anti-colocation-promoted", "Organize order of actions for promoted resources in anti-colocations"),
SchedulerTest("anti-colocation-unpromoted", "Organize order of actions for unpromoted resources in anti-colocations"),
SchedulerTest("group-anticolocation", "Group with failed last member anti-colocated with another group"),
SchedulerTest("group-anticolocation-2",
"Group with failed last member anti-colocated with another sticky group"),
SchedulerTest("group-anticolocation-3",
"Group with failed last member mandatorily anti-colocated with another group"),
SchedulerTest("group-anticolocation-4",
"Group with failed last member anti-colocated without influence with another group"),
SchedulerTest("group-anticolocation-5",
"Group with failed last member anti-colocated with another group (third node allowed)"),
SchedulerTest("group-colocation-failure",
"Group with sole member failed, colocated with another group"),
SchedulerTest("enforce-colo1", "Always enforce B with A INFINITY"),
SchedulerTest("complex_enforce_colo", "Always enforce B with A INFINITY. (make sure heat-engine stops)"),
SchedulerTest("coloc-dependee-should-stay", "Stickiness outweighs group colocation"),
SchedulerTest("coloc-dependee-should-move", "Group colocation outweighs stickiness"),
SchedulerTest("colocation-influence", "Respect colocation influence"),
SchedulerTest("colocation-priority-group", "Apply group colocations in order of primary priority"),
SchedulerTest("colocation-vs-stickiness", "Group stickiness outweighs anti-colocation score"),
SchedulerTest("promoted-with-blocked", "Promoted role colocated with a resource with blocked start"),
SchedulerTest("primitive-with-group-with-clone",
"Consider group dependent when colocating with clone"),
SchedulerTest("primitive-with-group-with-promoted",
"Consider group dependent when colocating with promoted role"),
SchedulerTest("primitive-with-unrunnable-group",
"Block primitive colocated with group that can't start"),
+ SchedulerTest("coloc-cloned-group-promoted-dependent1",
+ "Cloned group promoted role with primitive (mandatory)"),
+ SchedulerTest("coloc-cloned-group-promoted-dependent2",
+ "Cloned group promoted role with primitive (optional)"),
]),
SchedulerTestGroup([
SchedulerTest("rsc-sets-seq-true", "Resource Sets - sequential=false"),
SchedulerTest("rsc-sets-seq-false", "Resource Sets - sequential=true"),
SchedulerTest("rsc-sets-clone", "Resource Sets - Clone"),
SchedulerTest("rsc-sets-promoted", "Resource Sets - Promoted"),
SchedulerTest("rsc-sets-clone-1", "Resource Sets - Clone (lf#2404)"),
]),
SchedulerTestGroup([
SchedulerTest("attrs1", "string: eq (and)"),
SchedulerTest("attrs2", "string: lt / gt (and)"),
SchedulerTest("attrs3", "string: ne (or)"),
SchedulerTest("attrs4", "string: exists"),
SchedulerTest("attrs5", "string: not_exists"),
SchedulerTest("attrs6", "is_dc: true"),
SchedulerTest("attrs7", "is_dc: false"),
SchedulerTest("attrs8", "score_attribute"),
SchedulerTest("per-node-attrs", "Per node resource parameters"),
]),
SchedulerTestGroup([
SchedulerTest("mon-rsc-1", "Schedule Monitor - start"),
SchedulerTest("mon-rsc-2", "Schedule Monitor - move"),
SchedulerTest("mon-rsc-3", "Schedule Monitor - pending start"),
SchedulerTest("mon-rsc-4", "Schedule Monitor - move/pending start"),
]),
SchedulerTestGroup([
SchedulerTest("rec-rsc-0", "Resource Recover - no start"),
SchedulerTest("rec-rsc-1", "Resource Recover - start"),
SchedulerTest("rec-rsc-2", "Resource Recover - monitor"),
SchedulerTest("rec-rsc-3", "Resource Recover - stop - ignore"),
SchedulerTest("rec-rsc-4", "Resource Recover - stop - block"),
SchedulerTest("rec-rsc-5", "Resource Recover - stop - fence"),
SchedulerTest("rec-rsc-6", "Resource Recover - multiple - restart"),
SchedulerTest("rec-rsc-7", "Resource Recover - multiple - stop"),
SchedulerTest("rec-rsc-8", "Resource Recover - multiple - block"),
SchedulerTest("rec-rsc-9", "Resource Recover - group/group"),
SchedulerTest("stop-unexpected", "Recover multiply active group with stop_unexpected"),
SchedulerTest("stop-unexpected-2", "Resource multiply active primitve with stop_unexpected"),
SchedulerTest("monitor-recovery", "on-fail=block + resource recovery detected by recurring monitor"),
SchedulerTest("stop-failure-no-quorum", "Stop failure without quorum"),
SchedulerTest("stop-failure-no-fencing", "Stop failure without fencing available"),
SchedulerTest("stop-failure-with-fencing", "Stop failure with fencing available"),
SchedulerTest("multiple-active-block-group", "Support of multiple-active=block for resource groups"),
SchedulerTest("multiple-monitor-one-failed",
"Consider resource failed if any of the configured monitor operations failed"),
]),
SchedulerTestGroup([
SchedulerTest("quorum-1", "No quorum - ignore"),
SchedulerTest("quorum-2", "No quorum - freeze"),
SchedulerTest("quorum-3", "No quorum - stop"),
SchedulerTest("quorum-4", "No quorum - start anyway"),
SchedulerTest("quorum-5", "No quorum - start anyway (group)"),
SchedulerTest("quorum-6", "No quorum - start anyway (clone)"),
SchedulerTest("bug-cl-5212", "No promotion with no-quorum-policy=freeze"),
SchedulerTest("suicide-needed-inquorate", "no-quorum-policy=suicide: suicide necessary"),
SchedulerTest("suicide-not-needed-initial-quorum",
"no-quorum-policy=suicide: suicide not necessary at initial quorum"),
SchedulerTest("suicide-not-needed-never-quorate",
"no-quorum-policy=suicide: suicide not necessary if never quorate"),
SchedulerTest("suicide-not-needed-quorate", "no-quorum-policy=suicide: suicide necessary if quorate"),
]),
SchedulerTestGroup([
SchedulerTest("rec-node-1", "Node Recover - Startup - no fence"),
SchedulerTest("rec-node-2", "Node Recover - Startup - fence"),
SchedulerTest("rec-node-3", "Node Recover - HA down - no fence"),
SchedulerTest("rec-node-4", "Node Recover - HA down - fence"),
SchedulerTest("rec-node-5", "Node Recover - CRM down - no fence"),
SchedulerTest("rec-node-6", "Node Recover - CRM down - fence"),
SchedulerTest("rec-node-7", "Node Recover - no quorum - ignore"),
SchedulerTest("rec-node-8", "Node Recover - no quorum - freeze"),
SchedulerTest("rec-node-9", "Node Recover - no quorum - stop"),
SchedulerTest("rec-node-10", "Node Recover - no quorum - stop w/fence"),
SchedulerTest("rec-node-11", "Node Recover - CRM down w/ group - fence"),
SchedulerTest("rec-node-12", "Node Recover - nothing active - fence"),
SchedulerTest("rec-node-13", "Node Recover - failed resource + shutdown - fence"),
SchedulerTest("rec-node-15", "Node Recover - unknown lrm section"),
SchedulerTest("rec-node-14", "Serialize all stonith's"),
]),
SchedulerTestGroup([
SchedulerTest("multi1", "Multiple Active (stop/start)"),
]),
SchedulerTestGroup([
SchedulerTest("migrate-begin", "Normal migration"),
SchedulerTest("migrate-success", "Completed migration"),
SchedulerTest("migrate-partial-1", "Completed migration, missing stop on source"),
SchedulerTest("migrate-partial-2", "Successful migrate_to only"),
SchedulerTest("migrate-partial-3", "Successful migrate_to only, target down"),
SchedulerTest("migrate-partial-4", "Migrate from the correct host after migrate_to+migrate_from"),
SchedulerTest("bug-5186-partial-migrate", "Handle partial migration when src node loses membership"),
SchedulerTest("migrate-fail-2", "Failed migrate_from"),
SchedulerTest("migrate-fail-3", "Failed migrate_from + stop on source"),
SchedulerTest("migrate-fail-4",
"Failed migrate_from + stop on target - ideally we wouldn't need to re-stop on target"),
SchedulerTest("migrate-fail-5", "Failed migrate_from + stop on source and target"),
SchedulerTest("migrate-fail-6", "Failed migrate_to"),
SchedulerTest("migrate-fail-7", "Failed migrate_to + stop on source"),
SchedulerTest("migrate-fail-8",
"Failed migrate_to + stop on target - ideally we wouldn't need to re-stop on target"),
SchedulerTest("migrate-fail-9", "Failed migrate_to + stop on source and target"),
SchedulerTest("migration-ping-pong", "Old migrate_to failure + successful migrate_from on same node"),
SchedulerTest("migrate-stop", "Migration in a stopping stack"),
SchedulerTest("migrate-start", "Migration in a starting stack"),
SchedulerTest("migrate-stop_start", "Migration in a restarting stack"),
SchedulerTest("migrate-stop-complex", "Migration in a complex stopping stack"),
SchedulerTest("migrate-start-complex", "Migration in a complex starting stack"),
SchedulerTest("migrate-stop-start-complex", "Migration in a complex moving stack"),
SchedulerTest("migrate-shutdown", "Order the post-migration 'stop' before node shutdown"),
SchedulerTest("migrate-1", "Migrate (migrate)"),
SchedulerTest("migrate-2", "Migrate (stable)"),
SchedulerTest("migrate-3", "Migrate (failed migrate_to)"),
SchedulerTest("migrate-4", "Migrate (failed migrate_from)"),
SchedulerTest("novell-252693", "Migration in a stopping stack"),
SchedulerTest("novell-252693-2", "Migration in a starting stack"),
SchedulerTest("novell-252693-3", "Non-Migration in a starting and stopping stack"),
SchedulerTest("bug-1820", "Migration in a group"),
SchedulerTest("bug-1820-1", "Non-migration in a group"),
SchedulerTest("migrate-5", "Primitive migration with a clone"),
SchedulerTest("migrate-fencing", "Migration after Fencing"),
SchedulerTest("migrate-both-vms", "Migrate two VMs that have no colocation"),
SchedulerTest("migration-behind-migrating-remote", "Migrate resource behind migrating remote connection"),
SchedulerTest("1-a-then-bm-move-b", "Advanced migrate logic. A then B. migrate B"),
SchedulerTest("2-am-then-b-move-a", "Advanced migrate logic, A then B, migrate A without stopping B"),
SchedulerTest("3-am-then-bm-both-migrate", "Advanced migrate logic. A then B. migrate both"),
SchedulerTest("4-am-then-bm-b-not-migratable", "Advanced migrate logic, A then B, B not migratable"),
SchedulerTest("5-am-then-bm-a-not-migratable", "Advanced migrate logic. A then B. move both, a not migratable"),
SchedulerTest("6-migrate-group", "Advanced migrate logic, migrate a group"),
SchedulerTest("7-migrate-group-one-unmigratable",
"Advanced migrate logic, migrate group mixed with allow-migrate true/false"),
SchedulerTest("8-am-then-bm-a-migrating-b-stopping",
"Advanced migrate logic, A then B, A migrating, B stopping"),
SchedulerTest("9-am-then-bm-b-migrating-a-stopping",
"Advanced migrate logic, A then B, B migrate, A stopping"),
SchedulerTest("10-a-then-bm-b-move-a-clone",
"Advanced migrate logic, A clone then B, migrate B while stopping A"),
SchedulerTest("11-a-then-bm-b-move-a-clone-starting",
"Advanced migrate logic, A clone then B, B moving while A is start/stopping"),
SchedulerTest("a-promote-then-b-migrate", "A promote then B start. migrate B"),
SchedulerTest("a-demote-then-b-migrate", "A demote then B stop. migrate B"),
SchedulerTest("probe-target-of-failed-migrate_to-1", "Failed migrate_to, target rejoins"),
SchedulerTest("probe-target-of-failed-migrate_to-2", "Failed migrate_to, target rejoined and probed"),
SchedulerTest("partial-live-migration-multiple-active", "Prevent running on multiple nodes due to partial live migration"),
SchedulerTest("migration-intermediary-cleaned",
"Probe live-migration intermediary with no history"),
SchedulerTest("bug-lf-2422", "Dependency on partially active group - stop ocfs:*"),
]),
SchedulerTestGroup([
SchedulerTest("clone-anon-probe-1", "Probe the correct (anonymous) clone instance for each node"),
SchedulerTest("clone-anon-probe-2", "Avoid needless re-probing of anonymous clones"),
SchedulerTest("clone-anon-failcount", "Merge failcounts for anonymous clones"),
SchedulerTest("force-anon-clone-max", "Update clone-max properly when forcing a clone to be anonymous"),
SchedulerTest("anon-instance-pending", "Assign anonymous clone instance numbers properly when action pending"),
SchedulerTest("inc0", "Incarnation start"),
SchedulerTest("inc1", "Incarnation start order"),
SchedulerTest("inc2", "Incarnation silent restart, stop, move"),
SchedulerTest("inc3", "Inter-incarnation ordering, silent restart, stop, move"),
SchedulerTest("inc4", "Inter-incarnation ordering, silent restart, stop, move (ordered)"),
SchedulerTest("inc5", "Inter-incarnation ordering, silent restart, stop, move (restart 1)"),
SchedulerTest("inc6", "Inter-incarnation ordering, silent restart, stop, move (restart 2)"),
SchedulerTest("inc7", "Clone colocation"),
SchedulerTest("inc8", "Clone anti-colocation"),
SchedulerTest("inc9", "Non-unique clone"),
SchedulerTest("inc10", "Non-unique clone (stop)"),
SchedulerTest("inc11", "Primitive colocation with clones"),
SchedulerTest("inc12", "Clone shutdown"),
SchedulerTest("cloned-group", "Make sure only the correct number of cloned groups are started"),
SchedulerTest("cloned-group-stop", "Ensure stopping qpidd also stops glance and cinder"),
SchedulerTest("clone-no-shuffle", "Don't prioritize allocation of instances that must be moved"),
SchedulerTest("clone-recover-no-shuffle-1",
"Don't shuffle instances when starting a new primitive instance"),
SchedulerTest("clone-recover-no-shuffle-2",
"Don't shuffle instances when starting a new group instance"),
SchedulerTest("clone-recover-no-shuffle-3",
"Don't shuffle instances when starting a new bundle instance"),
SchedulerTest("clone-recover-no-shuffle-4",
"Don't shuffle instances when starting a new primitive instance with "
"location preference "),
SchedulerTest("clone-recover-no-shuffle-5",
"Don't shuffle instances when starting a new group instance with "
"location preference"),
SchedulerTest("clone-recover-no-shuffle-6",
"Don't shuffle instances when starting a new bundle instance with "
"location preference"),
SchedulerTest("clone-recover-no-shuffle-7",
"Don't shuffle instances when starting a new primitive instance that "
"will be promoted"),
SchedulerTest("clone-recover-no-shuffle-8",
"Don't shuffle instances when starting a new group instance that "
"will be promoted "),
SchedulerTest("clone-recover-no-shuffle-9",
"Don't shuffle instances when starting a new bundle instance that "
"will be promoted "),
SchedulerTest("clone-recover-no-shuffle-10",
"Don't shuffle instances when starting a new primitive instance that "
"won't be promoted"),
SchedulerTest("clone-recover-no-shuffle-11",
"Don't shuffle instances when starting a new group instance that "
"won't be promoted "),
SchedulerTest("clone-recover-no-shuffle-12",
"Don't shuffle instances when starting a new bundle instance that "
"won't be promoted "),
SchedulerTest("clone-max-zero", "Orphan processing with clone-max=0"),
SchedulerTest("clone-anon-dup",
"Bug LF#2087 - Correctly parse the state of anonymous clones that are active more than once per node"),
SchedulerTest("bug-lf-2160", "Don't shuffle clones due to colocation"),
SchedulerTest("bug-lf-2213", "clone-node-max enforcement for cloned groups"),
SchedulerTest("bug-lf-2153", "Clone ordering constraints"),
SchedulerTest("bug-lf-2361", "Ensure clones observe mandatory ordering constraints if the LHS is unrunnable"),
SchedulerTest("bug-lf-2317", "Avoid needless restart of primitive depending on a clone"),
SchedulerTest("bug-lf-2453", "Enforce mandatory clone ordering without colocation"),
SchedulerTest("bug-lf-2508", "Correctly reconstruct the status of anonymous cloned groups"),
SchedulerTest("bug-lf-2544", "Balanced clone placement"),
SchedulerTest("bug-lf-2445", "Redistribute clones with node-max > 1 and stickiness = 0"),
SchedulerTest("bug-lf-2574", "Avoid clone shuffle"),
SchedulerTest("bug-lf-2581", "Avoid group restart due to unrelated clone (re)start"),
SchedulerTest("bug-cl-5168", "Don't shuffle clones"),
SchedulerTest("bug-cl-5170", "Prevent clone from starting with on-fail=block"),
SchedulerTest("clone-fail-block-colocation", "Move colocated group when failed clone has on-fail=block"),
SchedulerTest("clone-interleave-1",
"Clone-3 cannot start on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("clone-interleave-2", "Clone-3 must stop on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("clone-interleave-3",
"Clone-3 must be recovered on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("rebalance-unique-clones", "Rebalance unique clone instances with no stickiness"),
SchedulerTest("clone-requires-quorum-recovery", "Clone with requires=quorum on failed node needing recovery"),
SchedulerTest("clone-requires-quorum",
"Clone with requires=quorum with presumed-inactive instance on failed node"),
]),
SchedulerTestGroup([
SchedulerTest("cloned_start_one", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_start_two", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_stop_one", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_stop_two", "order first clone then clone... first clone_min=2"),
SchedulerTest("clone_min_interleave_start_one",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_start_two",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_stop_one",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_stop_two",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_start_one", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_start_two", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_all", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_one", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_two", "order first clone then primitive... first clone_min=2"),
]),
SchedulerTestGroup([
SchedulerTest("unfence-startup", "Clean unfencing"),
SchedulerTest("unfence-definition", "Unfencing when the agent changes"),
SchedulerTest("unfence-parameters", "Unfencing when the agent parameters changes"),
SchedulerTest("unfence-device", "Unfencing when a cluster has only fence devices"),
]),
SchedulerTestGroup([
SchedulerTest("promoted-0", "Stopped -> Unpromoted"),
SchedulerTest("promoted-1", "Stopped -> Promote"),
SchedulerTest("promoted-2", "Stopped -> Promote : notify"),
SchedulerTest("promoted-3", "Stopped -> Promote : promoted location"),
SchedulerTest("promoted-4", "Started -> Promote : promoted location"),
SchedulerTest("promoted-5", "Promoted -> Promoted"),
SchedulerTest("promoted-6", "Promoted -> Promoted (2)"),
SchedulerTest("promoted-7", "Promoted -> Fenced"),
SchedulerTest("promoted-8", "Promoted -> Fenced -> Moved"),
SchedulerTest("promoted-9", "Stopped + Promotable + No quorum"),
SchedulerTest("promoted-10", "Stopped -> Promotable : notify with monitor"),
SchedulerTest("promoted-11", "Stopped -> Promote : colocation"),
SchedulerTest("novell-239082", "Demote/Promote ordering"),
SchedulerTest("novell-239087", "Stable promoted placement"),
SchedulerTest("promoted-12", "Promotion based solely on rsc_location constraints"),
SchedulerTest("promoted-13", "Include preferences of colocated resources when placing promoted"),
SchedulerTest("promoted-demote", "Ordering when actions depends on demoting an unpromoted resource"),
SchedulerTest("promoted-ordering", "Prevent resources from starting that need a promoted"),
SchedulerTest("bug-1765", "Verify promoted-with-promoted colocation does not stop unpromoted instances"),
SchedulerTest("promoted-group", "Promotion of cloned groups"),
SchedulerTest("bug-lf-1852", "Don't shuffle promotable instances unnecessarily"),
SchedulerTest("promoted-failed-demote", "Don't retry failed demote actions"),
SchedulerTest("promoted-failed-demote-2", "Don't retry failed demote actions (notify=false)"),
SchedulerTest("promoted-depend",
"Ensure resources that depend on promoted instance don't get allocated until that does"),
SchedulerTest("promoted-reattach", "Re-attach to a running promoted"),
SchedulerTest("promoted-allow-start", "Don't include promoted score if it would prevent allocation"),
SchedulerTest("promoted-colocation",
"Allow promoted instances placemaker to be influenced by colocation constraints"),
SchedulerTest("promoted-pseudo", "Make sure promote/demote pseudo actions are created correctly"),
SchedulerTest("promoted-role", "Prevent target-role from promoting more than promoted-max instances"),
SchedulerTest("bug-lf-2358", "Anti-colocation of promoted instances"),
SchedulerTest("promoted-promotion-constraint", "Mandatory promoted colocation constraints"),
SchedulerTest("unmanaged-promoted", "Ensure role is preserved for unmanaged resources"),
SchedulerTest("promoted-unmanaged-monitor", "Start correct monitor for unmanaged promoted instances"),
SchedulerTest("promoted-demote-2", "Demote does not clear past failure"),
SchedulerTest("promoted-move", "Move promoted based on failure of colocated group"),
SchedulerTest("promoted-probed-score", "Observe the promotion score of probed resources"),
SchedulerTest("colocation_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by colocation constraint"),
SchedulerTest("colocation_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by colocation constraint"),
SchedulerTest("order_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by order constraint"),
SchedulerTest("order_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by order constraint"),
SchedulerTest("promoted_monitor_restart", "cl#5072 - Ensure promoted monitor operation will start after promotion"),
SchedulerTest("bug-rh-880249", "Handle replacement of an m/s resource with a primitive"),
SchedulerTest("bug-5143-ms-shuffle", "Prevent promoted instance shuffling due to promotion score"),
SchedulerTest("promoted-demote-block", "Block promotion if demote fails with on-fail=block"),
SchedulerTest("promoted-dependent-ban",
"Don't stop instances from being active because a dependent is banned from that host"),
SchedulerTest("promoted-stop", "Stop instances due to location constraint with role=Started"),
SchedulerTest("promoted-partially-demoted-group", "Allow partially demoted group to finish demoting"),
SchedulerTest("bug-cl-5213", "Ensure role colocation with -INFINITY is enforced"),
SchedulerTest("bug-cl-5219", "Allow unrelated resources with a common colocation target to remain promoted"),
SchedulerTest("promoted-asymmetrical-order",
"Fix the behaviors of multi-state resources with asymmetrical ordering"),
SchedulerTest("promoted-notify", "Promotion with notifications"),
SchedulerTest("promoted-score-startup", "Use permanent promoted scores without LRM history"),
SchedulerTest("failed-demote-recovery", "Recover resource in unpromoted role after demote fails"),
SchedulerTest("failed-demote-recovery-promoted", "Recover resource in promoted role after demote fails"),
SchedulerTest("on_fail_demote1", "Recovery with on-fail=\"demote\" on healthy cluster, remote, guest, and bundle nodes"),
SchedulerTest("on_fail_demote2", "Recovery with on-fail=\"demote\" with promotion on different node"),
SchedulerTest("on_fail_demote3", "Recovery with on-fail=\"demote\" with no promotion"),
SchedulerTest("on_fail_demote4", "Recovery with on-fail=\"demote\" on failed cluster, remote, guest, and bundle nodes"),
SchedulerTest("no_quorum_demote", "Promotable demotion and primitive stop with no-quorum-policy=\"demote\""),
SchedulerTest("no-promote-on-unrunnable-guest", "Don't select bundle instance for promotion when container can't run"),
SchedulerTest("leftover-pending-monitor", "Prevent a leftover pending monitor from causing unexpected stop of other instances"),
]),
SchedulerTestGroup([
SchedulerTest("history-1", "Correctly parse stateful-1 resource state"),
]),
SchedulerTestGroup([
SchedulerTest("managed-0", "Managed (reference)"),
SchedulerTest("managed-1", "Not managed - down"),
SchedulerTest("managed-2", "Not managed - up"),
SchedulerTest("bug-5028", "Shutdown should block if anything depends on an unmanaged resource"),
SchedulerTest("bug-5028-detach", "Ensure detach still works"),
SchedulerTest("bug-5028-bottom",
"Ensure shutdown still blocks if the blocked resource is at the bottom of the stack"),
SchedulerTest("unmanaged-stop-1",
"cl#5155 - Block the stop of resources if any depending resource is unmanaged"),
SchedulerTest("unmanaged-stop-2",
"cl#5155 - Block the stop of resources if the first resource in a mandatory stop order is unmanaged"),
SchedulerTest("unmanaged-stop-3",
"cl#5155 - Block the stop of resources if any depending resource in a group is unmanaged"),
SchedulerTest("unmanaged-stop-4",
"cl#5155 - Block the stop of resources if any depending resource in the middle of a group is unmanaged"),
SchedulerTest("unmanaged-block-restart",
"Block restart of resources if any dependent resource in a group is unmanaged"),
]),
SchedulerTestGroup([
SchedulerTest("interleave-0", "Interleave (reference)"),
SchedulerTest("interleave-1", "coloc - not interleaved"),
SchedulerTest("interleave-2", "coloc - interleaved"),
SchedulerTest("interleave-3", "coloc - interleaved (2)"),
SchedulerTest("interleave-pseudo-stop", "Interleaved clone during stonith"),
SchedulerTest("interleave-stop", "Interleaved clone during stop"),
SchedulerTest("interleave-restart", "Interleaved clone during dependency restart"),
]),
SchedulerTestGroup([
SchedulerTest("notify-0", "Notify reference"),
SchedulerTest("notify-1", "Notify simple"),
SchedulerTest("notify-2", "Notify simple, confirm"),
SchedulerTest("notify-3", "Notify move, confirm"),
SchedulerTest("novell-239079", "Notification priority"),
SchedulerTest("notifs-for-unrunnable", "Don't schedule notifications for an unrunnable action"),
SchedulerTest("route-remote-notify", "Route remote notify actions through correct cluster node"),
SchedulerTest("notify-behind-stopping-remote", "Don't schedule notifications behind stopped remote"),
]),
SchedulerTestGroup([
SchedulerTest("594", "OSDL #594 - Unrunnable actions scheduled in transition"),
SchedulerTest("662", "OSDL #662 - Two resources start on one node when incarnation_node_max = 1"),
SchedulerTest("696", "OSDL #696 - CRM starts stonith RA without monitor"),
SchedulerTest("726", "OSDL #726 - Attempting to schedule rsc_posic041_monitor_5000 _after_ a stop"),
SchedulerTest("735", "OSDL #735 - Correctly detect that rsc_hadev1 is stopped on hadev3"),
SchedulerTest("764", "OSDL #764 - Missing monitor op for DoFencing:child_DoFencing:1"),
SchedulerTest("797", "OSDL #797 - Assert triggered: task_id_i > max_call_id"),
SchedulerTest("829", "OSDL #829"),
SchedulerTest("994",
"OSDL #994 - Stopping the last resource in a resource group causes the entire group to be restarted"),
SchedulerTest("994-2", "OSDL #994 - with a dependent resource"),
SchedulerTest("1360", "OSDL #1360 - Clone stickiness"),
SchedulerTest("1484", "OSDL #1484 - on_fail=stop"),
SchedulerTest("1494", "OSDL #1494 - Clone stability"),
SchedulerTest("unrunnable-1", "Unrunnable"),
SchedulerTest("unrunnable-2", "Unrunnable 2"),
SchedulerTest("stonith-0", "Stonith loop - 1"),
SchedulerTest("stonith-1", "Stonith loop - 2"),
SchedulerTest("stonith-2", "Stonith loop - 3"),
SchedulerTest("stonith-3", "Stonith startup"),
SchedulerTest("stonith-4", "Stonith node state"),
SchedulerTest("dc-fence-ordering", "DC needs fencing while other nodes are shutting down"),
SchedulerTest("bug-1572-1", "Recovery of groups depending on promotable role"),
SchedulerTest("bug-1572-2", "Recovery of groups depending on promotable role when promoted is not re-promoted"),
SchedulerTest("bug-1685", "Depends-on-promoted ordering"),
SchedulerTest("bug-1822", "Don't promote partially active groups"),
SchedulerTest("bug-pm-11", "New resource added to a m/s group"),
SchedulerTest("bug-pm-12", "Recover only the failed portion of a cloned group"),
SchedulerTest("bug-n-387749", "Don't shuffle clone instances"),
SchedulerTest("bug-n-385265",
"Don't ignore the failure stickiness of group children - resource_idvscommon should stay stopped"),
SchedulerTest("bug-n-385265-2",
"Ensure groups are migrated instead of remaining partially active on the current node"),
SchedulerTest("bug-lf-1920", "Correctly handle probes that find active resources"),
SchedulerTest("bnc-515172", "Location constraint with multiple expressions"),
SchedulerTest("colocate-primitive-with-clone", "Optional colocation with a clone"),
SchedulerTest("use-after-free-merge", "Use-after-free in native_merge_weights"),
SchedulerTest("bug-lf-2551", "STONITH ordering for stop"),
SchedulerTest("bug-lf-2606", "Stonith implies demote"),
SchedulerTest("bug-lf-2474", "Ensure resource op timeout takes precedence over op_defaults"),
SchedulerTest("bug-suse-707150", "Prevent vm-01 from starting due to colocation/ordering"),
SchedulerTest("bug-5014-A-start-B-start", "Verify when A starts B starts using symmetrical=false"),
SchedulerTest("bug-5014-A-stop-B-started",
"Verify when A stops B does not stop if it has already started using symmetric=false"),
SchedulerTest("bug-5014-A-stopped-B-stopped",
"Verify when A is stopped and B has not started, B does not start before A using symmetric=false"),
SchedulerTest("bug-5014-CthenAthenB-C-stopped",
"Verify when C then A is symmetrical=true, A then B is symmetric=false, and C is stopped that nothing starts"),
SchedulerTest("bug-5014-CLONE-A-start-B-start",
"Verify when A starts B starts using clone resources with symmetric=false"),
SchedulerTest("bug-5014-CLONE-A-stop-B-started",
"Verify when A stops B does not stop if it has already started using clone resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-start-B-start",
"Verify when A starts B starts when using group resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-stopped-B-started",
"Verify when A stops B does not stop if it has already started using group resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-stopped-B-stopped",
"Verify when A is stopped and B has not started, B does not start before A using group resources with symmetric=false"),
SchedulerTest("bug-5014-ordered-set-symmetrical-false",
"Verify ordered sets work with symmetrical=false"),
SchedulerTest("bug-5014-ordered-set-symmetrical-true",
"Verify ordered sets work with symmetrical=true"),
SchedulerTest("clbz5007-promotable-colocation",
"Verify use of colocation scores other than INFINITY and -INFINITY work on multi-state resources"),
SchedulerTest("bug-5038", "Prevent restart of anonymous clones when clone-max decreases"),
SchedulerTest("bug-5025-1", "Automatically clean up failcount after resource config change with reload"),
SchedulerTest("bug-5025-2", "Make sure clear failcount action isn't set when config does not change"),
SchedulerTest("bug-5025-3", "Automatically clean up failcount after resource config change with restart"),
SchedulerTest("bug-5025-4", "Clear failcount when last failure is a start op and rsc attributes changed"),
SchedulerTest("failcount", "Ensure failcounts are correctly expired"),
SchedulerTest("failcount-block", "Ensure failcounts are not expired when on-fail=block is present"),
SchedulerTest("per-op-failcount", "Ensure per-operation failcount is handled and not passed to fence agent"),
SchedulerTest("on-fail-ignore", "Ensure on-fail=ignore works even beyond migration-threshold"),
SchedulerTest("monitor-onfail-restart", "bug-5058 - Monitor failure with on-fail set to restart"),
SchedulerTest("monitor-onfail-stop", "bug-5058 - Monitor failure wiht on-fail set to stop"),
SchedulerTest("bug-5059", "No need to restart p_stateful1:*"),
SchedulerTest("bug-5069-op-enabled", "Test on-fail=ignore with failure when monitor is enabled"),
SchedulerTest("bug-5069-op-disabled", "Test on-fail-ignore with failure when monitor is disabled"),
SchedulerTest("obsolete-lrm-resource", "cl#5115 - Do not use obsolete lrm_resource sections"),
SchedulerTest("expire-non-blocked-failure",
"Ignore failure-timeout only if the failed operation has on-fail=block"),
SchedulerTest("asymmetrical-order-move", "Respect asymmetrical ordering when trying to move resources"),
SchedulerTest("asymmetrical-order-restart", "Respect asymmetrical ordering when restarting dependent resource"),
SchedulerTest("start-then-stop-with-unfence", "Avoid graph loop with start-then-stop constraint plus unfencing"),
SchedulerTest("order-expired-failure", "Order failcount cleanup after remote fencing"),
SchedulerTest("expired-stop-1", "Expired stop failure should not block resource"),
SchedulerTest("ignore_stonith_rsc_order1",
"cl#5056- Ignore order constraint between stonith and non-stonith rsc"),
SchedulerTest("ignore_stonith_rsc_order2",
"cl#5056- Ignore order constraint with group rsc containing mixed stonith and non-stonith"),
SchedulerTest("ignore_stonith_rsc_order3", "cl#5056- Ignore order constraint, stonith clone and mixed group"),
SchedulerTest("ignore_stonith_rsc_order4",
"cl#5056- Ignore order constraint, stonith clone and clone with nested mixed group"),
SchedulerTest("honor_stonith_rsc_order1",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(single rsc)"),
SchedulerTest("honor_stonith_rsc_order2",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(multiple rsc)"),
SchedulerTest("honor_stonith_rsc_order3",
"cl#5056- Honor order constraint, stonith clones with nested pure stonith group"),
SchedulerTest("honor_stonith_rsc_order4",
"cl#5056- Honor order constraint, between two native stonith rscs"),
SchedulerTest("multiply-active-stonith", "Multiply active stonith"),
SchedulerTest("probe-timeout", "cl#5099 - Default probe timeout"),
SchedulerTest("order-first-probes",
"cl#5301 - respect order constraints when relevant resources are being probed"),
SchedulerTest("concurrent-fencing", "Allow performing fencing operations in parallel"),
SchedulerTest("priority-fencing-delay", "Delay fencing targeting the more significant node"),
SchedulerTest("pending-node-no-uname", "Do not fence a pending node that doesn't have an uname in node state yet"),
SchedulerTest("node-pending-timeout", "Fence a pending node that has reached `node-pending-timeout`"),
]),
SchedulerTestGroup([
SchedulerTest("systemhealth1", "System Health () #1"),
SchedulerTest("systemhealth2", "System Health () #2"),
SchedulerTest("systemhealth3", "System Health () #3"),
SchedulerTest("systemhealthn1", "System Health (None) #1"),
SchedulerTest("systemhealthn2", "System Health (None) #2"),
SchedulerTest("systemhealthn3", "System Health (None) #3"),
SchedulerTest("systemhealthm1", "System Health (Migrate On Red) #1"),
SchedulerTest("systemhealthm2", "System Health (Migrate On Red) #2"),
SchedulerTest("systemhealthm3", "System Health (Migrate On Red) #3"),
SchedulerTest("systemhealtho1", "System Health (Only Green) #1"),
SchedulerTest("systemhealtho2", "System Health (Only Green) #2"),
SchedulerTest("systemhealtho3", "System Health (Only Green) #3"),
SchedulerTest("systemhealthp1", "System Health (Progessive) #1"),
SchedulerTest("systemhealthp2", "System Health (Progessive) #2"),
SchedulerTest("systemhealthp3", "System Health (Progessive) #3"),
SchedulerTest("allow-unhealthy-nodes", "System Health (migrate-on-red + allow-unhealth-nodes)"),
]),
SchedulerTestGroup([
SchedulerTest("utilization", "Placement Strategy - utilization"),
SchedulerTest("minimal", "Placement Strategy - minimal"),
SchedulerTest("balanced", "Placement Strategy - balanced"),
]),
SchedulerTestGroup([
SchedulerTest("placement-stickiness", "Optimized Placement Strategy - stickiness"),
SchedulerTest("placement-priority", "Optimized Placement Strategy - priority"),
SchedulerTest("placement-location", "Optimized Placement Strategy - location"),
SchedulerTest("placement-capacity", "Optimized Placement Strategy - capacity"),
]),
SchedulerTestGroup([
SchedulerTest("utilization-order1", "Utilization Order - Simple"),
SchedulerTest("utilization-order2", "Utilization Order - Complex"),
SchedulerTest("utilization-order3", "Utilization Order - Migrate"),
SchedulerTest("utilization-order4", "Utilization Order - Live Migration (bnc#695440)"),
SchedulerTest("utilization-complex", "Utilization with complex relationships"),
SchedulerTest("utilization-shuffle",
"Don't displace prmExPostgreSQLDB2 on act2, Start prmExPostgreSQLDB1 on act3"),
SchedulerTest("load-stopped-loop", "Avoid transition loop due to load_stopped (cl#5044)"),
SchedulerTest("load-stopped-loop-2",
"cl#5235 - Prevent graph loops that can be introduced by load_stopped -> migrate_to ordering"),
]),
SchedulerTestGroup([
SchedulerTest("colocated-utilization-primitive-1", "Colocated Utilization - Primitive"),
SchedulerTest("colocated-utilization-primitive-2", "Colocated Utilization - Choose the most capable node"),
SchedulerTest("colocated-utilization-group", "Colocated Utilization - Group"),
SchedulerTest("colocated-utilization-clone", "Colocated Utilization - Clone"),
SchedulerTest("utilization-check-allowed-nodes",
"Only check the capacities of the nodes that can run the resource"),
]),
SchedulerTestGroup([
SchedulerTest("node-maintenance-1", "cl#5128 - Node maintenance"),
SchedulerTest("node-maintenance-2", "cl#5128 - Node maintenance (coming out of maintenance mode)"),
SchedulerTest("shutdown-maintenance-node", "Do not fence a maintenance node if it shuts down cleanly"),
SchedulerTest("rsc-maintenance", "Per-resource maintenance"),
]),
SchedulerTestGroup([
SchedulerTest("not-installed-agent", "The resource agent is missing"),
SchedulerTest("not-installed-tools", "Something the resource agent needs is missing"),
]),
SchedulerTestGroup([
SchedulerTest("stopped-monitor-00", "Stopped Monitor - initial start"),
SchedulerTest("stopped-monitor-01", "Stopped Monitor - failed started"),
SchedulerTest("stopped-monitor-02", "Stopped Monitor - started multi-up"),
SchedulerTest("stopped-monitor-03", "Stopped Monitor - stop started"),
SchedulerTest("stopped-monitor-04", "Stopped Monitor - failed stop"),
SchedulerTest("stopped-monitor-05", "Stopped Monitor - start unmanaged"),
SchedulerTest("stopped-monitor-06", "Stopped Monitor - unmanaged multi-up"),
SchedulerTest("stopped-monitor-07", "Stopped Monitor - start unmanaged multi-up"),
SchedulerTest("stopped-monitor-08", "Stopped Monitor - migrate"),
SchedulerTest("stopped-monitor-09", "Stopped Monitor - unmanage started"),
SchedulerTest("stopped-monitor-10", "Stopped Monitor - unmanaged started multi-up"),
SchedulerTest("stopped-monitor-11", "Stopped Monitor - stop unmanaged started"),
SchedulerTest("stopped-monitor-12", "Stopped Monitor - unmanaged started multi-up (target-role=Stopped)"),
SchedulerTest("stopped-monitor-20", "Stopped Monitor - initial stop"),
SchedulerTest("stopped-monitor-21", "Stopped Monitor - stopped single-up"),
SchedulerTest("stopped-monitor-22", "Stopped Monitor - stopped multi-up"),
SchedulerTest("stopped-monitor-23", "Stopped Monitor - start stopped"),
SchedulerTest("stopped-monitor-24", "Stopped Monitor - unmanage stopped"),
SchedulerTest("stopped-monitor-25", "Stopped Monitor - unmanaged stopped multi-up"),
SchedulerTest("stopped-monitor-26", "Stopped Monitor - start unmanaged stopped"),
SchedulerTest("stopped-monitor-27", "Stopped Monitor - unmanaged stopped multi-up (target-role=Started)"),
SchedulerTest("stopped-monitor-30", "Stopped Monitor - new node started"),
SchedulerTest("stopped-monitor-31", "Stopped Monitor - new node stopped"),
]),
SchedulerTestGroup([
# This is a combo test to check:
# - probe timeout defaults to the minimum-interval monitor's
# - duplicate recurring operations are ignored
# - if timeout spec is bad, the default timeout is used
# - failure is blocked with on-fail=block even if ISO8601 interval is specified
# - started/stopped role monitors are started/stopped on right nodes
SchedulerTest("intervals", "Recurring monitor interval handling"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-primitive-1", "Ticket - Primitive (loss-policy=stop, initial)"),
SchedulerTest("ticket-primitive-2", "Ticket - Primitive (loss-policy=stop, granted)"),
SchedulerTest("ticket-primitive-3", "Ticket - Primitive (loss-policy-stop, revoked)"),
SchedulerTest("ticket-primitive-4", "Ticket - Primitive (loss-policy=demote, initial)"),
SchedulerTest("ticket-primitive-5", "Ticket - Primitive (loss-policy=demote, granted)"),
SchedulerTest("ticket-primitive-6", "Ticket - Primitive (loss-policy=demote, revoked)"),
SchedulerTest("ticket-primitive-7", "Ticket - Primitive (loss-policy=fence, initial)"),
SchedulerTest("ticket-primitive-8", "Ticket - Primitive (loss-policy=fence, granted)"),
SchedulerTest("ticket-primitive-9", "Ticket - Primitive (loss-policy=fence, revoked)"),
SchedulerTest("ticket-primitive-10", "Ticket - Primitive (loss-policy=freeze, initial)"),
SchedulerTest("ticket-primitive-11", "Ticket - Primitive (loss-policy=freeze, granted)"),
SchedulerTest("ticket-primitive-12", "Ticket - Primitive (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-primitive-13", "Ticket - Primitive (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-primitive-14", "Ticket - Primitive (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-primitive-15", "Ticket - Primitive (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-primitive-16", "Ticket - Primitive (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-primitive-17", "Ticket - Primitive (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-primitive-18", "Ticket - Primitive (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-primitive-19", "Ticket - Primitive (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-primitive-20", "Ticket - Primitive (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-primitive-21", "Ticket - Primitive (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-primitive-22", "Ticket - Primitive (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-primitive-23", "Ticket - Primitive (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-primitive-24", "Ticket - Primitive (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-group-1", "Ticket - Group (loss-policy=stop, initial)"),
SchedulerTest("ticket-group-2", "Ticket - Group (loss-policy=stop, granted)"),
SchedulerTest("ticket-group-3", "Ticket - Group (loss-policy-stop, revoked)"),
SchedulerTest("ticket-group-4", "Ticket - Group (loss-policy=demote, initial)"),
SchedulerTest("ticket-group-5", "Ticket - Group (loss-policy=demote, granted)"),
SchedulerTest("ticket-group-6", "Ticket - Group (loss-policy=demote, revoked)"),
SchedulerTest("ticket-group-7", "Ticket - Group (loss-policy=fence, initial)"),
SchedulerTest("ticket-group-8", "Ticket - Group (loss-policy=fence, granted)"),
SchedulerTest("ticket-group-9", "Ticket - Group (loss-policy=fence, revoked)"),
SchedulerTest("ticket-group-10", "Ticket - Group (loss-policy=freeze, initial)"),
SchedulerTest("ticket-group-11", "Ticket - Group (loss-policy=freeze, granted)"),
SchedulerTest("ticket-group-12", "Ticket - Group (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-group-13", "Ticket - Group (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-group-14", "Ticket - Group (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-group-15", "Ticket - Group (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-group-16", "Ticket - Group (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-group-17", "Ticket - Group (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-group-18", "Ticket - Group (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-group-19", "Ticket - Group (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-group-20", "Ticket - Group (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-group-21", "Ticket - Group (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-group-22", "Ticket - Group (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-group-23", "Ticket - Group (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-group-24", "Ticket - Group (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-clone-1", "Ticket - Clone (loss-policy=stop, initial)"),
SchedulerTest("ticket-clone-2", "Ticket - Clone (loss-policy=stop, granted)"),
SchedulerTest("ticket-clone-3", "Ticket - Clone (loss-policy-stop, revoked)"),
SchedulerTest("ticket-clone-4", "Ticket - Clone (loss-policy=demote, initial)"),
SchedulerTest("ticket-clone-5", "Ticket - Clone (loss-policy=demote, granted)"),
SchedulerTest("ticket-clone-6", "Ticket - Clone (loss-policy=demote, revoked)"),
SchedulerTest("ticket-clone-7", "Ticket - Clone (loss-policy=fence, initial)"),
SchedulerTest("ticket-clone-8", "Ticket - Clone (loss-policy=fence, granted)"),
SchedulerTest("ticket-clone-9", "Ticket - Clone (loss-policy=fence, revoked)"),
SchedulerTest("ticket-clone-10", "Ticket - Clone (loss-policy=freeze, initial)"),
SchedulerTest("ticket-clone-11", "Ticket - Clone (loss-policy=freeze, granted)"),
SchedulerTest("ticket-clone-12", "Ticket - Clone (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-clone-13", "Ticket - Clone (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-clone-14", "Ticket - Clone (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-clone-15", "Ticket - Clone (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-clone-16", "Ticket - Clone (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-clone-17", "Ticket - Clone (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-clone-18", "Ticket - Clone (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-clone-19", "Ticket - Clone (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-clone-20", "Ticket - Clone (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-clone-21", "Ticket - Clone (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-clone-22", "Ticket - Clone (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-clone-23", "Ticket - Clone (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-clone-24", "Ticket - Clone (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-promoted-1", "Ticket - Promoted (loss-policy=stop, initial)"),
SchedulerTest("ticket-promoted-2", "Ticket - Promoted (loss-policy=stop, granted)"),
SchedulerTest("ticket-promoted-3", "Ticket - Promoted (loss-policy-stop, revoked)"),
SchedulerTest("ticket-promoted-4", "Ticket - Promoted (loss-policy=demote, initial)"),
SchedulerTest("ticket-promoted-5", "Ticket - Promoted (loss-policy=demote, granted)"),
SchedulerTest("ticket-promoted-6", "Ticket - Promoted (loss-policy=demote, revoked)"),
SchedulerTest("ticket-promoted-7", "Ticket - Promoted (loss-policy=fence, initial)"),
SchedulerTest("ticket-promoted-8", "Ticket - Promoted (loss-policy=fence, granted)"),
SchedulerTest("ticket-promoted-9", "Ticket - Promoted (loss-policy=fence, revoked)"),
SchedulerTest("ticket-promoted-10", "Ticket - Promoted (loss-policy=freeze, initial)"),
SchedulerTest("ticket-promoted-11", "Ticket - Promoted (loss-policy=freeze, granted)"),
SchedulerTest("ticket-promoted-12", "Ticket - Promoted (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-promoted-13", "Ticket - Promoted (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-promoted-14", "Ticket - Promoted (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-promoted-15", "Ticket - Promoted (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-promoted-16", "Ticket - Promoted (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-promoted-17", "Ticket - Promoted (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-promoted-18", "Ticket - Promoted (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-promoted-19", "Ticket - Promoted (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-promoted-20", "Ticket - Promoted (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-promoted-21", "Ticket - Promoted (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-promoted-22", "Ticket - Promoted (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-promoted-23", "Ticket - Promoted (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-promoted-24", "Ticket - Promoted (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-rsc-sets-1", "Ticket - Resource sets (1 ticket, initial)"),
SchedulerTest("ticket-rsc-sets-2", "Ticket - Resource sets (1 ticket, granted)"),
SchedulerTest("ticket-rsc-sets-3", "Ticket - Resource sets (1 ticket, revoked)"),
SchedulerTest("ticket-rsc-sets-4", "Ticket - Resource sets (2 tickets, initial)"),
SchedulerTest("ticket-rsc-sets-5", "Ticket - Resource sets (2 tickets, granted)"),
SchedulerTest("ticket-rsc-sets-6", "Ticket - Resource sets (2 tickets, granted)"),
SchedulerTest("ticket-rsc-sets-7", "Ticket - Resource sets (2 tickets, revoked)"),
SchedulerTest("ticket-rsc-sets-8", "Ticket - Resource sets (1 ticket, standby, granted)"),
SchedulerTest("ticket-rsc-sets-9", "Ticket - Resource sets (1 ticket, granted, standby)"),
SchedulerTest("ticket-rsc-sets-10", "Ticket - Resource sets (1 ticket, standby, revoked)"),
SchedulerTest("ticket-rsc-sets-11", "Ticket - Resource sets (2 tickets, standby, granted)"),
SchedulerTest("ticket-rsc-sets-12", "Ticket - Resource sets (2 tickets, standby, granted)"),
SchedulerTest("ticket-rsc-sets-13", "Ticket - Resource sets (2 tickets, granted, standby)"),
SchedulerTest("ticket-rsc-sets-14", "Ticket - Resource sets (2 tickets, standby, revoked)"),
SchedulerTest("cluster-specific-params", "Cluster-specific instance attributes based on rules"),
SchedulerTest("site-specific-params", "Site-specific instance attributes based on rules"),
]),
SchedulerTestGroup([
SchedulerTest("template-1", "Template - 1"),
SchedulerTest("template-2", "Template - 2"),
SchedulerTest("template-3", "Template - 3 (merge operations)"),
SchedulerTest("template-coloc-1", "Template - Colocation 1"),
SchedulerTest("template-coloc-2", "Template - Colocation 2"),
SchedulerTest("template-coloc-3", "Template - Colocation 3"),
SchedulerTest("template-order-1", "Template - Order 1"),
SchedulerTest("template-order-2", "Template - Order 2"),
SchedulerTest("template-order-3", "Template - Order 3"),
SchedulerTest("template-ticket", "Template - Ticket"),
SchedulerTest("template-rsc-sets-1", "Template - Resource Sets 1"),
SchedulerTest("template-rsc-sets-2", "Template - Resource Sets 2"),
SchedulerTest("template-rsc-sets-3", "Template - Resource Sets 3"),
SchedulerTest("template-rsc-sets-4", "Template - Resource Sets 4"),
SchedulerTest("template-clone-primitive", "Cloned primitive from template"),
SchedulerTest("template-clone-group", "Cloned group from template"),
SchedulerTest("location-sets-templates", "Resource sets and templates - Location"),
SchedulerTest("tags-coloc-order-1", "Tags - Colocation and Order (Simple)"),
SchedulerTest("tags-coloc-order-2", "Tags - Colocation and Order (Resource Sets with Templates)"),
SchedulerTest("tags-location", "Tags - Location"),
SchedulerTest("tags-ticket", "Tags - Ticket"),
]),
SchedulerTestGroup([
SchedulerTest("container-1", "Container - initial"),
SchedulerTest("container-2", "Container - monitor failed"),
SchedulerTest("container-3", "Container - stop failed"),
SchedulerTest("container-4", "Container - reached migration-threshold"),
SchedulerTest("container-group-1", "Container in group - initial"),
SchedulerTest("container-group-2", "Container in group - monitor failed"),
SchedulerTest("container-group-3", "Container in group - stop failed"),
SchedulerTest("container-group-4", "Container in group - reached migration-threshold"),
SchedulerTest("container-is-remote-node", "Place resource within container when container is remote-node"),
SchedulerTest("bug-rh-1097457", "Kill user defined container/contents ordering"),
SchedulerTest("bug-cl-5247", "Graph loop when recovering m/s resource in a container"),
SchedulerTest("bundle-order-startup", "Bundle startup ordering"),
SchedulerTest("bundle-order-partial-start",
"Bundle startup ordering when some dependencies are already running"),
SchedulerTest("bundle-order-partial-start-2",
"Bundle startup ordering when some dependencies and the container are already running"),
SchedulerTest("bundle-order-stop", "Bundle stop ordering"),
SchedulerTest("bundle-order-partial-stop", "Bundle startup ordering when some dependencies are already stopped"),
SchedulerTest("bundle-order-stop-on-remote", "Stop nested resource after bringing up the connection"),
SchedulerTest("bundle-order-startup-clone", "Prevent startup because bundle isn't promoted"),
SchedulerTest("bundle-order-startup-clone-2", "Bundle startup with clones"),
SchedulerTest("bundle-order-stop-clone", "Stop bundle because clone is stopping"),
SchedulerTest("bundle-interleave-start", "Interleave bundle starts"),
SchedulerTest("bundle-interleave-promote", "Interleave bundle promotes"),
SchedulerTest("bundle-nested-colocation", "Colocation of nested connection resources"),
SchedulerTest("bundle-order-fencing",
"Order pseudo bundle fencing after parent node fencing if both are happening"),
SchedulerTest("bundle-probe-order-1", "order 1"),
SchedulerTest("bundle-probe-order-2", "order 2"),
SchedulerTest("bundle-probe-order-3", "order 3"),
SchedulerTest("bundle-probe-remotes", "Ensure remotes get probed too"),
SchedulerTest("bundle-replicas-change", "Change bundle from 1 replica to multiple"),
SchedulerTest("bundle-connection-with-container", "Don't move a container due to connection preferences"),
SchedulerTest("nested-remote-recovery", "Recover bundle's container hosted on remote node"),
SchedulerTest("bundle-promoted-location-1",
"Promotable bundle, positive location"),
SchedulerTest("bundle-promoted-location-2",
"Promotable bundle, negative location"),
SchedulerTest("bundle-promoted-location-3",
"Promotable bundle, positive location for promoted role"),
SchedulerTest("bundle-promoted-location-4",
"Promotable bundle, negative location for promoted role"),
SchedulerTest("bundle-promoted-location-5",
"Promotable bundle, positive location for unpromoted role"),
SchedulerTest("bundle-promoted-location-6",
"Promotable bundle, negative location for unpromoted role"),
SchedulerTest("bundle-promoted-colocation-1",
"Primary promoted bundle, dependent primitive (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-2",
"Primary promoted bundle, dependent primitive (optional coloc)"),
SchedulerTest("bundle-promoted-colocation-3",
"Dependent promoted bundle, primary primitive (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-4",
"Dependent promoted bundle, primary primitive (optional coloc)"),
SchedulerTest("bundle-promoted-colocation-5",
"Primary and dependent promoted bundle instances (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-6",
"Primary and dependent promoted bundle instances (optional coloc)"),
SchedulerTest("bundle-promoted-anticolocation-1",
"Primary promoted bundle, dependent primitive (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-2",
"Primary promoted bundle, dependent primitive (optional anti)"),
SchedulerTest("bundle-promoted-anticolocation-3",
"Dependent promoted bundle, primary primitive (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-4",
"Dependent promoted bundle, primary primitive (optional anti)"),
SchedulerTest("bundle-promoted-anticolocation-5",
"Primary and dependent promoted bundle instances (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-6",
"Primary and dependent promoted bundle instances (optional anti)"),
]),
SchedulerTestGroup([
SchedulerTest("whitebox-fail1", "Fail whitebox container rsc"),
SchedulerTest("whitebox-fail2", "Fail cluster connection to guest node"),
SchedulerTest("whitebox-fail3", "Failed containers should not run nested on remote nodes"),
SchedulerTest("whitebox-start", "Start whitebox container with resources assigned to it"),
SchedulerTest("whitebox-stop", "Stop whitebox container with resources assigned to it"),
SchedulerTest("whitebox-move", "Move whitebox container with resources assigned to it"),
SchedulerTest("whitebox-asymmetric", "Verify connection rsc opts-in based on container resource"),
SchedulerTest("whitebox-ms-ordering", "Verify promote/demote can not occur before connection is established"),
SchedulerTest("whitebox-ms-ordering-move", "Stop/Start cycle within a moving container"),
SchedulerTest("whitebox-orphaned", "Properly shutdown orphaned whitebox container"),
SchedulerTest("whitebox-orphan-ms", "Properly tear down orphan ms resources on remote-nodes"),
SchedulerTest("whitebox-unexpectedly-running", "Recover container nodes the cluster did not start"),
SchedulerTest("whitebox-migrate1", "Migrate both container and connection resource"),
SchedulerTest("whitebox-imply-stop-on-fence",
"imply stop action on container node rsc when host node is fenced"),
SchedulerTest("whitebox-nested-group", "Verify guest remote-node works nested in a group"),
SchedulerTest("guest-node-host-dies", "Verify guest node is recovered if host goes away"),
SchedulerTest("guest-node-cleanup", "Order guest node connection recovery after container probe"),
SchedulerTest("guest-host-not-fenceable", "Actions on guest node are unrunnable if host is unclean and cannot be fenced"),
]),
SchedulerTestGroup([
SchedulerTest("remote-startup-probes", "Baremetal remote-node startup probes"),
SchedulerTest("remote-startup", "Startup a newly discovered remote-nodes with no status"),
SchedulerTest("remote-fence-unclean", "Fence unclean baremetal remote-node"),
SchedulerTest("remote-fence-unclean2",
"Fence baremetal remote-node after cluster node fails and connection can not be recovered"),
SchedulerTest("remote-fence-unclean-3", "Probe failed remote nodes (triggers fencing)"),
SchedulerTest("remote-move", "Move remote-node connection resource"),
SchedulerTest("remote-disable", "Disable a baremetal remote-node"),
SchedulerTest("remote-probe-disable", "Probe then stop a baremetal remote-node"),
SchedulerTest("remote-orphaned", "Properly shutdown orphaned connection resource"),
SchedulerTest("remote-orphaned2",
"verify we can handle orphaned remote connections with active resources on the remote"),
SchedulerTest("remote-recover", "Recover connection resource after cluster-node fails"),
SchedulerTest("remote-stale-node-entry",
"Make sure we properly handle leftover remote-node entries in the node section"),
SchedulerTest("remote-partial-migrate",
"Make sure partial migrations are handled before ops on the remote node"),
SchedulerTest("remote-partial-migrate2",
"Make sure partial migration target is prefered for remote connection"),
SchedulerTest("remote-recover-fail", "Make sure start failure causes fencing if rsc are active on remote"),
SchedulerTest("remote-start-fail",
"Make sure a start failure does not result in fencing if no active resources are on remote"),
SchedulerTest("remote-unclean2",
"Make monitor failure always results in fencing, even if no rsc are active on remote"),
SchedulerTest("remote-fence-before-reconnect", "Fence before clearing recurring monitor failure"),
SchedulerTest("remote-recovery", "Recover remote connections before attempting demotion"),
SchedulerTest("remote-recover-connection", "Optimistically recovery of only the connection"),
SchedulerTest("remote-recover-all", "Fencing when the connection has no home"),
SchedulerTest("remote-recover-no-resources", "Fencing when the connection has no home and no active resources"),
SchedulerTest("remote-recover-unknown",
"Fencing when the connection has no home and the remote has no operation history"),
SchedulerTest("remote-reconnect-delay", "Waiting for remote reconnect interval to expire"),
SchedulerTest("remote-connection-unrecoverable",
"Remote connection host must be fenced, with connection unrecoverable"),
SchedulerTest("remote-connection-shutdown", "Remote connection shutdown"),
SchedulerTest("cancel-behind-moving-remote",
"Route recurring monitor cancellations through original node of a moving remote connection"),
]),
SchedulerTestGroup([
SchedulerTest("resource-discovery", "Exercises resource-discovery location constraint option"),
SchedulerTest("rsc-discovery-per-node", "Disable resource discovery per node"),
SchedulerTest("shutdown-lock", "Ensure shutdown lock works properly"),
SchedulerTest("shutdown-lock-expiration", "Ensure shutdown lock expiration works properly"),
]),
SchedulerTestGroup([
SchedulerTest("op-defaults", "Test op_defaults conditional expressions"),
SchedulerTest("op-defaults-2", "Test op_defaults AND'ed conditional expressions"),
SchedulerTest("op-defaults-3", "Test op_defaults precedence"),
SchedulerTest("rsc-defaults", "Test rsc_defaults conditional expressions"),
SchedulerTest("rsc-defaults-2", "Test rsc_defaults conditional expressions without type"),
]),
SchedulerTestGroup([
SchedulerTest("stop-all-resources", "Test stop-all-resources=true "),
]),
SchedulerTestGroup([
SchedulerTest("ocf_degraded-remap-ocf_ok", "Test degraded remapped to OK"),
SchedulerTest("ocf_degraded_promoted-remap-ocf_ok", "Test degraded promoted remapped to OK"),
]),
]
TESTS_64BIT = [
SchedulerTestGroup([
SchedulerTest("year-2038", "Check handling of timestamps beyond 2038-01-19 03:14:08 UTC"),
]),
]
def is_executable(path):
""" Check whether a file at a given path is executable. """
try:
return os.stat(path)[stat.ST_MODE] & stat.S_IXUSR
except OSError:
return False
def diff(file1, file2, **kwargs):
""" Call diff on two files """
return subprocess.call([ "diff", "-u", "-N", "--ignore-all-space",
"--ignore-blank-lines", file1, file2 ], **kwargs)
def sort_file(filename):
""" Sort a file alphabetically """
with io.open(filename, "rt") as f:
lines = sorted(f)
with io.open(filename, "wt") as f:
f.writelines(lines)
def remove_files(filenames):
""" Remove a list of files """
for filename in filenames:
try:
os.remove(filename)
except OSError:
pass
def normalize(filename):
""" Remove text from a file that isn't important for comparison """
if not hasattr(normalize, "patterns"):
normalize.patterns = [
re.compile(r'crm_feature_set="[^"]*"'),
re.compile(r'batch-limit="[0-9]*"')
]
if os.path.isfile(filename):
with io.open(filename, "rt") as f:
lines = f.readlines()
with io.open(filename, "wt") as f:
for line in lines:
for pattern in normalize.patterns:
line = pattern.sub("", line)
f.write(line)
def cat(filename, dest=sys.stdout):
""" Copy a file to a destination file descriptor """
with io.open(filename, "rt") as f:
shutil.copyfileobj(f, dest)
class CtsScheduler(object):
""" Regression tests for Pacemaker's scheduler """
def _parse_args(self, argv):
""" Parse command-line arguments """
parser = argparse.ArgumentParser(description=DESC)
parser.add_argument('-V', '--verbose', action='count',
help='Display any differences from expected output')
parser.add_argument('--run', metavar='TEST',
help=('Run only single specified test (any further '
'arguments will be passed to crm_simulate)'))
parser.add_argument('--update', action='store_true',
help='Update expected results with actual results')
parser.add_argument('-b', '--binary', metavar='PATH',
help='Specify path to crm_simulate')
parser.add_argument('-i', '--io-dir', metavar='PATH',
help='Specify path to regression test data directory')
parser.add_argument('-o', '--out-dir', metavar='PATH',
help='Specify where intermediate and output files should go')
parser.add_argument('-v', '--valgrind', action='store_true',
help='Run all commands under valgrind')
parser.add_argument('--valgrind-dhat', action='store_true',
help='Run all commands under valgrind with heap analyzer')
parser.add_argument('--valgrind-skip-output', action='store_true',
help='If running under valgrind, do not display output')
parser.add_argument('--testcmd-options', metavar='OPTIONS', default='',
help='Additional options for command under test')
# argparse can't handle "everything after --run TEST", so grab that
self.single_test_args = []
narg = 0
for arg in argv:
narg = narg + 1
if arg == '--run':
(argv, self.single_test_args) = (argv[:narg+1], argv[narg+1:])
break
self.args = parser.parse_args(argv[1:])
def _error(self, s):
print(" * ERROR: %s" % s)
def _failed(self, s):
print(" * FAILED: %s" % s)
def _get_valgrind_cmd(self):
""" Return command arguments needed (or not) to run valgrind """
if self.args.valgrind:
os.environ['G_SLICE'] = "always-malloc"
return [
"valgrind",
"-q",
"--gen-suppressions=all",
"--time-stamp=yes",
"--trace-children=no",
"--show-reachable=no",
"--leak-check=full",
"--num-callers=20",
"--suppressions=%s/valgrind-pcmk.suppressions" % (self.test_home)
]
if self.args.valgrind_dhat:
os.environ['G_SLICE'] = "always-malloc"
return [
"valgrind",
"--tool=exp-dhat",
"--time-stamp=yes",
"--trace-children=no",
"--show-top-n=100",
"--num-callers=4"
]
return []
def _get_simulator_cmd(self):
""" Locate the simulation binary """
if self.args.binary is None:
self.args.binary = BuildOptions._BUILD_DIR + "/tools/crm_simulate"
if not is_executable(self.args.binary):
self.args.binary = BuildOptions.SBIN_DIR + "/crm_simulate"
if not is_executable(self.args.binary):
# @TODO it would be more pythonic to raise an exception
self._error("Test binary " + self.args.binary + " not found")
sys.exit(ExitStatus.NOT_INSTALLED)
return [ self.args.binary ] + shlex.split(self.args.testcmd_options)
def set_schema_env(self):
""" Ensure schema directory environment variable is set, if possible """
try:
return os.environ['PCMK_schema_directory']
except KeyError:
for d in [ os.path.join(BuildOptions._BUILD_DIR, "xml"),
BuildOptions.SCHEMA_DIR ]:
if os.path.isdir(d):
os.environ['PCMK_schema_directory'] = d
return d
return None
def __init__(self, argv=sys.argv):
# Ensure all command output is in portable locale for comparison
os.environ['LC_ALL'] = "C"
self._parse_args(argv)
# Where this executable lives
self.test_home = os.path.dirname(os.path.realpath(argv[0]))
# Where test data resides
if self.args.io_dir is None:
self.args.io_dir = os.path.join(self.test_home, "scheduler")
self.xml_input_dir = os.path.join(self.args.io_dir, "xml")
self.expected_dir = os.path.join(self.args.io_dir, "exp")
self.dot_expected_dir = os.path.join(self.args.io_dir, "dot")
self.scores_dir = os.path.join(self.args.io_dir, "scores")
self.summary_dir = os.path.join(self.args.io_dir, "summary")
self.stderr_expected_dir = os.path.join(self.args.io_dir, "stderr")
# Create a temporary directory to store diff file
self.failed_dir = tempfile.mkdtemp(prefix='cts-scheduler_')
# Where to store generated files
if self.args.out_dir is None:
self.args.out_dir = self.args.io_dir
self.failed_filename = os.path.join(self.failed_dir, "test-output.diff")
else:
self.failed_filename = os.path.join(self.args.out_dir, "test-output.diff")
os.environ['CIB_shadow_dir'] = self.args.out_dir
self.failed_file = None
self.outfile_out_dir = os.path.join(self.args.out_dir, "out")
self.dot_out_dir = os.path.join(self.args.out_dir, "dot")
self.scores_out_dir = os.path.join(self.args.out_dir, "scores")
self.summary_out_dir = os.path.join(self.args.out_dir, "summary")
self.stderr_out_dir = os.path.join(self.args.out_dir, "stderr")
self.valgrind_out_dir = os.path.join(self.args.out_dir, "valgrind")
# Single test mode (if requested)
try:
# User can give test base name or file name of a test input
self.args.run = os.path.splitext(os.path.basename(self.args.run))[0]
except (AttributeError, TypeError):
pass # --run was not specified
self.set_schema_env()
# Arguments needed (or not) to run commands
self.valgrind_args = self._get_valgrind_cmd()
self.simulate_args = self._get_simulator_cmd()
# Test counters
self.num_failed = 0
self.num_tests = 0
# Ensure that the main output directory exists
# We don't want to create it with os.makedirs below
if not os.path.isdir(self.args.out_dir):
self._error("Output directory missing; can't create output files")
sys.exit(ExitStatus.CANTCREAT)
# Create output subdirectories if they don't exist
try:
os.makedirs(self.outfile_out_dir, 0o755, True)
os.makedirs(self.dot_out_dir, 0o755, True)
os.makedirs(self.scores_out_dir, 0o755, True)
os.makedirs(self.summary_out_dir, 0o755, True)
os.makedirs(self.stderr_out_dir, 0o755, True)
if self.valgrind_args:
os.makedirs(self.valgrind_out_dir, 0o755, True)
except OSError as ex:
self._error("Unable to create output subdirectory: %s" % ex)
remove_files([
self.outfile_out_dir,
self.dot_out_dir,
self.scores_out_dir,
self.summary_out_dir,
self.stderr_out_dir,
])
sys.exit(ExitStatus.CANTCREAT)
def _compare_files(self, filename1, filename2):
""" Add any file differences to failed results """
if diff(filename1, filename2, stdout=subprocess.DEVNULL) != 0:
diff(filename1, filename2, stdout=self.failed_file, stderr=subprocess.DEVNULL)
self.failed_file.write("\n")
return True
return False
def run_one(self, test_name, test_desc, test_args):
""" Run one scheduler test """
print(" Test %-41s %s" % ((test_name + ":"), test_desc))
did_fail = False
self.num_tests = self.num_tests + 1
# Test inputs
input_filename = os.path.join(
self.xml_input_dir, "%s.xml" % test_name)
expected_filename = os.path.join(
self.expected_dir, "%s.exp" % test_name)
dot_expected_filename = os.path.join(
self.dot_expected_dir, "%s.dot" % test_name)
scores_filename = os.path.join(
self.scores_dir, "%s.scores" % test_name)
summary_filename = os.path.join(
self.summary_dir, "%s.summary" % test_name)
stderr_expected_filename = os.path.join(
self.stderr_expected_dir, "%s.stderr" % test_name)
# (Intermediate) test outputs
output_filename = os.path.join(
self.outfile_out_dir, "%s.out" % test_name)
dot_output_filename = os.path.join(
self.dot_out_dir, "%s.dot.pe" % test_name)
score_output_filename = os.path.join(
self.scores_out_dir, "%s.scores.pe" % test_name)
summary_output_filename = os.path.join(
self.summary_out_dir, "%s.summary.pe" % test_name)
stderr_output_filename = os.path.join(
self.stderr_out_dir, "%s.stderr.pe" % test_name)
valgrind_output_filename = os.path.join(
self.valgrind_out_dir, "%s.valgrind" % test_name)
# Common arguments for running test
test_cmd = []
if self.valgrind_args:
test_cmd = self.valgrind_args + [ "--log-file=%s" % valgrind_output_filename ]
test_cmd = test_cmd + self.simulate_args
# @TODO It would be more pythonic to raise exceptions for errors,
# then perhaps it would be nice to make a single-test class
# Ensure necessary test inputs exist
if not os.path.isfile(input_filename):
self._error("No input")
self.num_failed = self.num_failed + 1
return ExitStatus.NOINPUT
if not self.args.update and not os.path.isfile(expected_filename):
self._error("no stored output")
return ExitStatus.NOINPUT
# Run simulation to generate summary output
test_cmd_full = test_cmd + [ '-x', input_filename, '-S' ] + test_args
if self.args.run: # Single test mode
print(" ".join(test_cmd_full))
with io.open(summary_output_filename, "wt") as f:
simulation = subprocess.Popen(test_cmd_full, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=os.environ)
# This makes diff happy regardless of --enable-compat-2.0.
# Use sed -E to make Linux and BSD special characters more compatible.
sed = subprocess.Popen(["sed", "-E",
"-e", "s/ocf::/ocf:/g",
"-e", r"s/Masters:/Promoted:/",
"-e", r"s/Slaves:/Unpromoted:/",
"-e", r"s/ Master( |\[|$)/ Promoted\1/",
"-e", r"s/ Slave / Unpromoted /",
], stdin=simulation.stdout, stdout=f,
stderr=subprocess.STDOUT)
simulation.stdout.close()
sed.communicate()
if self.args.run:
cat(summary_output_filename)
# Re-run simulation to generate dot, graph, and scores
test_cmd_full = test_cmd + [
'-x', input_filename,
'-D', dot_output_filename,
'-G', output_filename,
'-sSQ' ] + test_args
with io.open(stderr_output_filename, "wt") as f_stderr, \
io.open(score_output_filename, "wt") as f_score:
rc = subprocess.call(test_cmd_full, stdout=f_score, stderr=f_stderr, env=os.environ)
# Check for test command failure
if rc != ExitStatus.OK:
self._failed("Test returned: %d" % rc)
did_fail = True
print(" ".join(test_cmd_full))
# Check for valgrind errors
if self.valgrind_args and not self.args.valgrind_skip_output:
if os.stat(valgrind_output_filename).st_size > 0:
self._failed("Valgrind reported errors")
did_fail = True
cat(valgrind_output_filename)
remove_files([ valgrind_output_filename ])
# Check for core dump
if os.path.isfile("core"):
self._failed("Core-file detected: core." + test_name)
did_fail = True
os.rename("core", "%s/core.%s" % (self.test_home, test_name))
# Check any stderr output
if os.path.isfile(stderr_expected_filename):
if self._compare_files(stderr_expected_filename, stderr_output_filename):
self._failed("stderr changed")
did_fail = True
elif os.stat(stderr_output_filename).st_size > 0:
self._failed("Output was written to stderr")
did_fail = True
cat(stderr_output_filename)
remove_files([ stderr_output_filename ])
# Check whether output graph exists, and normalize it
if (not os.path.isfile(output_filename)
or os.stat(output_filename).st_size == 0):
self._error("No graph produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ output_filename ])
return ExitStatus.ERROR
normalize(output_filename)
# Check whether dot output exists, and sort it
if (not os.path.isfile(dot_output_filename) or
os.stat(dot_output_filename).st_size == 0):
self._error("No dot-file summary produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ dot_output_filename, output_filename ])
return ExitStatus.ERROR
with io.open(dot_output_filename, "rt") as f:
first_line = f.readline() # "digraph" line with opening brace
lines = f.readlines()
last_line = lines[-1] # closing brace
del lines[-1]
lines = sorted(set(lines)) # unique sort
with io.open(dot_output_filename, "wt") as f:
f.write(first_line)
f.writelines(lines)
f.write(last_line)
# Check whether score output exists, and sort it
if (not os.path.isfile(score_output_filename)
or os.stat(score_output_filename).st_size == 0):
self._error("No allocation scores produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ score_output_filename, output_filename ])
return ExitStatus.ERROR
else:
sort_file(score_output_filename)
if self.args.update:
shutil.copyfile(output_filename, expected_filename)
shutil.copyfile(dot_output_filename, dot_expected_filename)
shutil.copyfile(score_output_filename, scores_filename)
shutil.copyfile(summary_output_filename, summary_filename)
print(" Updated expected outputs")
if self._compare_files(summary_filename, summary_output_filename):
self._failed("summary changed")
did_fail = True
if self._compare_files(dot_expected_filename, dot_output_filename):
self._failed("dot-file summary changed")
did_fail = True
else:
remove_files([ dot_output_filename ])
if self._compare_files(expected_filename, output_filename):
self._failed("xml-file changed")
did_fail = True
if self._compare_files(scores_filename, score_output_filename):
self._failed("scores-file changed")
did_fail = True
remove_files([ output_filename,
dot_output_filename,
score_output_filename,
summary_output_filename])
if did_fail:
self.num_failed = self.num_failed + 1
return ExitStatus.ERROR
return ExitStatus.OK
def run_all(self):
""" Run all defined tests """
if platform.architecture()[0] == "64bit":
TESTS.extend(TESTS_64BIT)
for group in TESTS:
for test in group.tests:
self.run_one(test.name, test.desc, test.args)
print()
def _print_summary(self):
""" Print a summary of parameters for this test run """
print("Test home is:\t" + self.test_home)
print("Test binary is:\t" + self.args.binary)
if 'PCMK_schema_directory' in os.environ:
print("Schema home is:\t" + os.environ['PCMK_schema_directory'])
if self.valgrind_args != []:
print("Activating memory testing with valgrind")
print()
def _test_results(self):
if self.num_failed == 0:
shutil.rmtree(self.failed_dir)
return ExitStatus.OK
if os.path.isfile(self.failed_filename) and os.stat(self.failed_filename).st_size != 0:
if self.args.verbose:
self._error("Results of %d failed tests (out of %d):" %
(self.num_failed, self.num_tests))
cat(self.failed_filename)
else:
self._error("Results of %d failed tests (out of %d) are in %s" %
(self.num_failed, self.num_tests, self.failed_filename))
self._error("Use -V to display them after running the tests")
else:
self._error("%d (of %d) tests failed (no diff results)" %
(self.num_failed, self.num_tests))
if os.path.isfile(self.failed_filename):
shutil.rmtree(self.failed_dir)
return ExitStatus.ERROR
def find_test(self, name):
if platform.architecture()[0] == "64bit":
TESTS.extend(TESTS_64BIT)
for group in TESTS:
for test in group.tests:
if test.name == name:
return test
return None
def run(self):
""" Run test(s) as specified """
# Check for pre-existing core so we don't think it's from us
if os.path.exists("core"):
self._failed("Can't run with core already present in " + self.test_home)
return ExitStatus.OSFILE
self._print_summary()
# Zero out the error log
self.failed_file = io.open(self.failed_filename, "wt")
if self.args.run is None:
print("Performing the following tests from " + self.args.io_dir)
print()
self.run_all()
print()
self.failed_file.close()
rc = self._test_results()
else:
# Find the test we were asked to run
test = self.find_test(self.args.run)
if test is None:
print("No test named %s" % self.args.run)
return ExitStatus.INVALID_PARAM
# If no arguments were given on the command line, default to the ones
# contained in the test
if self.single_test_args:
args = self.single_test_args
else:
args = test.args
rc = self.run_one(test.name, test.desc, args)
self.failed_file.close()
if self.num_failed > 0:
print("\nFailures:\nThese have also been written to: " + self.failed_filename + "\n")
cat(self.failed_filename)
shutil.rmtree(self.failed_dir)
return rc
if __name__ == "__main__":
sys.exit(CtsScheduler().run())
# vim: set filetype=python expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=120:
diff --git a/cts/scheduler/dot/coloc-cloned-group-promoted-dependent1.dot b/cts/scheduler/dot/coloc-cloned-group-promoted-dependent1.dot
new file mode 100644
index 0000000000..d8f1c9f22b
--- /dev/null
+++ b/cts/scheduler/dot/coloc-cloned-group-promoted-dependent1.dot
@@ -0,0 +1,2 @@
+ digraph "g" {
+}
diff --git a/cts/scheduler/dot/coloc-cloned-group-promoted-dependent2.dot b/cts/scheduler/dot/coloc-cloned-group-promoted-dependent2.dot
new file mode 100644
index 0000000000..d8f1c9f22b
--- /dev/null
+++ b/cts/scheduler/dot/coloc-cloned-group-promoted-dependent2.dot
@@ -0,0 +1,2 @@
+ digraph "g" {
+}
diff --git a/cts/scheduler/exp/coloc-cloned-group-promoted-dependent1.exp b/cts/scheduler/exp/coloc-cloned-group-promoted-dependent1.exp
new file mode 100644
index 0000000000..56e315ff01
--- /dev/null
+++ b/cts/scheduler/exp/coloc-cloned-group-promoted-dependent1.exp
@@ -0,0 +1 @@
+
diff --git a/cts/scheduler/exp/coloc-cloned-group-promoted-dependent2.exp b/cts/scheduler/exp/coloc-cloned-group-promoted-dependent2.exp
new file mode 100644
index 0000000000..56e315ff01
--- /dev/null
+++ b/cts/scheduler/exp/coloc-cloned-group-promoted-dependent2.exp
@@ -0,0 +1 @@
+
diff --git a/cts/scheduler/scores/coloc-clone-stays-active.scores b/cts/scheduler/scores/coloc-clone-stays-active.scores
index 52c6bc59ef..6995176853 100644
--- a/cts/scheduler/scores/coloc-clone-stays-active.scores
+++ b/cts/scheduler/scores/coloc-clone-stays-active.scores
@@ -1,505 +1,505 @@
drbd-pool-0:0 promotion score on s01-1: 10000
drbd-pool-0:1 promotion score on s01-0: 12000
drbd-pool-1:0 promotion score on s01-1: 12000
drbd-pool-1:1 promotion score on s01-0: 10000
drbd-s01-logs:0 promotion score on s01-1: 10000
drbd-s01-logs:1 promotion score on s01-0: 10000
drbd-s01-service:0 promotion score on s01-1: 10000
drbd-s01-service:1 promotion score on s01-0: 10000
drbd-s01-vm-data:0 promotion score on s01-1: 10000
drbd-s01-vm-data:1 promotion score on s01-0: 1
drbd-vds-dom0-stateless-0:0 promotion score on s01-1: 10000
drbd-vds-dom0-stateless-0:1 promotion score on s01-0: 10000
drbd-vds-http:0 promotion score on s01-1: 10000
drbd-vds-http:1 promotion score on s01-0: 10000
drbd-vds-tftpboot:0 promotion score on s01-1: 10000
drbd-vds-tftpboot:1 promotion score on s01-0: 10000
iscsi-pool-0-vips-fw:0 promotion score on s01-1: -INFINITY
-iscsi-pool-0-vips-fw:1 promotion score on s01-0: 2000
-iscsi-pool-1-vips-fw:0 promotion score on s01-1: 2000
+iscsi-pool-0-vips-fw:1 promotion score on s01-0: INFINITY
+iscsi-pool-1-vips-fw:0 promotion score on s01-1: INFINITY
iscsi-pool-1-vips-fw:1 promotion score on s01-0: -INFINITY
iscsi-vds-dom0-stateless-0-vips-fw:0 promotion score on s01-1: -INFINITY
iscsi-vds-dom0-stateless-0-vips-fw:1 promotion score on s01-0: -INFINITY
pcmk__clone_assign: cl-clvmd allocation score on s01-0: 0
pcmk__clone_assign: cl-clvmd allocation score on s01-1: 0
pcmk__clone_assign: cl-dhcpd allocation score on s01-0: 0
pcmk__clone_assign: cl-dhcpd allocation score on s01-1: 0
pcmk__clone_assign: cl-dlm allocation score on s01-0: 0
pcmk__clone_assign: cl-dlm allocation score on s01-1: 0
pcmk__clone_assign: cl-drbdlinks-s01-service allocation score on s01-0: 0
pcmk__clone_assign: cl-drbdlinks-s01-service allocation score on s01-1: 0
pcmk__clone_assign: cl-gfs2 allocation score on s01-0: 0
pcmk__clone_assign: cl-gfs2 allocation score on s01-1: 0
pcmk__clone_assign: cl-ietd allocation score on s01-0: 12001
pcmk__clone_assign: cl-ietd allocation score on s01-1: 1000
pcmk__clone_assign: cl-libvirtd allocation score on s01-0: 0
pcmk__clone_assign: cl-libvirtd allocation score on s01-1: 0
pcmk__clone_assign: cl-o2cb allocation score on s01-0: 0
pcmk__clone_assign: cl-o2cb allocation score on s01-1: 0
pcmk__clone_assign: cl-ospf-routing allocation score on s01-0: 0
pcmk__clone_assign: cl-ospf-routing allocation score on s01-1: 0
pcmk__clone_assign: cl-s01-logs-fs allocation score on s01-0: 0
pcmk__clone_assign: cl-s01-logs-fs allocation score on s01-1: 0
pcmk__clone_assign: cl-s01-service-fs allocation score on s01-0: 0
pcmk__clone_assign: cl-s01-service-fs allocation score on s01-1: 0
pcmk__clone_assign: cl-s01-vm-data-metadata-fs allocation score on s01-0: 0
pcmk__clone_assign: cl-s01-vm-data-metadata-fs allocation score on s01-1: 0
pcmk__clone_assign: cl-s01-vm-data-storage-pool allocation score on s01-0: 0
pcmk__clone_assign: cl-s01-vm-data-storage-pool allocation score on s01-1: 0
pcmk__clone_assign: cl-vds-http-fs allocation score on s01-0: 0
pcmk__clone_assign: cl-vds-http-fs allocation score on s01-1: 0
pcmk__clone_assign: cl-vds-tftpboot-fs allocation score on s01-0: 0
pcmk__clone_assign: cl-vds-tftpboot-fs allocation score on s01-1: 0
pcmk__clone_assign: cl-vg-s01-vm-data allocation score on s01-0: 0
pcmk__clone_assign: cl-vg-s01-vm-data allocation score on s01-1: 0
pcmk__clone_assign: cl-xinetd allocation score on s01-0: 0
pcmk__clone_assign: cl-xinetd allocation score on s01-1: 0
pcmk__clone_assign: clvmd:0 allocation score on s01-0: 0
pcmk__clone_assign: clvmd:0 allocation score on s01-1: 1
pcmk__clone_assign: clvmd:1 allocation score on s01-0: 1
pcmk__clone_assign: clvmd:1 allocation score on s01-1: 0
pcmk__clone_assign: connected-outer allocation score on s01-0: 0
pcmk__clone_assign: connected-outer allocation score on s01-1: 0
pcmk__clone_assign: dhcpd:0 allocation score on s01-0: 0
pcmk__clone_assign: dhcpd:0 allocation score on s01-1: 0
pcmk__clone_assign: dhcpd:1 allocation score on s01-0: 0
pcmk__clone_assign: dhcpd:1 allocation score on s01-1: 0
pcmk__clone_assign: dlm:0 allocation score on s01-0: 0
pcmk__clone_assign: dlm:0 allocation score on s01-1: 1
pcmk__clone_assign: dlm:1 allocation score on s01-0: 1
pcmk__clone_assign: dlm:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-pool-0:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-pool-0:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-pool-0:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-pool-0:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-pool-1:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-pool-1:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-pool-1:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-pool-1:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-s01-logs:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-s01-logs:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-s01-logs:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-s01-logs:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-s01-service:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-s01-service:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-s01-service:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-s01-service:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-s01-vm-data:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-s01-vm-data:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-s01-vm-data:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-s01-vm-data:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-vds-dom0-stateless-0:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-vds-dom0-stateless-0:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-vds-dom0-stateless-0:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-vds-dom0-stateless-0:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-vds-http:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-vds-http:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-vds-http:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-vds-http:1 allocation score on s01-1: 0
pcmk__clone_assign: drbd-vds-tftpboot:0 allocation score on s01-0: 0
pcmk__clone_assign: drbd-vds-tftpboot:0 allocation score on s01-1: 10001
pcmk__clone_assign: drbd-vds-tftpboot:1 allocation score on s01-0: 10001
pcmk__clone_assign: drbd-vds-tftpboot:1 allocation score on s01-1: 0
pcmk__clone_assign: drbdlinks-s01-service:0 allocation score on s01-0: 0
pcmk__clone_assign: drbdlinks-s01-service:0 allocation score on s01-1: 1
pcmk__clone_assign: drbdlinks-s01-service:1 allocation score on s01-0: 1
pcmk__clone_assign: drbdlinks-s01-service:1 allocation score on s01-1: 0
pcmk__clone_assign: gfs2:0 allocation score on s01-0: 0
pcmk__clone_assign: gfs2:0 allocation score on s01-1: 1
pcmk__clone_assign: gfs2:1 allocation score on s01-0: 1
pcmk__clone_assign: gfs2:1 allocation score on s01-1: 0
pcmk__clone_assign: ietd:0 allocation score on s01-0: 0
pcmk__clone_assign: ietd:0 allocation score on s01-1: 1
pcmk__clone_assign: ietd:1 allocation score on s01-0: 1
pcmk__clone_assign: ietd:1 allocation score on s01-1: 0
pcmk__clone_assign: iscsi-pool-0-vips-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: iscsi-pool-0-vips-fw:0 allocation score on s01-1: 2000
pcmk__clone_assign: iscsi-pool-0-vips-fw:1 allocation score on s01-0: 2000
pcmk__clone_assign: iscsi-pool-0-vips-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: iscsi-pool-1-vips-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: iscsi-pool-1-vips-fw:0 allocation score on s01-1: 2000
pcmk__clone_assign: iscsi-pool-1-vips-fw:1 allocation score on s01-0: 2000
pcmk__clone_assign: iscsi-pool-1-vips-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: iscsi-vds-dom0-stateless-0-vips-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: iscsi-vds-dom0-stateless-0-vips-fw:0 allocation score on s01-1: 2000
pcmk__clone_assign: iscsi-vds-dom0-stateless-0-vips-fw:1 allocation score on s01-0: 2000
pcmk__clone_assign: iscsi-vds-dom0-stateless-0-vips-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: libvirtd:0 allocation score on s01-0: 0
pcmk__clone_assign: libvirtd:0 allocation score on s01-1: 1
pcmk__clone_assign: libvirtd:1 allocation score on s01-0: 1
pcmk__clone_assign: libvirtd:1 allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-pool-0 allocation score on s01-0: 1000
pcmk__clone_assign: ms-drbd-pool-0 allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-pool-1 allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-pool-1 allocation score on s01-1: 1000
pcmk__clone_assign: ms-drbd-s01-logs allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-s01-logs allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-s01-service allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-s01-service allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-s01-vm-data allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-s01-vm-data allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-vds-dom0-stateless-0 allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-vds-dom0-stateless-0 allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-vds-http allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-vds-http allocation score on s01-1: 0
pcmk__clone_assign: ms-drbd-vds-tftpboot allocation score on s01-0: 0
pcmk__clone_assign: ms-drbd-vds-tftpboot allocation score on s01-1: 0
pcmk__clone_assign: ms-iscsi-pool-0-vips-fw allocation score on s01-0: 0
pcmk__clone_assign: ms-iscsi-pool-0-vips-fw allocation score on s01-1: 0
pcmk__clone_assign: ms-iscsi-pool-1-vips-fw allocation score on s01-0: 0
pcmk__clone_assign: ms-iscsi-pool-1-vips-fw allocation score on s01-1: 0
pcmk__clone_assign: ms-iscsi-vds-dom0-stateless-0-vips-fw allocation score on s01-0: 0
pcmk__clone_assign: ms-iscsi-vds-dom0-stateless-0-vips-fw allocation score on s01-1: 0
pcmk__clone_assign: o2cb:0 allocation score on s01-0: 0
pcmk__clone_assign: o2cb:0 allocation score on s01-1: 0
pcmk__clone_assign: o2cb:1 allocation score on s01-0: 0
pcmk__clone_assign: o2cb:1 allocation score on s01-1: 0
pcmk__clone_assign: ospf-routing:0 allocation score on s01-0: 0
pcmk__clone_assign: ospf-routing:0 allocation score on s01-1: 0
pcmk__clone_assign: ospf-routing:1 allocation score on s01-0: 0
pcmk__clone_assign: ospf-routing:1 allocation score on s01-1: 0
pcmk__clone_assign: ospfd:0 allocation score on s01-0: 0
pcmk__clone_assign: ospfd:0 allocation score on s01-1: 1
pcmk__clone_assign: ospfd:1 allocation score on s01-0: 1
pcmk__clone_assign: ospfd:1 allocation score on s01-1: 0
pcmk__clone_assign: ping-bmc-and-switch:0 allocation score on s01-0: 0
pcmk__clone_assign: ping-bmc-and-switch:0 allocation score on s01-1: 1
pcmk__clone_assign: ping-bmc-and-switch:1 allocation score on s01-0: 1
pcmk__clone_assign: ping-bmc-and-switch:1 allocation score on s01-1: 0
pcmk__clone_assign: s01-logs-fs:0 allocation score on s01-0: 0
pcmk__clone_assign: s01-logs-fs:0 allocation score on s01-1: 1
pcmk__clone_assign: s01-logs-fs:1 allocation score on s01-0: 1
pcmk__clone_assign: s01-logs-fs:1 allocation score on s01-1: 0
pcmk__clone_assign: s01-service-fs:0 allocation score on s01-0: 0
pcmk__clone_assign: s01-service-fs:0 allocation score on s01-1: 1
pcmk__clone_assign: s01-service-fs:1 allocation score on s01-0: 1
pcmk__clone_assign: s01-service-fs:1 allocation score on s01-1: 0
pcmk__clone_assign: s01-vm-data-metadata-fs:0 allocation score on s01-0: 0
pcmk__clone_assign: s01-vm-data-metadata-fs:0 allocation score on s01-1: 1
pcmk__clone_assign: s01-vm-data-metadata-fs:1 allocation score on s01-0: 1
pcmk__clone_assign: s01-vm-data-metadata-fs:1 allocation score on s01-1: 0
pcmk__clone_assign: s01-vm-data-storage-pool:0 allocation score on s01-0: 0
pcmk__clone_assign: s01-vm-data-storage-pool:0 allocation score on s01-1: 1
pcmk__clone_assign: s01-vm-data-storage-pool:1 allocation score on s01-0: 1
pcmk__clone_assign: s01-vm-data-storage-pool:1 allocation score on s01-1: 0
pcmk__clone_assign: vds-http-fs:0 allocation score on s01-0: 0
pcmk__clone_assign: vds-http-fs:0 allocation score on s01-1: 1
pcmk__clone_assign: vds-http-fs:1 allocation score on s01-0: 1
pcmk__clone_assign: vds-http-fs:1 allocation score on s01-1: 0
pcmk__clone_assign: vds-tftpboot-fs:0 allocation score on s01-0: 0
pcmk__clone_assign: vds-tftpboot-fs:0 allocation score on s01-1: 0
pcmk__clone_assign: vds-tftpboot-fs:1 allocation score on s01-0: 0
pcmk__clone_assign: vds-tftpboot-fs:1 allocation score on s01-1: 0
pcmk__clone_assign: vg-s01-vm-data:0 allocation score on s01-0: 0
pcmk__clone_assign: vg-s01-vm-data:0 allocation score on s01-1: 1
pcmk__clone_assign: vg-s01-vm-data:1 allocation score on s01-0: 1
pcmk__clone_assign: vg-s01-vm-data:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-227-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-227-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-227-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-227-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-228-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-228-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-228-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-228-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-235-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-235-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-235-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-235-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-236-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-236-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-236-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-236-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-237-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-237-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-237-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-237-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: vip-238-fw:0 allocation score on s01-0: 0
pcmk__clone_assign: vip-238-fw:0 allocation score on s01-1: 1
pcmk__clone_assign: vip-238-fw:1 allocation score on s01-0: 1
pcmk__clone_assign: vip-238-fw:1 allocation score on s01-1: 0
pcmk__clone_assign: xinetd:0 allocation score on s01-0: 0
pcmk__clone_assign: xinetd:0 allocation score on s01-1: 1
pcmk__clone_assign: xinetd:1 allocation score on s01-0: 1
pcmk__clone_assign: xinetd:1 allocation score on s01-1: 0
pcmk__clone_assign: zebra:0 allocation score on s01-0: 0
pcmk__clone_assign: zebra:0 allocation score on s01-1: 1
pcmk__clone_assign: zebra:1 allocation score on s01-0: 1
pcmk__clone_assign: zebra:1 allocation score on s01-1: 0
pcmk__group_assign: http-server allocation score on s01-0: 0
pcmk__group_assign: http-server allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-0-lun-1 allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-0-lun-1 allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-0-target allocation score on s01-0: 1000
pcmk__group_assign: iscsi-pool-0-target allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-0-target-all allocation score on s01-0: 1000
pcmk__group_assign: iscsi-pool-0-target-all allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-0-vips allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-0-vips allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-0-vips-fw:0 allocation score on s01-0: -INFINITY
pcmk__group_assign: iscsi-pool-0-vips-fw:0 allocation score on s01-1: 2000
pcmk__group_assign: iscsi-pool-0-vips-fw:1 allocation score on s01-0: 2000
pcmk__group_assign: iscsi-pool-0-vips-fw:1 allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-1-lun-1 allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-1-lun-1 allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-1-target allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-1-target allocation score on s01-1: 1000
pcmk__group_assign: iscsi-pool-1-target-all allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-1-target-all allocation score on s01-1: 1000
pcmk__group_assign: iscsi-pool-1-vips allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-1-vips allocation score on s01-1: 0
pcmk__group_assign: iscsi-pool-1-vips-fw:0 allocation score on s01-0: 0
pcmk__group_assign: iscsi-pool-1-vips-fw:0 allocation score on s01-1: 2000
pcmk__group_assign: iscsi-pool-1-vips-fw:1 allocation score on s01-0: 2000
pcmk__group_assign: iscsi-pool-1-vips-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: iscsi-vds-dom0-stateless-0-lun-1 allocation score on s01-0: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-lun-1 allocation score on s01-1: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-target allocation score on s01-0: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-target allocation score on s01-1: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-target-all allocation score on s01-0: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-target-all allocation score on s01-1: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips allocation score on s01-0: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips allocation score on s01-1: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips-fw:0 allocation score on s01-0: 0
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips-fw:0 allocation score on s01-1: 2000
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips-fw:1 allocation score on s01-0: 2000
pcmk__group_assign: iscsi-vds-dom0-stateless-0-vips-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: nginx allocation score on s01-0: 0
pcmk__group_assign: nginx allocation score on s01-1: 0
pcmk__group_assign: ospf-routing:0 allocation score on s01-0: 0
pcmk__group_assign: ospf-routing:0 allocation score on s01-1: 0
pcmk__group_assign: ospf-routing:1 allocation score on s01-0: 0
pcmk__group_assign: ospf-routing:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: ospfd:0 allocation score on s01-0: 0
pcmk__group_assign: ospfd:0 allocation score on s01-1: 1
pcmk__group_assign: ospfd:1 allocation score on s01-0: 1
pcmk__group_assign: ospfd:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: syslog-ng allocation score on s01-0: 0
pcmk__group_assign: syslog-ng allocation score on s01-1: 0
pcmk__group_assign: syslog-server allocation score on s01-0: 0
pcmk__group_assign: syslog-server allocation score on s01-1: 0
pcmk__group_assign: tftp-server allocation score on s01-0: 0
pcmk__group_assign: tftp-server allocation score on s01-1: 0
pcmk__group_assign: tftpd allocation score on s01-0: 0
pcmk__group_assign: tftpd allocation score on s01-1: 0
pcmk__group_assign: vip-227 allocation score on s01-0: 0
pcmk__group_assign: vip-227 allocation score on s01-1: 0
pcmk__group_assign: vip-227-fw:0 allocation score on s01-0: 0
pcmk__group_assign: vip-227-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-227-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-227-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: vip-228 allocation score on s01-0: 0
pcmk__group_assign: vip-228 allocation score on s01-1: 0
pcmk__group_assign: vip-228-fw:0 allocation score on s01-0: 0
pcmk__group_assign: vip-228-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-228-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-228-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: vip-232 allocation score on s01-0: 0
pcmk__group_assign: vip-232 allocation score on s01-1: 0
pcmk__group_assign: vip-233 allocation score on s01-0: 0
pcmk__group_assign: vip-233 allocation score on s01-1: 0
pcmk__group_assign: vip-234 allocation score on s01-0: 0
pcmk__group_assign: vip-234 allocation score on s01-1: 0
pcmk__group_assign: vip-235 allocation score on s01-0: 0
pcmk__group_assign: vip-235 allocation score on s01-1: 0
pcmk__group_assign: vip-235-fw:0 allocation score on s01-0: -INFINITY
pcmk__group_assign: vip-235-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-235-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-235-fw:1 allocation score on s01-1: 0
pcmk__group_assign: vip-236 allocation score on s01-0: 0
pcmk__group_assign: vip-236 allocation score on s01-1: 0
pcmk__group_assign: vip-236-fw:0 allocation score on s01-0: -INFINITY
pcmk__group_assign: vip-236-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-236-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-236-fw:1 allocation score on s01-1: 0
pcmk__group_assign: vip-237 allocation score on s01-0: 0
pcmk__group_assign: vip-237 allocation score on s01-1: 0
pcmk__group_assign: vip-237-fw:0 allocation score on s01-0: 0
pcmk__group_assign: vip-237-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-237-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-237-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: vip-238 allocation score on s01-0: 0
pcmk__group_assign: vip-238 allocation score on s01-1: 0
pcmk__group_assign: vip-238-fw:0 allocation score on s01-0: 0
pcmk__group_assign: vip-238-fw:0 allocation score on s01-1: 1
pcmk__group_assign: vip-238-fw:1 allocation score on s01-0: 1
pcmk__group_assign: vip-238-fw:1 allocation score on s01-1: -INFINITY
pcmk__group_assign: zebra:0 allocation score on s01-0: 0
pcmk__group_assign: zebra:0 allocation score on s01-1: 1
pcmk__group_assign: zebra:1 allocation score on s01-0: 1
pcmk__group_assign: zebra:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: clvmd:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: clvmd:0 allocation score on s01-1: 1
pcmk__primitive_assign: clvmd:1 allocation score on s01-0: 1
pcmk__primitive_assign: clvmd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: dhcpd:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: dhcpd:0 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: dhcpd:1 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: dhcpd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: dlm:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: dlm:0 allocation score on s01-1: 1
pcmk__primitive_assign: dlm:1 allocation score on s01-0: 1
pcmk__primitive_assign: dlm:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-pool-0:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: drbd-pool-0:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-pool-0:1 allocation score on s01-0: 11001
pcmk__primitive_assign: drbd-pool-0:1 allocation score on s01-1: 0
pcmk__primitive_assign: drbd-pool-1:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-pool-1:0 allocation score on s01-1: 11001
pcmk__primitive_assign: drbd-pool-1:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-pool-1:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-s01-logs:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-s01-logs:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-s01-logs:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-s01-logs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-s01-service:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-s01-service:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-s01-service:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-s01-service:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-s01-vm-data:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: drbd-s01-vm-data:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-s01-vm-data:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-s01-vm-data:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-vds-dom0-stateless-0:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-vds-dom0-stateless-0:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-vds-dom0-stateless-0:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-vds-dom0-stateless-0:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-vds-http:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-vds-http:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-vds-http:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-vds-http:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbd-vds-tftpboot:0 allocation score on s01-0: 0
pcmk__primitive_assign: drbd-vds-tftpboot:0 allocation score on s01-1: 10001
pcmk__primitive_assign: drbd-vds-tftpboot:1 allocation score on s01-0: 10001
pcmk__primitive_assign: drbd-vds-tftpboot:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: drbdlinks-s01-service:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: drbdlinks-s01-service:0 allocation score on s01-1: 1
pcmk__primitive_assign: drbdlinks-s01-service:1 allocation score on s01-0: 1
pcmk__primitive_assign: drbdlinks-s01-service:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: gfs2:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: gfs2:0 allocation score on s01-1: 1
pcmk__primitive_assign: gfs2:1 allocation score on s01-0: 1
pcmk__primitive_assign: gfs2:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: ietd:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: ietd:0 allocation score on s01-1: 1001
pcmk__primitive_assign: ietd:1 allocation score on s01-0: 12002
pcmk__primitive_assign: ietd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: iscsi-pool-0-lun-1 allocation score on s01-0: 0
pcmk__primitive_assign: iscsi-pool-0-lun-1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: iscsi-pool-0-target allocation score on s01-0: 12001
pcmk__primitive_assign: iscsi-pool-0-target allocation score on s01-1: -INFINITY
pcmk__primitive_assign: iscsi-pool-1-lun-1 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: iscsi-pool-1-lun-1 allocation score on s01-1: 0
pcmk__primitive_assign: iscsi-pool-1-target allocation score on s01-0: -INFINITY
pcmk__primitive_assign: iscsi-pool-1-target allocation score on s01-1: 12001
pcmk__primitive_assign: iscsi-vds-dom0-stateless-0-lun-1 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: iscsi-vds-dom0-stateless-0-lun-1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: iscsi-vds-dom0-stateless-0-target allocation score on s01-0: -INFINITY
pcmk__primitive_assign: iscsi-vds-dom0-stateless-0-target allocation score on s01-1: -INFINITY
pcmk__primitive_assign: libvirtd:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: libvirtd:0 allocation score on s01-1: 1
pcmk__primitive_assign: libvirtd:1 allocation score on s01-0: 1
pcmk__primitive_assign: libvirtd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: mgmt-vm allocation score on s01-0: -INFINITY
pcmk__primitive_assign: mgmt-vm allocation score on s01-1: 0
pcmk__primitive_assign: nginx allocation score on s01-0: -INFINITY
pcmk__primitive_assign: nginx allocation score on s01-1: -INFINITY
pcmk__primitive_assign: o2cb:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: o2cb:0 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: o2cb:1 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: o2cb:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: ospfd:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: ospfd:0 allocation score on s01-1: 1
pcmk__primitive_assign: ospfd:1 allocation score on s01-0: 1
pcmk__primitive_assign: ospfd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: ping-bmc-and-switch:0 allocation score on s01-0: 0
pcmk__primitive_assign: ping-bmc-and-switch:0 allocation score on s01-1: 1
pcmk__primitive_assign: ping-bmc-and-switch:1 allocation score on s01-0: 1
pcmk__primitive_assign: ping-bmc-and-switch:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: s01-logs-fs:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: s01-logs-fs:0 allocation score on s01-1: 10002
pcmk__primitive_assign: s01-logs-fs:1 allocation score on s01-0: 10002
pcmk__primitive_assign: s01-logs-fs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: s01-service-fs:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: s01-service-fs:0 allocation score on s01-1: 10002
pcmk__primitive_assign: s01-service-fs:1 allocation score on s01-0: 10002
pcmk__primitive_assign: s01-service-fs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: s01-vm-data-metadata-fs:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: s01-vm-data-metadata-fs:0 allocation score on s01-1: 1
pcmk__primitive_assign: s01-vm-data-metadata-fs:1 allocation score on s01-0: 1
pcmk__primitive_assign: s01-vm-data-metadata-fs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: s01-vm-data-storage-pool:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: s01-vm-data-storage-pool:0 allocation score on s01-1: 1
pcmk__primitive_assign: s01-vm-data-storage-pool:1 allocation score on s01-0: 1
pcmk__primitive_assign: s01-vm-data-storage-pool:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: stonith-s01-0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: stonith-s01-0 allocation score on s01-1: 0
pcmk__primitive_assign: stonith-s01-1 allocation score on s01-0: 0
pcmk__primitive_assign: stonith-s01-1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: syslog-ng allocation score on s01-0: -INFINITY
pcmk__primitive_assign: syslog-ng allocation score on s01-1: 0
pcmk__primitive_assign: tftpd allocation score on s01-0: -INFINITY
pcmk__primitive_assign: tftpd allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vds-http-fs:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vds-http-fs:0 allocation score on s01-1: 10002
pcmk__primitive_assign: vds-http-fs:1 allocation score on s01-0: 10002
pcmk__primitive_assign: vds-http-fs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vds-tftpboot-fs:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vds-tftpboot-fs:0 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vds-tftpboot-fs:1 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vds-tftpboot-fs:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vg-s01-vm-data:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vg-s01-vm-data:0 allocation score on s01-1: 10002
pcmk__primitive_assign: vg-s01-vm-data:1 allocation score on s01-0: 10002
pcmk__primitive_assign: vg-s01-vm-data:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-227 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-227 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-227-fw:0 allocation score on s01-0: 0
pcmk__primitive_assign: vip-227-fw:0 allocation score on s01-1: 2
pcmk__primitive_assign: vip-227-fw:1 allocation score on s01-0: 2
pcmk__primitive_assign: vip-227-fw:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-228 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-228 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-228-fw:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-228-fw:0 allocation score on s01-1: 1
pcmk__primitive_assign: vip-228-fw:1 allocation score on s01-0: 1
pcmk__primitive_assign: vip-228-fw:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-232 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-232 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-233 allocation score on s01-0: 0
pcmk__primitive_assign: vip-233 allocation score on s01-1: 0
pcmk__primitive_assign: vip-234 allocation score on s01-0: 0
pcmk__primitive_assign: vip-234 allocation score on s01-1: 0
pcmk__primitive_assign: vip-235 allocation score on s01-0: 0
pcmk__primitive_assign: vip-235 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-235-fw:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-235-fw:0 allocation score on s01-1: 2
pcmk__primitive_assign: vip-235-fw:1 allocation score on s01-0: 2
pcmk__primitive_assign: vip-235-fw:1 allocation score on s01-1: 0
pcmk__primitive_assign: vip-236 allocation score on s01-0: 0
pcmk__primitive_assign: vip-236 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-236-fw:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-236-fw:0 allocation score on s01-1: 1
pcmk__primitive_assign: vip-236-fw:1 allocation score on s01-0: 1
pcmk__primitive_assign: vip-236-fw:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-237 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-237 allocation score on s01-1: 0
pcmk__primitive_assign: vip-237-fw:0 allocation score on s01-0: 0
pcmk__primitive_assign: vip-237-fw:0 allocation score on s01-1: 2
pcmk__primitive_assign: vip-237-fw:1 allocation score on s01-0: 2
pcmk__primitive_assign: vip-237-fw:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: vip-238 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-238 allocation score on s01-1: 0
pcmk__primitive_assign: vip-238-fw:0 allocation score on s01-0: -INFINITY
pcmk__primitive_assign: vip-238-fw:0 allocation score on s01-1: 1
pcmk__primitive_assign: vip-238-fw:1 allocation score on s01-0: 1
pcmk__primitive_assign: vip-238-fw:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: xinetd:0 allocation score on s01-0: 0
pcmk__primitive_assign: xinetd:0 allocation score on s01-1: 1
pcmk__primitive_assign: xinetd:1 allocation score on s01-0: 1
pcmk__primitive_assign: xinetd:1 allocation score on s01-1: -INFINITY
pcmk__primitive_assign: zebra:0 allocation score on s01-0: 0
pcmk__primitive_assign: zebra:0 allocation score on s01-1: 2
pcmk__primitive_assign: zebra:1 allocation score on s01-0: 2
pcmk__primitive_assign: zebra:1 allocation score on s01-1: -INFINITY
diff --git a/cts/scheduler/scores/coloc-cloned-group-promoted-dependent1.scores b/cts/scheduler/scores/coloc-cloned-group-promoted-dependent1.scores
new file mode 100644
index 0000000000..a900815c34
--- /dev/null
+++ b/cts/scheduler/scores/coloc-cloned-group-promoted-dependent1.scores
@@ -0,0 +1,27 @@
+
+grp1:0 promotion score on node2: -INFINITY
+grp1:1 promotion score on node1: INFINITY
+pcmk__clone_assign: grp1-clone allocation score on node1: 0
+pcmk__clone_assign: grp1-clone allocation score on node2: 0
+pcmk__clone_assign: grp1:0 allocation score on node1: 0
+pcmk__clone_assign: grp1:0 allocation score on node2: 50
+pcmk__clone_assign: grp1:1 allocation score on node1: 10
+pcmk__clone_assign: grp1:1 allocation score on node2: 0
+pcmk__clone_assign: rsc1:0 allocation score on node1: 0
+pcmk__clone_assign: rsc1:0 allocation score on node2: 1
+pcmk__clone_assign: rsc1:1 allocation score on node1: 1
+pcmk__clone_assign: rsc1:1 allocation score on node2: 0
+pcmk__group_assign: grp1:0 allocation score on node1: 0
+pcmk__group_assign: grp1:0 allocation score on node2: 50
+pcmk__group_assign: grp1:1 allocation score on node1: 10
+pcmk__group_assign: grp1:1 allocation score on node2: -INFINITY
+pcmk__group_assign: rsc1:0 allocation score on node1: 0
+pcmk__group_assign: rsc1:0 allocation score on node2: 1
+pcmk__group_assign: rsc1:1 allocation score on node1: 1
+pcmk__group_assign: rsc1:1 allocation score on node2: -INFINITY
+pcmk__primitive_assign: primary allocation score on node1: 1
+pcmk__primitive_assign: primary allocation score on node2: 0
+pcmk__primitive_assign: rsc1:0 allocation score on node1: 0
+pcmk__primitive_assign: rsc1:0 allocation score on node2: 1
+pcmk__primitive_assign: rsc1:1 allocation score on node1: 1
+pcmk__primitive_assign: rsc1:1 allocation score on node2: -INFINITY
diff --git a/cts/scheduler/scores/coloc-cloned-group-promoted-dependent2.scores b/cts/scheduler/scores/coloc-cloned-group-promoted-dependent2.scores
new file mode 100644
index 0000000000..151a57d702
--- /dev/null
+++ b/cts/scheduler/scores/coloc-cloned-group-promoted-dependent2.scores
@@ -0,0 +1,27 @@
+
+grp1:0 promotion score on node2: 50
+grp1:1 promotion score on node1: 2011
+pcmk__clone_assign: grp1-clone allocation score on node1: 0
+pcmk__clone_assign: grp1-clone allocation score on node2: 0
+pcmk__clone_assign: grp1:0 allocation score on node1: 0
+pcmk__clone_assign: grp1:0 allocation score on node2: 50
+pcmk__clone_assign: grp1:1 allocation score on node1: 10
+pcmk__clone_assign: grp1:1 allocation score on node2: 0
+pcmk__clone_assign: rsc1:0 allocation score on node1: 0
+pcmk__clone_assign: rsc1:0 allocation score on node2: 1
+pcmk__clone_assign: rsc1:1 allocation score on node1: 1
+pcmk__clone_assign: rsc1:1 allocation score on node2: 0
+pcmk__group_assign: grp1:0 allocation score on node1: 0
+pcmk__group_assign: grp1:0 allocation score on node2: 50
+pcmk__group_assign: grp1:1 allocation score on node1: 10
+pcmk__group_assign: grp1:1 allocation score on node2: -INFINITY
+pcmk__group_assign: rsc1:0 allocation score on node1: 0
+pcmk__group_assign: rsc1:0 allocation score on node2: 1
+pcmk__group_assign: rsc1:1 allocation score on node1: 1
+pcmk__group_assign: rsc1:1 allocation score on node2: -INFINITY
+pcmk__primitive_assign: primary allocation score on node1: 1
+pcmk__primitive_assign: primary allocation score on node2: 0
+pcmk__primitive_assign: rsc1:0 allocation score on node1: 0
+pcmk__primitive_assign: rsc1:0 allocation score on node2: 1
+pcmk__primitive_assign: rsc1:1 allocation score on node1: 1
+pcmk__primitive_assign: rsc1:1 allocation score on node2: -INFINITY
diff --git a/cts/scheduler/scores/promoted-partially-demoted-group.scores b/cts/scheduler/scores/promoted-partially-demoted-group.scores
index f266c64753..d1504ac08c 100644
--- a/cts/scheduler/scores/promoted-partially-demoted-group.scores
+++ b/cts/scheduler/scores/promoted-partially-demoted-group.scores
@@ -1,93 +1,93 @@
cdev-pool-0-drbd:0 promotion score on sd01-1: 10800
cdev-pool-0-drbd:1 promotion score on sd01-0: INFINITY
cdev-pool-0-iscsi-vips-fw:0 promotion score on sd01-1: -INFINITY
-cdev-pool-0-iscsi-vips-fw:1 promotion score on sd01-0: 2000
+cdev-pool-0-iscsi-vips-fw:1 promotion score on sd01-0: INFINITY
pcmk__clone_assign: cdev-pool-0-drbd:0 allocation score on sd01-0: 0
pcmk__clone_assign: cdev-pool-0-drbd:0 allocation score on sd01-1: 10100
pcmk__clone_assign: cdev-pool-0-drbd:1 allocation score on sd01-0: 10100
pcmk__clone_assign: cdev-pool-0-drbd:1 allocation score on sd01-1: 0
pcmk__clone_assign: cdev-pool-0-iscsi-vips-fw:0 allocation score on sd01-0: 0
pcmk__clone_assign: cdev-pool-0-iscsi-vips-fw:0 allocation score on sd01-1: 2000
pcmk__clone_assign: cdev-pool-0-iscsi-vips-fw:1 allocation score on sd01-0: 2000
pcmk__clone_assign: cdev-pool-0-iscsi-vips-fw:1 allocation score on sd01-1: 0
pcmk__clone_assign: cl-ietd allocation score on sd01-0: INFINITY
pcmk__clone_assign: cl-ietd allocation score on sd01-1: 300
pcmk__clone_assign: cl-vlan1-net allocation score on sd01-0: 0
pcmk__clone_assign: cl-vlan1-net allocation score on sd01-1: 0
pcmk__clone_assign: ietd:0 allocation score on sd01-0: 0
pcmk__clone_assign: ietd:0 allocation score on sd01-1: 100
pcmk__clone_assign: ietd:1 allocation score on sd01-0: 100
pcmk__clone_assign: ietd:1 allocation score on sd01-1: 0
pcmk__clone_assign: ms-cdev-pool-0-drbd allocation score on sd01-0: INFINITY
pcmk__clone_assign: ms-cdev-pool-0-drbd allocation score on sd01-1: 400
pcmk__clone_assign: ms-cdev-pool-0-iscsi-vips-fw allocation score on sd01-0: 0
pcmk__clone_assign: ms-cdev-pool-0-iscsi-vips-fw allocation score on sd01-1: 0
pcmk__clone_assign: vip-164-fw:0 allocation score on sd01-0: 0
pcmk__clone_assign: vip-164-fw:0 allocation score on sd01-1: 100
pcmk__clone_assign: vip-164-fw:1 allocation score on sd01-0: 100
pcmk__clone_assign: vip-164-fw:1 allocation score on sd01-1: 0
pcmk__clone_assign: vip-165-fw:0 allocation score on sd01-0: 0
pcmk__clone_assign: vip-165-fw:0 allocation score on sd01-1: 100
pcmk__clone_assign: vip-165-fw:1 allocation score on sd01-0: 100
pcmk__clone_assign: vip-165-fw:1 allocation score on sd01-1: 0
pcmk__clone_assign: vlan1-net:0 allocation score on sd01-0: 0
pcmk__clone_assign: vlan1-net:0 allocation score on sd01-1: 100
pcmk__clone_assign: vlan1-net:1 allocation score on sd01-0: 100
pcmk__clone_assign: vlan1-net:1 allocation score on sd01-1: 0
pcmk__group_assign: cdev-pool-0-iscsi-export allocation score on sd01-0: INFINITY
pcmk__group_assign: cdev-pool-0-iscsi-export allocation score on sd01-1: 0
pcmk__group_assign: cdev-pool-0-iscsi-lun-1 allocation score on sd01-0: 0
pcmk__group_assign: cdev-pool-0-iscsi-lun-1 allocation score on sd01-1: 100
pcmk__group_assign: cdev-pool-0-iscsi-target allocation score on sd01-0: INFINITY
pcmk__group_assign: cdev-pool-0-iscsi-target allocation score on sd01-1: 100
pcmk__group_assign: cdev-pool-0-iscsi-vips allocation score on sd01-0: 0
pcmk__group_assign: cdev-pool-0-iscsi-vips allocation score on sd01-1: 0
pcmk__group_assign: cdev-pool-0-iscsi-vips-fw:0 allocation score on sd01-0: -INFINITY
pcmk__group_assign: cdev-pool-0-iscsi-vips-fw:0 allocation score on sd01-1: 2000
pcmk__group_assign: cdev-pool-0-iscsi-vips-fw:1 allocation score on sd01-0: 2000
pcmk__group_assign: cdev-pool-0-iscsi-vips-fw:1 allocation score on sd01-1: 0
pcmk__group_assign: vip-164 allocation score on sd01-0: 0
pcmk__group_assign: vip-164 allocation score on sd01-1: 100
pcmk__group_assign: vip-164-fw:0 allocation score on sd01-0: -INFINITY
pcmk__group_assign: vip-164-fw:0 allocation score on sd01-1: 100
pcmk__group_assign: vip-164-fw:1 allocation score on sd01-0: 100
pcmk__group_assign: vip-164-fw:1 allocation score on sd01-1: 0
pcmk__group_assign: vip-165 allocation score on sd01-0: 0
pcmk__group_assign: vip-165 allocation score on sd01-1: 100
pcmk__group_assign: vip-165-fw:0 allocation score on sd01-0: -INFINITY
pcmk__group_assign: vip-165-fw:0 allocation score on sd01-1: 100
pcmk__group_assign: vip-165-fw:1 allocation score on sd01-0: 100
pcmk__group_assign: vip-165-fw:1 allocation score on sd01-1: 0
pcmk__primitive_assign: cdev-pool-0-drbd:0 allocation score on sd01-0: -INFINITY
pcmk__primitive_assign: cdev-pool-0-drbd:0 allocation score on sd01-1: 10500
pcmk__primitive_assign: cdev-pool-0-drbd:1 allocation score on sd01-0: INFINITY
pcmk__primitive_assign: cdev-pool-0-drbd:1 allocation score on sd01-1: 400
pcmk__primitive_assign: cdev-pool-0-iscsi-lun-1 allocation score on sd01-0: 0
pcmk__primitive_assign: cdev-pool-0-iscsi-lun-1 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: cdev-pool-0-iscsi-target allocation score on sd01-0: INFINITY
pcmk__primitive_assign: cdev-pool-0-iscsi-target allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: ietd:0 allocation score on sd01-0: -INFINITY
pcmk__primitive_assign: ietd:0 allocation score on sd01-1: 100
pcmk__primitive_assign: ietd:1 allocation score on sd01-0: INFINITY
pcmk__primitive_assign: ietd:1 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: stonith-xvm-sd01-0 allocation score on sd01-0: -INFINITY
pcmk__primitive_assign: stonith-xvm-sd01-0 allocation score on sd01-1: 100
pcmk__primitive_assign: stonith-xvm-sd01-1 allocation score on sd01-0: 100
pcmk__primitive_assign: stonith-xvm-sd01-1 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: vip-164 allocation score on sd01-0: 0
pcmk__primitive_assign: vip-164 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: vip-164-fw:0 allocation score on sd01-0: -INFINITY
pcmk__primitive_assign: vip-164-fw:0 allocation score on sd01-1: 200
pcmk__primitive_assign: vip-164-fw:1 allocation score on sd01-0: 200
pcmk__primitive_assign: vip-164-fw:1 allocation score on sd01-1: 0
pcmk__primitive_assign: vip-165 allocation score on sd01-0: 0
pcmk__primitive_assign: vip-165 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: vip-165-fw:0 allocation score on sd01-0: -INFINITY
pcmk__primitive_assign: vip-165-fw:0 allocation score on sd01-1: 100
pcmk__primitive_assign: vip-165-fw:1 allocation score on sd01-0: 100
pcmk__primitive_assign: vip-165-fw:1 allocation score on sd01-1: -INFINITY
pcmk__primitive_assign: vlan1-net:0 allocation score on sd01-0: 0
pcmk__primitive_assign: vlan1-net:0 allocation score on sd01-1: 100
pcmk__primitive_assign: vlan1-net:1 allocation score on sd01-0: 100
pcmk__primitive_assign: vlan1-net:1 allocation score on sd01-1: -INFINITY
diff --git a/cts/scheduler/summary/coloc-cloned-group-promoted-dependent1.summary b/cts/scheduler/summary/coloc-cloned-group-promoted-dependent1.summary
new file mode 100644
index 0000000000..6e8518093e
--- /dev/null
+++ b/cts/scheduler/summary/coloc-cloned-group-promoted-dependent1.summary
@@ -0,0 +1,23 @@
+Current cluster status:
+ * Node List:
+ * Online: [ node1 node2 ]
+
+ * Full List of Resources:
+ * Clone Set: grp1-clone [grp1] (promotable):
+ * Promoted: [ node1 ]
+ * Unpromoted: [ node2 ]
+ * primary (ocf:pacemaker:Dummy): Started node1
+
+Transition Summary:
+
+Executing Cluster Transition:
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ node1 node2 ]
+
+ * Full List of Resources:
+ * Clone Set: grp1-clone [grp1] (promotable):
+ * Promoted: [ node1 ]
+ * Unpromoted: [ node2 ]
+ * primary (ocf:pacemaker:Dummy): Started node1
diff --git a/cts/scheduler/summary/coloc-cloned-group-promoted-dependent2.summary b/cts/scheduler/summary/coloc-cloned-group-promoted-dependent2.summary
new file mode 100644
index 0000000000..6e8518093e
--- /dev/null
+++ b/cts/scheduler/summary/coloc-cloned-group-promoted-dependent2.summary
@@ -0,0 +1,23 @@
+Current cluster status:
+ * Node List:
+ * Online: [ node1 node2 ]
+
+ * Full List of Resources:
+ * Clone Set: grp1-clone [grp1] (promotable):
+ * Promoted: [ node1 ]
+ * Unpromoted: [ node2 ]
+ * primary (ocf:pacemaker:Dummy): Started node1
+
+Transition Summary:
+
+Executing Cluster Transition:
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ node1 node2 ]
+
+ * Full List of Resources:
+ * Clone Set: grp1-clone [grp1] (promotable):
+ * Promoted: [ node1 ]
+ * Unpromoted: [ node2 ]
+ * primary (ocf:pacemaker:Dummy): Started node1
diff --git a/cts/scheduler/xml/coloc-cloned-group-promoted-dependent1.xml b/cts/scheduler/xml/coloc-cloned-group-promoted-dependent1.xml
new file mode 100644
index 0000000000..58df0980f9
--- /dev/null
+++ b/cts/scheduler/xml/coloc-cloned-group-promoted-dependent1.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cts/scheduler/xml/coloc-cloned-group-promoted-dependent2.xml b/cts/scheduler/xml/coloc-cloned-group-promoted-dependent2.xml
new file mode 100644
index 0000000000..1fbfcbb903
--- /dev/null
+++ b/cts/scheduler/xml/coloc-cloned-group-promoted-dependent2.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index fbbe93bcd3..42a7e5427b 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,1174 +1,1176 @@
/*
* Copyright 2021-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
#define PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
/* This header is for the sole use of libpacemaker, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include // NULL
#include // uint32_t
#include // bool, false
#include // guint, gpointer, GList, GHashTable
#include // xmlNode
#include // pcmk_action_t, pcmk_node_t, etc.
#include // pcmk__location_t, etc.
#include // cib_t
#include // lrmd_event_data_t
#include // pe__const_top_resource(), etc.
#include // pcmk_injections_t
#include // pcmk__colocation_t
#ifdef __cplusplus
extern "C" {
#endif
// Colocation flags
enum pcmk__coloc_flags {
pcmk__coloc_none = 0U,
// Primary is affected even if already active
pcmk__coloc_influence = (1U << 0),
// Colocation was explicitly configured in CIB
pcmk__coloc_explicit = (1U << 1),
};
// Flags to modify the behavior of add_colocated_node_scores()
enum pcmk__coloc_select {
// With no other flags, apply all "with this" colocations
pcmk__coloc_select_default = 0,
// Apply "this with" colocations instead of "with this" colocations
pcmk__coloc_select_this_with = (1 << 0),
// Apply only colocations with non-negative scores
pcmk__coloc_select_nonnegative = (1 << 1),
// Apply only colocations with at least one matching node
pcmk__coloc_select_active = (1 << 2),
};
// Flags the update_ordered_actions() method can return
enum pcmk__updated {
pcmk__updated_none = 0, // Nothing changed
pcmk__updated_first = (1 << 0), // First action was updated
pcmk__updated_then = (1 << 1), // Then action was updated
};
#define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \
au_flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_set), #flags_to_set); \
} while (0)
#define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \
au_flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
// Resource assignment methods
struct pcmk__assignment_methods {
/*!
* \internal
* \brief Assign a resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and \p rsc can't be assigned to a
* node, set next role to stopped and update
* existing actions (if \p rsc is not a
* primitive, this applies to its primitive
* descendants instead)
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource()
* can completely undo the assignment. A successful assignment can be
* either undone or left alone as final. A failed assignment has the
* same effect as calling pcmk__unassign_resource(); there are no side
* effects on roles or actions.
*/
pcmk_node_t *(*assign)(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
/*!
* \internal
* \brief Create all actions needed for a given resource
*
* \param[in,out] rsc Resource to create actions for
*/
void (*create_actions)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Schedule any probes needed for a resource on a node
*
* \param[in,out] rsc Resource to create probe for
* \param[in,out] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool (*create_probe)(pcmk_resource_t *rsc, pcmk_node_t *node);
/*!
* \internal
* \brief Create implicit constraints needed for a resource
*
* \param[in,out] rsc Resource to create implicit constraints for
*/
void (*internal_constraints)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
+ *
+ * \return The score added to the dependent's priority
*/
- void (*apply_coloc_score)(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation,
- bool for_dependent);
+ int (*apply_coloc_score)(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation,
+ bool for_dependent);
/*!
* \internal
* \brief Create list of all resources in colocations with a given resource
*
* Given a resource, create a list of all resources involved in mandatory
* colocations with it, whether directly or via chained colocations.
*
* \param[in] rsc Resource to add to colocated list
* \param[in] orig_rsc Resource originally requested
* \param[in,out] colocated_rscs Existing list
*
* \return List of given resource and all resources involved in colocations
*
* \note This function is recursive; top-level callers should pass NULL as
* \p colocated_rscs and \p orig_rsc, and the desired resource as
* \p rsc. The recursive calls will use other values.
*/
GList *(*colocated_resources)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
/*!
* \internal
* \brief Add colocations affecting a resource as primary to a list
*
* Given a resource being assigned (\p orig_rsc) and a resource somewhere in
* its chain of ancestors (\p rsc, which may be \p orig_rsc), get
* colocations that affect the ancestor as primary and should affect the
* resource, and add them to a given list.
*
* \param[in] rsc Resource whose colocations should be added
* \param[in] orig_rsc Affected resource (\p rsc or a descendant)
* \param[in,out] list List of colocations to add to
*
* \note All arguments should be non-NULL.
* \note The pcmk__with_this_colocations() wrapper should usually be used
* instead of using this method directly.
*/
void (*with_this_colocations)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
/*!
* \internal
* \brief Add colocations affecting a resource as dependent to a list
*
* Given a resource being assigned (\p orig_rsc) and a resource somewhere in
* its chain of ancestors (\p rsc, which may be \p orig_rsc), get
* colocations that affect the ancestor as dependent and should affect the
* resource, and add them to a given list.
*
*
* \param[in] rsc Resource whose colocations should be added
* \param[in] orig_rsc Affected resource (\p rsc or a descendant)
* \param[in,out] list List of colocations to add to
*
* \note All arguments should be non-NULL.
* \note The pcmk__this_with_colocations() wrapper should usually be used
* instead of using this method directly.
*/
void (*this_with_colocations)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to
* \c NULL to copy allowed nodes from
* \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* source_rsc's own matching node scores
* will not be added, and \p *nodes must be
* \c NULL as well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation,
* and the \c pcmk__coloc_select_this_with flag are used together (and
* only by \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
*/
void (*add_colocated_node_scores)(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id, GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags);
/*!
* \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
*/
void (*apply_location)(pcmk_resource_t *rsc, pcmk__location_t *location);
/*!
* \internal
* \brief Return action flags for a given resource action
*
* \param[in,out] action Action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
* \note For primitives, this will be the same as action->flags regardless
* of node. For collective resources, the flags can differ due to
* multiple instances possibly being involved.
*/
uint32_t (*action_flags)(pcmk_action_t *action, const pcmk_node_t *node);
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions'
* flags (and runnable_before members if appropriate) as appropriate for the
* ordering. Effects may cascade to other orderings involving the actions as
* well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this
* node (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates
* (may include pcmk__action_optional to affect
* only mandatory actions and
* pcmk__action_runnable to affect only runnable
* actions)
* \param[in] type Group of enum pcmk__action_relation_flags
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t (*update_ordered_actions)(pcmk_action_t *first,
pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler);
/*!
* \internal
* \brief Output a summary of scheduled actions for a resource
*
* \param[in,out] rsc Resource to output actions for
*/
void (*output_actions)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Add a resource's actions to the transition graph
*
* \param[in,out] rsc Resource whose actions should be added
*/
void (*add_actions_to_graph)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Add meta-attributes relevant to transition graph actions to XML
*
* If a given resource supports variant-specific meta-attributes that are
* needed for transition graph actions, add them to a given XML element.
*
* \param[in] rsc Resource whose meta-attributes should be added
* \param[in,out] xml Transition graph action attributes XML to add to
*/
void (*add_graph_meta)(const pcmk_resource_t *rsc, xmlNode *xml);
/*!
* \internal
* \brief Add a resource's utilization to a table of utilization values
*
* This function is used when summing the utilization of a resource and all
* resources colocated with it, to determine whether a node has sufficient
* capacity. Given a resource and a table of utilization values, it will add
* the resource's utilization to the existing values, if the resource has
* not yet been assigned to a node.
*
* \param[in] rsc Resource with utilization to add
* \param[in] orig_rsc Resource being assigned (for logging only)
* \param[in] all_rscs List of all resources that will be summed
* \param[in,out] utilization Table of utilization values to add to
*/
void (*add_utilization)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization);
/*!
* \internal
* \brief Apply a shutdown lock for a resource, if appropriate
*
* \param[in,out] rsc Resource to check for shutdown lock
*/
void (*shutdown_lock)(pcmk_resource_t *rsc);
};
// Actions (pcmk_sched_actions.c)
G_GNUC_INTERNAL
void pcmk__update_action_for_orderings(pcmk_action_t *action,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
uint32_t pcmk__update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__log_action(const char *pre_text, const pcmk_action_t *action,
bool details);
G_GNUC_INTERNAL
pcmk_action_t *pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *name,
guint interval_ms,
const pcmk_node_t *node);
G_GNUC_INTERNAL
pcmk_action_t *pcmk__new_shutdown_action(pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_locks_rsc_to_node(const pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__deduplicate_action_inputs(pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__output_actions(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__check_action_config(pcmk_resource_t *rsc, pcmk_node_t *node,
const xmlNode *xml_op);
G_GNUC_INTERNAL
void pcmk__handle_rsc_config_changes(pcmk_scheduler_t *scheduler);
// Recurring actions (pcmk_sched_recurring.c)
G_GNUC_INTERNAL
void pcmk__create_recurring_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id,
const char *task, guint interval_ms,
const pcmk_node_t *node, const char *reason);
G_GNUC_INTERNAL
void pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task,
guint interval_ms, pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_is_recurring(const pcmk_action_t *action);
// Producing transition graphs (pcmk_graph_producer.c)
G_GNUC_INTERNAL
bool pcmk__graph_has_loop(const pcmk_action_t *init_action,
const pcmk_action_t *action,
pcmk__related_action_t *input);
G_GNUC_INTERNAL
void pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_graph(pcmk_scheduler_t *scheduler);
// Fencing (pcmk_sched_fencing.c)
G_GNUC_INTERNAL
void pcmk__order_vs_fence(pcmk_action_t *stonith_op,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__order_vs_unfence(const pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_action_t *action,
enum pcmk__action_relation_flags order);
G_GNUC_INTERNAL
void pcmk__fence_guest(pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__node_unfenced(const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_restart_vs_unfence(gpointer data, gpointer user_data);
// Injected scheduler inputs (pcmk_sched_injections.c)
G_GNUC_INTERNAL
void pcmk__inject_scheduler_input(pcmk_scheduler_t *scheduler, cib_t *cib,
const pcmk_injections_t *injections);
// Constraints of any type (pcmk_sched_constraints.c)
G_GNUC_INTERNAL
pcmk_resource_t *pcmk__find_constraint_resource(GList *rsc_list,
const char *id);
G_GNUC_INTERNAL
xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler,
const char *id, pcmk_resource_t **rsc,
pcmk__idref_t **tag);
G_GNUC_INTERNAL
bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
bool convert_rsc, const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler);
// Location constraints
G_GNUC_INTERNAL
void pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
pcmk__location_t *pcmk__new_location(const char *id, pcmk_resource_t *rsc,
int node_score, const char *discover_mode,
pcmk_node_t *foo_node);
G_GNUC_INTERNAL
void pcmk__apply_locations(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *constraint);
// Colocation constraints (pcmk_sched_colocation.c)
enum pcmk__coloc_affects {
pcmk__coloc_affects_nothing = 0,
pcmk__coloc_affects_location,
pcmk__coloc_affects_role,
};
G_GNUC_INTERNAL
const char *pcmk__colocation_node_attr(const pcmk_node_t *node,
const char *attr,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
enum pcmk__coloc_affects pcmk__colocation_affects(const pcmk_resource_t
*dependent,
const pcmk_resource_t
*primary,
const pcmk__colocation_t
*colocation,
bool preview);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
-void pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation);
+int pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id, GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__add_dependent_scores(gpointer data, gpointer user_data);
G_GNUC_INTERNAL
void pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
const GList *primary_nodes,
bool merge_scores);
G_GNUC_INTERNAL
void pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_this_with_list(GList **list, GList *addition,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_with_this_list(GList **list, GList *addition,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
GList *pcmk__with_this_colocations(const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
GList *pcmk__this_with_colocations(const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__new_colocation(const char *id, const char *node_attr, int score,
pcmk_resource_t *dependent, pcmk_resource_t *primary,
const char *dependent_role, const char *primary_role,
uint32_t flags);
G_GNUC_INTERNAL
void pcmk__block_colocation_dependents(pcmk_action_t *action);
/*!
* \internal
* \brief Check whether colocation's dependent preferences should be considered
*
* \param[in] colocation Colocation constraint
* \param[in] rsc Primary instance (normally this will be
* colocation->primary, which NULL will be treated as,
* but for clones or bundles with multiple instances
* this can be a particular instance)
*
* \return true if colocation influence should be effective, otherwise false
*/
static inline bool
pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc)
{
if (rsc == NULL) {
rsc = colocation->primary;
}
/* A bundle replica colocates its remote connection with its container,
* using a finite score so that the container can run on Pacemaker Remote
* nodes.
*
* Moving a connection is lightweight and does not interrupt the service,
* while moving a container is heavyweight and does interrupt the service,
* so don't move a clean, active container based solely on the preferences
* of its connection.
*
* This also avoids problematic scenarios where two containers want to
* perpetually swap places.
*/
if (pcmk_is_set(colocation->dependent->flags,
pcmk__rsc_remote_nesting_allowed)
&& !pcmk_is_set(rsc->flags, pcmk__rsc_failed)
&& pcmk__list_of_1(rsc->priv->active_nodes)) {
return false;
}
/* The dependent in a colocation influences the primary's location
* if the PCMK_XA_INFLUENCE option is true or the primary is not yet active.
*/
return pcmk_is_set(colocation->flags, pcmk__coloc_influence)
|| (rsc->priv->active_nodes == NULL);
}
// Ordering constraints (pcmk_sched_ordering.c)
G_GNUC_INTERNAL
void pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_task,
pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
char *then_task, pcmk_action_t *then_action,
uint32_t flags, pcmk_scheduler_t *sched);
G_GNUC_INTERNAL
void pcmk__unpack_ordering(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__disable_invalid_orderings(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__order_stops_before_shutdown(pcmk_node_t *node,
pcmk_action_t *shutdown_op);
G_GNUC_INTERNAL
void pcmk__apply_orderings(pcmk_scheduler_t *sched);
G_GNUC_INTERNAL
void pcmk__order_after_each(pcmk_action_t *after, GList *list);
/*!
* \internal
* \brief Create a new ordering between two resource actions
*
* \param[in,out] first_rsc Resource for 'first' action
* \param[in,out] first_task Action key for 'first' action
* \param[in] then_rsc Resource for 'then' action
* \param[in,out] then_task Action key for 'then' action
* \param[in] flags Group of enum pcmk__action_relation_flags
*/
#define pcmk__order_resource_actions(first_rsc, first_task, \
then_rsc, then_task, flags) \
pcmk__new_ordering((first_rsc), \
pcmk__op_key((first_rsc)->id, (first_task), 0), \
NULL, \
(then_rsc), \
pcmk__op_key((then_rsc)->id, (then_task), 0), \
NULL, (flags), (first_rsc)->priv->scheduler)
#define pcmk__order_starts(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), PCMK_ACTION_START, \
(rsc2), PCMK_ACTION_START, (flags))
#define pcmk__order_stops(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), PCMK_ACTION_STOP, \
(rsc2), PCMK_ACTION_STOP, (flags))
// Ticket constraints (pcmk_sched_tickets.c)
G_GNUC_INTERNAL
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
// Promotable clone resources (pcmk_sched_promotable.c)
G_GNUC_INTERNAL
void pcmk__add_promotion_scores(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__require_promotion_tickets(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__set_instance_roles(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_promotable_actions(pcmk_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__promotable_restart_ordering(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__order_promotable_instances(pcmk_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__update_dependent_with_promotable(const pcmk_resource_t *primary,
pcmk_resource_t *dependent,
const pcmk__colocation_t
*colocation);
G_GNUC_INTERNAL
-void pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
- pcmk_resource_t *dependent,
- const pcmk__colocation_t
- *colocation);
+int pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
+ pcmk_resource_t *dependent,
+ const pcmk__colocation_t
+ *colocation);
// Pacemaker Remote nodes (pcmk_sched_remote.c)
G_GNUC_INTERNAL
bool pcmk__is_failed_remote_node(const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_remote_connection_actions(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__rsc_corresponds_to_guest(const pcmk_resource_t *rsc,
const pcmk_node_t *node);
G_GNUC_INTERNAL
pcmk_node_t *pcmk__connection_host_for_action(const pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__substitute_remote_addr(pcmk_resource_t *rsc, GHashTable *params);
G_GNUC_INTERNAL
void pcmk__add_guest_meta_to_xml(xmlNode *args_xml,
const pcmk_action_t *action);
// Primitives (pcmk_sched_primitive.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__primitive_assign(pcmk_resource_t *rsc,
const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__primitive_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__primitive_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
uint32_t pcmk__primitive_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
-void pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation,
- bool for_dependent);
+int pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation,
+ bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_primitive_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__primitive_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__schedule_cleanup(pcmk_resource_t *rsc, const pcmk_node_t *node,
bool optional);
G_GNUC_INTERNAL
void pcmk__primitive_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__primitive_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__primitive_shutdown_lock(pcmk_resource_t *rsc);
// Groups (pcmk_sched_group.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__group_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__group_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
-void pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation,
- bool for_dependent);
+int pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation,
+ bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_group_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__group_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id,
GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__group_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *location);
G_GNUC_INTERNAL
uint32_t pcmk__group_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
uint32_t pcmk__group_update_ordered_actions(pcmk_action_t *first,
pcmk_action_t *then,
const pcmk_node_t *node,
uint32_t flags, uint32_t filter,
uint32_t type,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
GList *pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__group_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__group_shutdown_lock(pcmk_resource_t *rsc);
// Clones (pcmk_sched_clone.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__clone_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__clone_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__clone_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__clone_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
-void pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation,
- bool for_dependent);
+int pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation,
+ bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_clone_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__clone_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__clone_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *constraint);
G_GNUC_INTERNAL
uint32_t pcmk__clone_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__clone_add_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__clone_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__clone_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__clone_shutdown_lock(pcmk_resource_t *rsc);
// Bundles (pcmk_sched_bundle.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__bundle_assign(pcmk_resource_t *rsc,
const pcmk_node_t *prefer, bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__bundle_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__bundle_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
-void pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
- const pcmk_resource_t *primary,
- const pcmk__colocation_t *colocation,
- bool for_dependent);
+int pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
+ const pcmk_resource_t *primary,
+ const pcmk__colocation_t *colocation,
+ bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__bundle_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *constraint);
G_GNUC_INTERNAL
uint32_t pcmk__bundle_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__output_bundle_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc);
// Clone instances or bundle replica containers (pcmk_sched_instances.c)
G_GNUC_INTERNAL
void pcmk__assign_instances(pcmk_resource_t *collective, GList *instances,
int max_total, int max_per_node);
G_GNUC_INTERNAL
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);
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);
G_GNUC_INTERNAL
uint32_t pcmk__instance_update_ordered_actions(pcmk_action_t *first,
pcmk_action_t *then,
const pcmk_node_t *node,
uint32_t flags, uint32_t filter,
uint32_t type,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
uint32_t pcmk__collective_action_flags(pcmk_action_t *action,
const GList *instances,
const pcmk_node_t *node);
// Injections (pcmk_injections.c)
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
bool up);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
const char *resource,
const char *lrm_name,
const char *rclass,
const char *rtype,
const char *rprovider);
G_GNUC_INTERNAL
void pcmk__inject_failcount(pcmk__output_t *out, cib_t *cib_conn,
xmlNode *cib_node, const char *resource,
const char *task, guint interval_ms, int rc);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
lrmd_event_data_t *op, const char *node,
int target_rc);
// Nodes (pcmk_sched_nodes.c)
G_GNUC_INTERNAL
bool pcmk__node_available(const pcmk_node_t *node, bool consider_score,
bool consider_guest);
G_GNUC_INTERNAL
bool pcmk__any_node_available(GHashTable *nodes);
G_GNUC_INTERNAL
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
G_GNUC_INTERNAL
void pcmk__copy_node_tables(const pcmk_resource_t *rsc, GHashTable **copy);
G_GNUC_INTERNAL
void pcmk__restore_node_tables(pcmk_resource_t *rsc, GHashTable *backup);
G_GNUC_INTERNAL
GList *pcmk__sort_nodes(GList *nodes, pcmk_node_t *active_node);
G_GNUC_INTERNAL
void pcmk__apply_node_health(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
pcmk_node_t *pcmk__top_allowed_node(const pcmk_resource_t *rsc,
const pcmk_node_t *node);
// Functions applying to more than one variant (pcmk_sched_resource.c)
G_GNUC_INTERNAL
void pcmk__set_assignment_methods(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__rsc_agent_changed(pcmk_resource_t *rsc, pcmk_node_t *node,
const xmlNode *rsc_entry, bool active_on_node);
G_GNUC_INTERNAL
GList *pcmk__rscs_matching_id(const char *id,
const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
GList *pcmk__colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__noop_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__output_resource_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__assign_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool force,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__unassign_resource(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__threshold_reached(pcmk_resource_t *rsc, const pcmk_node_t *node,
pcmk_resource_t **failed);
G_GNUC_INTERNAL
void pcmk__sort_resources(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
gint pcmk__cmp_instance(gconstpointer a, gconstpointer b);
G_GNUC_INTERNAL
gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b);
// Functions related to probes (pcmk_sched_probes.c)
G_GNUC_INTERNAL
bool pcmk__probe_rsc_on_node(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_probes(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__probe_resource_list(GList *rscs, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__schedule_probes(pcmk_scheduler_t *scheduler);
// Functions related to live migration (pcmk_sched_migration.c)
void pcmk__create_migration_actions(pcmk_resource_t *rsc,
const pcmk_node_t *current);
void pcmk__abort_dangling_migration(void *data, void *user_data);
bool pcmk__rsc_can_migrate(const pcmk_resource_t *rsc,
const pcmk_node_t *current);
void pcmk__order_migration_equivalents(pcmk__action_relation_t *order);
// Functions related to node utilization (pcmk_sched_utilization.c)
G_GNUC_INTERNAL
int pcmk__compare_node_capacities(const pcmk_node_t *node1,
const pcmk_node_t *node2);
G_GNUC_INTERNAL
void pcmk__consume_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__release_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
const pcmk_node_t *pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
const GList *allowed_nodes);
G_GNUC_INTERNAL
void pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler);
// Functions related to the scheduler (pcmk_scheduler.c)
G_GNUC_INTERNAL
int pcmk__init_scheduler(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
pcmk_scheduler_t **scheduler);
// General setup functions (pcmk_setup.c)
G_GNUC_INTERNAL
int pcmk__setup_output_cib_sched(pcmk__output_t **out, cib_t **cib,
pcmk_scheduler_t **scheduler, xmlNode **xml);
#ifdef __cplusplus
}
#endif
#endif // PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_sched_bundle.c b/lib/pacemaker/pcmk_sched_bundle.c
index 0ee88113fa..34579ffde5 100644
--- a/lib/pacemaker/pcmk_sched_bundle.c
+++ b/lib/pacemaker/pcmk_sched_bundle.c
@@ -1,1064 +1,1075 @@
/*
* 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 "libpacemaker_private.h"
struct assign_data {
const pcmk_node_t *prefer;
bool stop_if_fail;
};
/*!
* \internal
* \brief Assign a single bundle replica's resources (other than container)
*
* \param[in,out] replica Replica to assign
* \param[in] user_data Preferred node, if any
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
assign_replica(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk_node_t *container_host = NULL;
struct assign_data *assign_data = user_data;
const pcmk_node_t *prefer = assign_data->prefer;
bool stop_if_fail = assign_data->stop_if_fail;
const pcmk_resource_t *bundle = pe__const_top_resource(replica->container,
true);
if (replica->ip != NULL) {
pcmk__rsc_trace(bundle, "Assigning bundle %s IP %s",
bundle->id, replica->ip->id);
replica->ip->priv->cmds->assign(replica->ip, prefer, stop_if_fail);
}
container_host = replica->container->priv->assigned_node;
if (replica->remote != NULL) {
if (pcmk__is_pacemaker_remote_node(container_host)) {
/* REMOTE_CONTAINER_HACK: "Nested" connection resources must be on
* the same host because Pacemaker Remote only supports a single
* active connection.
*/
pcmk__new_colocation("#replica-remote-with-host-remote", NULL,
PCMK_SCORE_INFINITY, replica->remote,
container_host->priv->remote, NULL,
NULL, pcmk__coloc_influence);
}
pcmk__rsc_trace(bundle, "Assigning bundle %s connection %s",
bundle->id, replica->remote->id);
replica->remote->priv->cmds->assign(replica->remote, prefer,
stop_if_fail);
}
if (replica->child != NULL) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, replica->child->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
if (!pcmk__same_node(node, replica->node)) {
node->assign->score = -PCMK_SCORE_INFINITY;
} else if (!pcmk__threshold_reached(replica->child, node, NULL)) {
node->assign->score = PCMK_SCORE_INFINITY;
}
}
pcmk__set_rsc_flags(replica->child->priv->parent,
pcmk__rsc_assigning);
pcmk__rsc_trace(bundle, "Assigning bundle %s replica child %s",
bundle->id, replica->child->id);
replica->child->priv->cmds->assign(replica->child, replica->node,
stop_if_fail);
pcmk__clear_rsc_flags(replica->child->priv->parent,
pcmk__rsc_assigning);
}
return true;
}
/*!
* \internal
* \brief Assign a bundle resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and a primitive descendant of \p rsc
* can't be assigned to a node, set the
* descendant's next role to stopped and update
* existing actions
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__bundle_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
GList *containers = NULL;
pcmk_resource_t *bundled_resource = NULL;
struct assign_data assign_data = { prefer, stop_if_fail };
CRM_ASSERT(pcmk__is_bundle(rsc));
pcmk__rsc_trace(rsc, "Assigning bundle %s", rsc->id);
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
pe__show_node_scores(!pcmk_is_set(rsc->priv->scheduler->flags,
pcmk__sched_output_scores),
rsc, __func__, rsc->priv->allowed_nodes,
rsc->priv->scheduler);
// Assign all containers first, so we know what nodes the bundle will be on
containers = g_list_sort(pe__bundle_containers(rsc), pcmk__cmp_instance);
pcmk__assign_instances(rsc, containers, pe__bundle_max(rsc),
rsc->priv->fns->max_per_node(rsc));
g_list_free(containers);
// Then assign remaining replica resources
pe__foreach_bundle_replica(rsc, assign_replica, (void *) &assign_data);
// Finally, assign the bundled resources to each bundle node
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, bundled_resource->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) {
if (pe__node_is_bundle_instance(rsc, node)) {
node->assign->score = 0;
} else {
node->assign->score = -PCMK_SCORE_INFINITY;
}
}
bundled_resource->priv->cmds->assign(bundled_resource, prefer,
stop_if_fail);
}
pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned);
return NULL;
}
/*!
* \internal
* \brief Create actions for a bundle replica's resources (other than child)
*
* \param[in,out] replica Replica to create actions for
* \param[in] user_data Unused
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
create_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
{
if (replica->ip != NULL) {
replica->ip->priv->cmds->create_actions(replica->ip);
}
if (replica->container != NULL) {
replica->container->priv->cmds->create_actions(replica->container);
}
if (replica->remote != NULL) {
replica->remote->priv->cmds->create_actions(replica->remote);
}
return true;
}
/*!
* \internal
* \brief Create all actions needed for a given bundle resource
*
* \param[in,out] rsc Bundle resource to create actions for
*/
void
pcmk__bundle_create_actions(pcmk_resource_t *rsc)
{
pcmk_action_t *action = NULL;
GList *containers = NULL;
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, create_replica_actions, NULL);
containers = pe__bundle_containers(rsc);
pcmk__create_instance_actions(rsc, containers);
g_list_free(containers);
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
bundled_resource->priv->cmds->create_actions(bundled_resource);
if (pcmk_is_set(bundled_resource->flags, pcmk__rsc_promotable)) {
pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTE, true, true);
action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTED,
true, true);
action->priority = PCMK_SCORE_INFINITY;
pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTE, true, true);
action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTED,
true, true);
action->priority = PCMK_SCORE_INFINITY;
}
}
}
/*!
* \internal
* \brief Create internal constraints for a bundle replica's resources
*
* \param[in,out] replica Replica to create internal constraints for
* \param[in,out] user_data Replica's parent bundle
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
replica_internal_constraints(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk_resource_t *bundle = user_data;
replica->container->priv->cmds->internal_constraints(replica->container);
// Start bundle -> start replica container
pcmk__order_starts(bundle, replica->container,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
// Stop bundle -> stop replica child and container
if (replica->child != NULL) {
pcmk__order_stops(bundle, replica->child,
pcmk__ar_then_implies_first_graphed);
}
pcmk__order_stops(bundle, replica->container,
pcmk__ar_then_implies_first_graphed);
// Start replica container -> bundle is started
pcmk__order_resource_actions(replica->container, PCMK_ACTION_START, bundle,
PCMK_ACTION_RUNNING,
pcmk__ar_first_implies_then_graphed);
// Stop replica container -> bundle is stopped
pcmk__order_resource_actions(replica->container, PCMK_ACTION_STOP, bundle,
PCMK_ACTION_STOPPED,
pcmk__ar_first_implies_then_graphed);
if (replica->ip != NULL) {
replica->ip->priv->cmds->internal_constraints(replica->ip);
// Replica IP address -> replica container (symmetric)
pcmk__order_starts(replica->ip, replica->container,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_guest_allowed);
pcmk__order_stops(replica->container, replica->ip,
pcmk__ar_then_implies_first|pcmk__ar_guest_allowed);
pcmk__new_colocation("#ip-with-container", NULL, PCMK_SCORE_INFINITY,
replica->ip, replica->container, NULL, NULL,
pcmk__coloc_influence);
}
if (replica->remote != NULL) {
/* This handles ordering and colocating remote relative to container
* (via "#resource-with-container"). Since IP is also ordered and
* colocated relative to the container, we don't need to do anything
* explicit here with IP.
*/
replica->remote->priv->cmds->internal_constraints(replica->remote);
}
if (replica->child != NULL) {
CRM_ASSERT(replica->remote != NULL);
// "Start remote then child" is implicit in scheduler's remote logic
}
return true;
}
/*!
* \internal
* \brief Create implicit constraints needed for a bundle resource
*
* \param[in,out] rsc Bundle resource to create implicit constraints for
*/
void
pcmk__bundle_internal_constraints(pcmk_resource_t *rsc)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, replica_internal_constraints, rsc);
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource == NULL) {
return;
}
// Start bundle -> start bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_START, bundled_resource,
PCMK_ACTION_START,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is started -> bundle is started
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_RUNNING,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_first_implies_then_graphed);
// Stop bundle -> stop bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP, bundled_resource,
PCMK_ACTION_STOP,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is stopped -> bundle is stopped
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_STOPPED,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_first_implies_then_graphed);
bundled_resource->priv->cmds->internal_constraints(bundled_resource);
if (!pcmk_is_set(bundled_resource->flags, pcmk__rsc_promotable)) {
return;
}
pcmk__promotable_restart_ordering(rsc);
// Demote bundle -> demote bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTE, bundled_resource,
PCMK_ACTION_DEMOTE,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is demoted -> bundle is demoted
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_DEMOTED,
rsc, PCMK_ACTION_DEMOTED,
pcmk__ar_first_implies_then_graphed);
// Promote bundle -> promote bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_PROMOTE,
bundled_resource, PCMK_ACTION_PROMOTE,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is promoted -> bundle is promoted
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_PROMOTED,
rsc, PCMK_ACTION_PROMOTED,
pcmk__ar_first_implies_then_graphed);
}
struct match_data {
const pcmk_node_t *node; // Node to compare against replica
pcmk_resource_t *container; // Replica container corresponding to node
};
/*!
* \internal
* \brief Check whether a replica container is assigned to a given node
*
* \param[in] replica Replica to check
* \param[in,out] user_data struct match_data with node to compare against
*
* \return true if the replica does not match (to indicate further replicas
* should be processed), otherwise false
*/
static bool
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)) {
match_data->container = replica->container;
return false; // Match found, don't bother searching further replicas
}
return true; // No match, keep searching
}
/*!
* \internal
* \brief Get the host to which a bundle node is assigned
*
* \param[in] node Possible bundle node to check
*
* \return Node to which the container for \p node is assigned if \p node is a
* bundle node, otherwise \p node itself
*/
static const pcmk_node_t *
get_bundle_node_host(const pcmk_node_t *node)
{
if (pcmk__is_bundle_node(node)) {
const pcmk_resource_t *container = NULL;
container = node->priv->remote->priv->launcher;
return container->priv->fns->location(container, NULL, 0);
}
return node;
}
/*!
* \internal
* \brief Find a bundle container compatible with a dependent resource
*
* \param[in] dependent Dependent resource in colocation with bundle
* \param[in] bundle Bundle that \p dependent is colocated with
*
* \return A container from \p bundle assigned to the same node as \p dependent
* if assigned, otherwise assigned to any of dependent's allowed nodes,
* otherwise NULL.
*/
static pcmk_resource_t *
compatible_container(const pcmk_resource_t *dependent,
const pcmk_resource_t *bundle)
{
GList *scratch = NULL;
struct match_data match_data = { NULL, NULL };
// If dependent is assigned, only check there
match_data.node = dependent->priv->fns->location(dependent, NULL, 0);
match_data.node = get_bundle_node_host(match_data.node);
if (match_data.node != NULL) {
pe__foreach_const_bundle_replica(bundle, match_replica_container,
&match_data);
return match_data.container;
}
// Otherwise, check for any of the dependent's allowed nodes
scratch = g_hash_table_get_values(dependent->priv->allowed_nodes);
scratch = pcmk__sort_nodes(scratch, NULL);
for (const GList *iter = scratch; iter != NULL; iter = iter->next) {
match_data.node = iter->data;
match_data.node = get_bundle_node_host(match_data.node);
if (match_data.node == NULL) {
continue;
}
pe__foreach_const_bundle_replica(bundle, match_replica_container,
&match_data);
if (match_data.container != NULL) {
break;
}
}
g_list_free(scratch);
return match_data.container;
}
struct coloc_data {
const pcmk__colocation_t *colocation;
pcmk_resource_t *dependent;
GList *container_hosts;
+ int priority_delta;
};
/*!
* \internal
* \brief Apply a colocation score to replica node scores or resource priority
*
* \param[in] replica Replica of primary bundle resource in colocation
* \param[in,out] user_data struct coloc_data for colocation being applied
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
replica_apply_coloc_score(const pcmk__bundle_replica_t *replica,
void *user_data)
{
struct coloc_data *coloc_data = user_data;
pcmk_node_t *chosen = NULL;
pcmk_resource_t *container = replica->container;
if (coloc_data->colocation->score < PCMK_SCORE_INFINITY) {
- container->priv->cmds->apply_coloc_score(coloc_data->dependent,
- container,
- coloc_data->colocation, false);
+ int priority_delta =
+ container->priv->cmds->apply_coloc_score(coloc_data->dependent,
+ container,
+ coloc_data->colocation,
+ false);
+
+ coloc_data->priority_delta =
+ pcmk__add_scores(coloc_data->priority_delta, priority_delta);
return true;
}
chosen = container->priv->fns->location(container, NULL, 0);
if ((chosen == NULL)
|| is_set_recursive(container, pcmk__rsc_blocked, true)) {
return true;
}
if ((coloc_data->colocation->primary_role >= pcmk_role_promoted)
&& ((replica->child == NULL)
|| (replica->child->priv->next_role < pcmk_role_promoted))) {
return true;
}
pcmk__rsc_trace(pe__const_top_resource(container, true),
"Allowing mandatory colocation %s using %s @%d",
coloc_data->colocation->id, pcmk__node_name(chosen),
chosen->assign->score);
coloc_data->container_hosts = g_list_prepend(coloc_data->container_hosts,
chosen);
return true;
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
- struct coloc_data coloc_data = { colocation, dependent, NULL };
+ struct coloc_data coloc_data = { colocation, dependent, NULL, 0 };
/* This should never be called for the bundle itself as a dependent.
* Instead, we add its colocation constraints to its containers and bundled
* primitive and call the apply_coloc_score() method for them as dependents.
*/
CRM_ASSERT(pcmk__is_bundle(primary) && pcmk__is_primitive(dependent)
&& (colocation != NULL) && !for_dependent);
if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
pcmk__rsc_trace(primary,
"Skipping applying colocation %s "
"because %s is still provisional",
colocation->id, primary->id);
- return;
+ return 0;
}
pcmk__rsc_trace(primary, "Applying colocation %s (%s with %s at %s)",
colocation->id, dependent->id, primary->id,
pcmk_readable_score(colocation->score));
/* If the constraint dependent is a clone or bundle, "dependent" here is one
* of its instances. Look for a compatible instance of this bundle.
*/
if (colocation->dependent->priv->variant > pcmk__rsc_variant_group) {
const pcmk_resource_t *primary_container = NULL;
primary_container = compatible_container(dependent, primary);
if (primary_container != NULL) { // Success, we found one
pcmk__rsc_debug(primary, "Pairing %s with %s",
dependent->id, primary_container->id);
- dependent->priv->cmds->apply_coloc_score(dependent,
- primary_container,
- colocation, true);
- } else if (colocation->score >= PCMK_SCORE_INFINITY) {
+ return dependent->priv->cmds->apply_coloc_score(dependent,
+ primary_container,
+ colocation, true);
+ }
+
+ if (colocation->score >= PCMK_SCORE_INFINITY) {
// Failure, and it's fatal
crm_notice("%s cannot run because there is no compatible "
"instance of %s to colocate with",
dependent->id, primary->id);
pcmk__assign_resource(dependent, NULL, true, true);
} else { // Failure, but we can ignore it
pcmk__rsc_debug(primary,
"%s cannot be colocated with any instance of %s",
dependent->id, primary->id);
}
- return;
+ return 0;
}
pe__foreach_const_bundle_replica(primary, replica_apply_coloc_score,
&coloc_data);
if (colocation->score >= PCMK_SCORE_INFINITY) {
pcmk__colocation_intersect_nodes(dependent, primary, colocation,
coloc_data.container_hosts, false);
}
g_list_free(coloc_data.container_hosts);
+ return coloc_data.priority_delta;
}
// Bundle implementation of pcmk__assignment_methods_t:with_this_colocations()
void
pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *bundled_rsc = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc) && (orig_rsc != NULL) && (list != NULL));
// The bundle itself and its containers always get its colocations
if ((orig_rsc == rsc)
|| pcmk_is_set(orig_rsc->flags, pcmk__rsc_replica_container)) {
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
return;
}
/* The bundled resource gets the colocations if it's promotable and we've
* begun choosing roles
*/
bundled_rsc = pe__bundled_resource(rsc);
if ((bundled_rsc == NULL)
|| !pcmk_is_set(bundled_rsc->flags, pcmk__rsc_promotable)
|| (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
return;
}
if (orig_rsc == bundled_rsc) {
if (pe__clone_flag_is_set(orig_rsc,
pcmk__clone_promotion_constrained)) {
/* orig_rsc is the clone and we're setting roles (or have already
* done so)
*/
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
}
} else if (!pcmk_is_set(orig_rsc->flags, pcmk__rsc_unassigned)) {
/* orig_rsc is an instance and is already assigned. If something
* requests colocations for orig_rsc now, it's for setting roles.
*/
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
}
}
// Bundle implementation of pcmk__assignment_methods_t:this_with_colocations()
void
pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *bundled_rsc = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc) && (orig_rsc != NULL) && (list != NULL));
// The bundle itself and its containers always get its colocations
if ((orig_rsc == rsc)
|| pcmk_is_set(orig_rsc->flags, pcmk__rsc_replica_container)) {
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
return;
}
/* The bundled resource gets the colocations if it's promotable and we've
* begun choosing roles
*/
bundled_rsc = pe__bundled_resource(rsc);
if ((bundled_rsc == NULL)
|| !pcmk_is_set(bundled_rsc->flags, pcmk__rsc_promotable)
|| (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
return;
}
if (orig_rsc == bundled_rsc) {
if (pe__clone_flag_is_set(orig_rsc,
pcmk__clone_promotion_constrained)) {
/* orig_rsc is the clone and we're setting roles (or have already
* done so)
*/
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
}
} else if (!pcmk_is_set(orig_rsc->flags, pcmk__rsc_unassigned)) {
/* orig_rsc is an instance and is already assigned. If something
* requests colocations for orig_rsc now, it's for setting roles.
*/
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
}
}
/*!
* \internal
* \brief Return action flags for a given bundle resource action
*
* \param[in,out] action Bundle resource action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__bundle_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
GList *containers = NULL;
uint32_t flags = 0;
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT((action != NULL) && pcmk__is_bundle(action->rsc));
bundled_resource = pe__bundled_resource(action->rsc);
if (bundled_resource != NULL) {
GList *children = bundled_resource->priv->children;
// Clone actions are done on the bundled clone resource, not container
switch (get_complex_task(bundled_resource, action->task)) {
case pcmk__action_unspecified:
case pcmk__action_notify:
case pcmk__action_notified:
case pcmk__action_promote:
case pcmk__action_promoted:
case pcmk__action_demote:
case pcmk__action_demoted:
return pcmk__collective_action_flags(action, children, node);
default:
break;
}
}
containers = pe__bundle_containers(action->rsc);
flags = pcmk__collective_action_flags(action, containers, node);
g_list_free(containers);
return flags;
}
/*!
* \internal
* \brief Apply a location constraint to a bundle replica
*
* \param[in,out] replica Replica to apply constraint to
* \param[in,out] user_data Location constraint to apply
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
apply_location_to_replica(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk__location_t *location = user_data;
replica->container->priv->cmds->apply_location(replica->container,
location);
if (replica->ip != NULL) {
replica->ip->priv->cmds->apply_location(replica->ip, location);
}
return true;
}
/*!
* \internal
* \brief Apply a location constraint to a bundle resource's allowed node scores
*
* \param[in,out] rsc Bundle resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void
pcmk__bundle_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT((location != NULL) && pcmk__is_bundle(rsc));
pcmk__apply_location(rsc, location);
pe__foreach_bundle_replica(rsc, apply_location_to_replica, location);
bundled_resource = pe__bundled_resource(rsc);
if ((bundled_resource != NULL)
&& ((location->role_filter == pcmk_role_unpromoted)
|| (location->role_filter == pcmk_role_promoted))) {
bundled_resource->priv->cmds->apply_location(bundled_resource,
location);
bundled_resource->priv->location_constraints =
g_list_prepend(bundled_resource->priv->location_constraints,
location);
}
}
#define XPATH_REMOTE "//nvpair[@name='" PCMK_REMOTE_RA_ADDR "']"
/*!
* \internal
* \brief Add a bundle replica's actions to transition graph
*
* \param[in,out] replica Replica to add to graph
* \param[in] user_data Bundle that replica belongs to (for logging only)
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
add_replica_actions_to_graph(pcmk__bundle_replica_t *replica, void *user_data)
{
if ((replica->remote != NULL)
&& pe__bundle_needs_remote_name(replica->remote)) {
/* REMOTE_CONTAINER_HACK: Allow remote nodes to run containers that
* run pacemaker-remoted inside, without needing a separate IP for
* the container. This is done by configuring the inner remote's
* connection host as the magic string "#uname", then
* replacing it with the underlying host when needed.
*/
xmlNode *nvpair = get_xpath_object(XPATH_REMOTE,
replica->remote->priv->xml, LOG_ERR);
const char *calculated_addr = NULL;
// Replace the value in replica->remote->xml (if appropriate)
calculated_addr = pe__add_bundle_remote_name(replica->remote, nvpair,
PCMK_XA_VALUE);
if (calculated_addr != NULL) {
/* Since this is for the bundle as a resource, and not any
* particular action, replace the value in the default
* parameters (not evaluated for node). create_graph_action()
* will grab it from there to replace it in node-evaluated
* parameters.
*/
GHashTable *params = NULL;
params = pe_rsc_params(replica->remote, NULL,
replica->remote->priv->scheduler);
pcmk__insert_dup(params, PCMK_REMOTE_RA_ADDR, calculated_addr);
} else {
pcmk_resource_t *bundle = user_data;
/* The only way to get here is if the remote connection is
* neither currently running nor scheduled to run. That means we
* won't be doing any operations that require addr (only start
* requires it; we additionally use it to compare digests when
* unpacking status, promote, and migrate_from history, but
* that's already happened by this point).
*/
pcmk__rsc_info(bundle,
"Unable to determine address for bundle %s "
"remote connection", bundle->id);
}
}
if (replica->ip != NULL) {
replica->ip->priv->cmds->add_actions_to_graph(replica->ip);
}
replica->container->priv->cmds->add_actions_to_graph(replica->container);
if (replica->remote != NULL) {
replica->remote->priv->cmds->add_actions_to_graph(replica->remote);
}
return true;
}
/*!
* \internal
* \brief Add a bundle resource's actions to the transition graph
*
* \param[in,out] rsc Bundle resource whose actions should be added
*/
void
pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
bundled_resource->priv->cmds->add_actions_to_graph(bundled_resource);
}
pe__foreach_bundle_replica(rsc, add_replica_actions_to_graph, rsc);
}
struct probe_data {
pcmk_resource_t *bundle; // Bundle being probed
pcmk_node_t *node; // Node to create probes on
bool any_created; // Whether any probes have been created
};
/*!
* \internal
* \brief Order a bundle replica's start after another replica's probe
*
* \param[in,out] replica Replica to order start for
* \param[in,out] user_data Replica with probe to order after
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
order_replica_start_after(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk__bundle_replica_t *probed_replica = user_data;
if ((replica == probed_replica) || (replica->container == NULL)) {
return true;
}
pcmk__new_ordering(probed_replica->container,
pcmk__op_key(probed_replica->container->id,
PCMK_ACTION_MONITOR, 0),
NULL, replica->container,
pcmk__op_key(replica->container->id, PCMK_ACTION_START,
0),
NULL, pcmk__ar_ordered|pcmk__ar_if_on_same_node,
replica->container->priv->scheduler);
return true;
}
/*!
* \internal
* \brief Create probes for a bundle replica's resources
*
* \param[in,out] replica Replica to create probes for
* \param[in,out] user_data struct probe_data
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
create_replica_probes(pcmk__bundle_replica_t *replica, void *user_data)
{
struct probe_data *probe_data = user_data;
pcmk_resource_t *bundle = probe_data->bundle;
if ((replica->ip != NULL)
&& replica->ip->priv->cmds->create_probe(replica->ip,
probe_data->node)) {
probe_data->any_created = true;
}
if ((replica->child != NULL)
&& pcmk__same_node(probe_data->node, replica->node)
&& replica->child->priv->cmds->create_probe(replica->child,
probe_data->node)) {
probe_data->any_created = true;
}
if (replica->container->priv->cmds->create_probe(replica->container,
probe_data->node)) {
probe_data->any_created = true;
/* If we're limited to one replica per host (due to
* the lack of an IP range probably), then we don't
* want any of our peer containers starting until
* we've established that no other copies are already
* running.
*
* Partly this is to ensure that the maximum replicas per host is
* observed, but also to ensure that the containers
* don't fail to start because the necessary port
* mappings (which won't include an IP for uniqueness)
* are already taken
*/
if (bundle->priv->fns->max_per_node(bundle) == 1) {
pe__foreach_bundle_replica(bundle, order_replica_start_after,
replica);
}
}
if ((replica->remote != NULL)
&& replica->remote->priv->cmds->create_probe(replica->remote,
probe_data->node)) {
/* Do not probe the remote resource until we know where the container is
* running. This is required for REMOTE_CONTAINER_HACK to correctly
* probe remote resources.
*/
char *probe_uuid = pcmk__op_key(replica->remote->id,
PCMK_ACTION_MONITOR, 0);
pcmk_action_t *probe = NULL;
probe = find_first_action(replica->remote->priv->actions, probe_uuid,
NULL, probe_data->node);
free(probe_uuid);
if (probe != NULL) {
probe_data->any_created = true;
pcmk__rsc_trace(bundle, "Ordering %s probe on %s",
replica->remote->id,
pcmk__node_name(probe_data->node));
pcmk__new_ordering(replica->container,
pcmk__op_key(replica->container->id,
PCMK_ACTION_START, 0),
NULL, replica->remote, NULL, probe,
pcmk__ar_nested_remote_probe,
bundle->priv->scheduler);
}
}
return true;
}
/*!
* \internal
*
* \brief Schedule any probes needed for a bundle resource on a node
*
* \param[in,out] rsc Bundle resource to create probes for
* \param[in,out] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool
pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node)
{
struct probe_data probe_data = { rsc, node, false };
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, create_replica_probes, &probe_data);
return probe_data.any_created;
}
/*!
* \internal
* \brief Output actions for one bundle replica
*
* \param[in,out] replica Replica to output actions for
* \param[in] user_data Unused
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
output_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
{
if (replica->ip != NULL) {
replica->ip->priv->cmds->output_actions(replica->ip);
}
replica->container->priv->cmds->output_actions(replica->container);
if (replica->remote != NULL) {
replica->remote->priv->cmds->output_actions(replica->remote);
}
if (replica->child != NULL) {
replica->child->priv->cmds->output_actions(replica->child);
}
return true;
}
/*!
* \internal
* \brief Output a summary of scheduled actions for a bundle resource
*
* \param[in,out] rsc Bundle resource to output actions for
*/
void
pcmk__output_bundle_actions(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, output_replica_actions, NULL);
}
// Bundle implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
pcmk_resource_t *container = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
/* All bundle replicas are identical, so using the utilization of the first
* is sufficient for any. Only the implicit container resource can have
* utilization values.
*/
container = pe__first_container(rsc);
if (container != NULL) {
container->priv->cmds->add_utilization(container, orig_rsc, all_rscs,
utilization);
}
}
// Bundle implementation of pcmk__assignment_methods_t:shutdown_lock()
void
pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_bundle(rsc));
// Bundles currently don't support shutdown locks
}
diff --git a/lib/pacemaker/pcmk_sched_clone.c b/lib/pacemaker/pcmk_sched_clone.c
index add8550164..022b7c12a0 100644
--- a/lib/pacemaker/pcmk_sched_clone.c
+++ b/lib/pacemaker/pcmk_sched_clone.c
@@ -1,737 +1,746 @@
/*
* 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 "libpacemaker_private.h"
/*!
* \internal
* \brief Assign a clone resource's instances to nodes
*
* \param[in,out] rsc Clone resource to assign
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and a primitive descendant of \p rsc
* can't be assigned to a node, set the
* descendant's next role to stopped and update
* existing actions
*
* \return NULL (clones are not assigned to a single node)
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__clone_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
GList *colocations = NULL;
CRM_ASSERT(pcmk__is_clone(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return NULL; // Assignment has already been done
}
// Detect assignment loops
if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
return NULL;
}
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
// If this clone is promotable, consider nodes' promotion scores
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
pcmk__add_promotion_scores(rsc);
}
// If this clone is colocated with any other resources, assign those first
colocations = pcmk__this_with_colocations(rsc);
for (GList *iter = colocations; iter != NULL; iter = iter->next) {
pcmk__colocation_t *constraint = (pcmk__colocation_t *) iter->data;
pcmk_resource_t *primary = constraint->primary;
pcmk__rsc_trace(rsc, "%s: Assigning colocation %s primary %s first",
rsc->id, constraint->id, primary->id);
primary->priv->cmds->assign(primary, prefer, stop_if_fail);
}
g_list_free(colocations);
// If any resources are colocated with this one, consider their preferences
colocations = pcmk__with_this_colocations(rsc);
g_list_foreach(colocations, pcmk__add_dependent_scores, rsc);
g_list_free(colocations);
pe__show_node_scores(!pcmk_is_set(rsc->priv->scheduler->flags,
pcmk__sched_output_scores),
rsc, __func__, rsc->priv->allowed_nodes,
rsc->priv->scheduler);
rsc->priv->children = g_list_sort(rsc->priv->children, pcmk__cmp_instance);
pcmk__assign_instances(rsc, rsc->priv->children, pe__clone_max(rsc),
pe__clone_node_max(rsc));
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
pcmk__set_instance_roles(rsc);
}
pcmk__clear_rsc_flags(rsc, pcmk__rsc_unassigned|pcmk__rsc_assigning);
pcmk__rsc_trace(rsc, "Assigned clone %s", rsc->id);
return NULL;
}
/*!
* \internal
* \brief Create all actions needed for a given clone resource
*
* \param[in,out] rsc Clone resource to create actions for
*/
void
pcmk__clone_create_actions(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_clone(rsc));
pcmk__rsc_trace(rsc, "Creating actions for clone %s", rsc->id);
pcmk__create_instance_actions(rsc, rsc->priv->children);
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
pcmk__create_promotable_actions(rsc);
}
}
/*!
* \internal
* \brief Create implicit constraints needed for a clone resource
*
* \param[in,out] rsc Clone resource to create implicit constraints for
*/
void
pcmk__clone_internal_constraints(pcmk_resource_t *rsc)
{
bool ordered = false;
CRM_ASSERT(pcmk__is_clone(rsc));
pcmk__rsc_trace(rsc, "Creating internal constraints for clone %s", rsc->id);
// Restart ordering: Stop -> stopped -> start -> started
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
rsc, PCMK_ACTION_START,
pcmk__ar_ordered);
pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_unrunnable_first_blocks);
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_unrunnable_first_blocks);
// Demoted -> stop and started -> promote
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
rsc, PCMK_ACTION_STOP,
pcmk__ar_ordered);
pcmk__order_resource_actions(rsc, PCMK_ACTION_RUNNING,
rsc, PCMK_ACTION_PROMOTE,
pcmk__ar_unrunnable_first_blocks);
}
ordered = pe__clone_is_ordered(rsc);
if (ordered) {
/* Ordered clone instances must start and stop by instance number. The
* instances might have been previously shuffled for assignment or
* promotion purposes, so re-sort them.
*/
rsc->priv->children = g_list_sort(rsc->priv->children,
pcmk__cmp_instance_number);
}
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
instance->priv->cmds->internal_constraints(instance);
// Start clone -> start instance -> clone started
pcmk__order_starts(rsc, instance, pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
pcmk__order_resource_actions(instance, PCMK_ACTION_START,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_first_implies_then_graphed);
// Stop clone -> stop instance -> clone stopped
pcmk__order_stops(rsc, instance, pcmk__ar_then_implies_first_graphed);
pcmk__order_resource_actions(instance, PCMK_ACTION_STOP,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_first_implies_then_graphed);
/* Instances of ordered clones must be started and stopped by instance
* number. Since only some instances may be starting or stopping, order
* each instance relative to every later instance.
*/
if (ordered) {
for (GList *later = iter->next;
later != NULL; later = later->next) {
pcmk__order_starts(instance, (pcmk_resource_t *) later->data,
pcmk__ar_ordered);
pcmk__order_stops((pcmk_resource_t *) later->data, instance,
pcmk__ar_ordered);
}
}
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
pcmk__order_promotable_instances(rsc);
}
}
/*!
* \internal
* \brief Check whether colocated resources can be interleaved
*
* \param[in] colocation Colocation constraint with clone as primary
*
* \return true if colocated resources can be interleaved, otherwise false
*/
static bool
can_interleave(const pcmk__colocation_t *colocation)
{
const pcmk_resource_t *primary = colocation->primary;
const pcmk_resource_t *dependent = colocation->dependent;
// Only colocations between clone or bundle resources use interleaving
if (dependent->priv->variant <= pcmk__rsc_variant_group) {
return false;
}
// Only the dependent needs to be marked for interleaving
if (!crm_is_true(g_hash_table_lookup(dependent->priv->meta,
PCMK_META_INTERLEAVE))) {
return false;
}
/* @TODO Do we actually care about multiple primary instances sharing a
* dependent instance?
*/
if (dependent->priv->fns->max_per_node(dependent)
!= primary->priv->fns->max_per_node(primary)) {
pcmk__config_err("Cannot interleave %s and %s because they do not "
"support the same number of instances per node",
dependent->id, primary->id);
return false;
}
return true;
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
const GList *iter = NULL;
+ int priority_delta = 0;
/* This should never be called for the clone itself as a dependent. Instead,
* we add its colocation constraints to its instances and call the
* apply_coloc_score() method for the instances as dependents.
*/
CRM_ASSERT(!for_dependent);
CRM_ASSERT((colocation != NULL) && pcmk__is_clone(primary)
&& pcmk__is_primitive(dependent));
if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
pcmk__rsc_trace(primary,
"Delaying processing colocation %s "
"because cloned primary %s is still provisional",
colocation->id, primary->id);
- return;
+ return 0;
}
pcmk__rsc_trace(primary, "Processing colocation %s (%s with clone %s @%s)",
colocation->id, dependent->id, primary->id,
pcmk_readable_score(colocation->score));
// Apply role-specific colocations
if (pcmk_is_set(primary->flags, pcmk__rsc_promotable)
&& (colocation->primary_role != pcmk_role_unknown)) {
if (pcmk_is_set(dependent->flags, pcmk__rsc_unassigned)) {
// We're assigning the dependent to a node
pcmk__update_dependent_with_promotable(primary, dependent,
colocation);
- return;
+ return 0;
}
if (colocation->dependent_role == pcmk_role_promoted) {
// We're choosing a role for the dependent
- pcmk__update_promotable_dependent_priority(primary, dependent,
- colocation);
- return;
+ return pcmk__update_promotable_dependent_priority(primary,
+ dependent,
+ colocation);
}
}
// Apply interleaved colocations
if (can_interleave(colocation)) {
const pcmk_resource_t *primary_instance = NULL;
primary_instance = pcmk__find_compatible_instance(dependent, primary,
pcmk_role_unknown,
false);
if (primary_instance != NULL) {
pcmk__rsc_debug(primary, "Interleaving %s with %s",
dependent->id, primary_instance->id);
- dependent->priv->cmds->apply_coloc_score(dependent,
- primary_instance,
- colocation, true);
- } else if (colocation->score >= PCMK_SCORE_INFINITY) {
+ return dependent->priv->cmds->apply_coloc_score(dependent,
+ primary_instance,
+ colocation, true);
+ }
+
+ if (colocation->score >= PCMK_SCORE_INFINITY) {
crm_notice("%s cannot run because it cannot interleave with "
"any instance of %s", dependent->id, primary->id);
pcmk__assign_resource(dependent, NULL, true, true);
} else {
pcmk__rsc_debug(primary,
"%s will not colocate with %s "
"because no instance can interleave with it",
dependent->id, primary->id);
}
- return;
+ return 0;
}
// Apply mandatory colocations
if (colocation->score >= PCMK_SCORE_INFINITY) {
GList *primary_nodes = NULL;
// Dependent can run only where primary will have unblocked instances
for (iter = primary->priv->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *instance = iter->data;
pcmk_node_t *chosen = NULL;
chosen = instance->priv->fns->location(instance, NULL, 0);
if ((chosen != NULL)
&& !is_set_recursive(instance, pcmk__rsc_blocked, TRUE)) {
pcmk__rsc_trace(primary, "Allowing %s: %s %d",
colocation->id, pcmk__node_name(chosen),
chosen->assign->score);
primary_nodes = g_list_prepend(primary_nodes, chosen);
}
}
pcmk__colocation_intersect_nodes(dependent, primary, colocation,
primary_nodes, false);
g_list_free(primary_nodes);
- return;
+ return 0;
}
// Apply optional colocations
for (iter = primary->priv->children; iter != NULL; iter = iter->next) {
const pcmk_resource_t *instance = iter->data;
+ int instance_delta = instance->priv->cmds->apply_coloc_score(dependent,
+ instance,
+ colocation,
+ false);
- instance->priv->cmds->apply_coloc_score(dependent, instance,
- colocation, false);
+ priority_delta = pcmk__add_scores(priority_delta, instance_delta);
}
+ return priority_delta;
}
// Clone implementation of pcmk__assignment_methods_t:with_this_colocations()
void
pcmk__with_clone_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
parent = rsc->priv->parent;
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
if (parent != NULL) {
parent->priv->cmds->with_this_colocations(parent, orig_rsc, list);
}
}
// Clone implementation of pcmk__assignment_methods_t:this_with_colocations()
void
pcmk__clone_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
parent = rsc->priv->parent;
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
if (parent != NULL) {
parent->priv->cmds->this_with_colocations(parent, orig_rsc, list);
}
}
/*!
* \internal
* \brief Return action flags for a given clone resource action
*
* \param[in,out] action Action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__clone_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
CRM_ASSERT((action != NULL) && pcmk__is_clone(action->rsc));
return pcmk__collective_action_flags(action, action->rsc->priv->children,
node);
}
/*!
* \internal
* \brief Apply a location constraint to a clone resource's allowed node scores
*
* \param[in,out] rsc Clone resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void
pcmk__clone_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
{
CRM_CHECK((location != NULL) && pcmk__is_clone(rsc), return);
pcmk__apply_location(rsc, location);
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
instance->priv->cmds->apply_location(instance, location);
}
}
// GFunc wrapper for calling the action_flags() resource method
static void
call_action_flags(gpointer data, gpointer user_data)
{
pcmk_resource_t *rsc = user_data;
rsc->priv->cmds->action_flags((pcmk_action_t *) data, NULL);
}
/*!
* \internal
* \brief Add a clone resource's actions to the transition graph
*
* \param[in,out] rsc Resource whose actions should be added
*/
void
pcmk__clone_add_actions_to_graph(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_clone(rsc));
g_list_foreach(rsc->priv->actions, call_action_flags, rsc);
pe__create_clone_notifications(rsc);
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
child_rsc->priv->cmds->add_actions_to_graph(child_rsc);
}
pcmk__add_rsc_actions_to_graph(rsc);
pe__free_clone_notification_data(rsc);
}
/*!
* \internal
* \brief Check whether a resource or any children have been probed on a node
*
* \param[in] rsc Resource to check
* \param[in] node Node to check
*
* \return true if \p node is in the probed_nodes table of \p rsc or any of its
* children, otherwise false
*/
static bool
rsc_probed_on(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
if (rsc->priv->children != NULL) {
for (GList *child_iter = rsc->priv->children;
child_iter != NULL; child_iter = child_iter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) child_iter->data;
if (rsc_probed_on(child, node)) {
return true;
}
}
return false;
}
if (rsc->priv->probed_nodes != NULL) {
GHashTableIter iter;
pcmk_node_t *known_node = NULL;
g_hash_table_iter_init(&iter, rsc->priv->probed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &known_node)) {
if (pcmk__same_node(node, known_node)) {
return true;
}
}
}
return false;
}
/*!
* \internal
* \brief Find clone instance that has been probed on given node
*
* \param[in] clone Clone resource to check
* \param[in] node Node to check
*
* \return Instance of \p clone that has been probed on \p node if any,
* otherwise NULL
*/
static pcmk_resource_t *
find_probed_instance_on(const pcmk_resource_t *clone, const pcmk_node_t *node)
{
for (GList *iter = clone->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
if (rsc_probed_on(instance, node)) {
return instance;
}
}
return NULL;
}
/*!
* \internal
* \brief Probe an anonymous clone on a node
*
* \param[in,out] clone Anonymous clone to probe
* \param[in,out] node Node to probe \p clone on
*/
static bool
probe_anonymous_clone(pcmk_resource_t *clone, pcmk_node_t *node)
{
// Check whether we already probed an instance on this node
pcmk_resource_t *child = find_probed_instance_on(clone, node);
// Otherwise, check if we plan to start an instance on this node
for (GList *iter = clone->priv->children;
(iter != NULL) && (child == NULL); iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
const pcmk_node_t *instance_node = NULL;
instance_node = instance->priv->fns->location(instance, NULL, 0);
if (pcmk__same_node(instance_node, node)) {
child = instance;
}
}
// Otherwise, use the first clone instance
if (child == NULL) {
child = clone->priv->children->data;
}
// Anonymous clones only need to probe a single instance
return child->priv->cmds->create_probe(child, node);
}
/*!
* \internal
* \brief Schedule any probes needed for a resource on a node
*
* \param[in,out] rsc Resource to create probe for
* \param[in,out] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool
pcmk__clone_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node)
{
CRM_ASSERT((node != NULL) && pcmk__is_clone(rsc));
if (pcmk_is_set(rsc->flags, pcmk__rsc_exclusive_probes)) {
/* The clone is configured to be probed only where a location constraint
* exists with PCMK_XA_RESOURCE_DISCOVERY set to exclusive.
*
* This check is not strictly necessary here since the instance's
* create_probe() method would also check, but doing it here is more
* efficient (especially for unique clones with a large number of
* instances), and affects the CRM_meta_notify_available_uname variable
* passed with notify actions.
*/
pcmk_node_t *allowed = g_hash_table_lookup(rsc->priv->allowed_nodes,
node->priv->id);
if ((allowed == NULL)
|| (allowed->assign->probe_mode != pcmk__probe_exclusive)) {
/* This node is not marked for resource discovery. Remove it from
* allowed nodes so that notifications contain only nodes that the
* clone can possibly run on.
*/
pcmk__rsc_trace(rsc,
"Skipping probe for %s on %s because resource has "
"exclusive discovery but is not allowed on node",
rsc->id, pcmk__node_name(node));
g_hash_table_remove(rsc->priv->allowed_nodes, node->priv->id);
return false;
}
}
rsc->priv->children = g_list_sort(rsc->priv->children,
pcmk__cmp_instance_number);
if (pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
return pcmk__probe_resource_list(rsc->priv->children, node);
} else {
return probe_anonymous_clone(rsc, node);
}
}
/*!
* \internal
* \brief Add meta-attributes relevant to transition graph actions to XML
*
* Add clone-specific meta-attributes needed for transition graph actions.
*
* \param[in] rsc Clone resource whose meta-attributes should be added
* \param[in,out] xml Transition graph action attributes XML to add to
*/
void
pcmk__clone_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml)
{
char *name = NULL;
CRM_ASSERT(pcmk__is_clone(rsc) && (xml != NULL));
name = crm_meta_name(PCMK_META_GLOBALLY_UNIQUE);
crm_xml_add(xml, name, pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
free(name);
name = crm_meta_name(PCMK_META_NOTIFY);
crm_xml_add(xml, name, pcmk__flag_text(rsc->flags, pcmk__rsc_notify));
free(name);
name = crm_meta_name(PCMK_META_CLONE_MAX);
crm_xml_add_int(xml, name, pe__clone_max(rsc));
free(name);
name = crm_meta_name(PCMK_META_CLONE_NODE_MAX);
crm_xml_add_int(xml, name, pe__clone_node_max(rsc));
free(name);
if (pcmk_is_set(rsc->flags, pcmk__rsc_promotable)) {
int promoted_max = pe__clone_promoted_max(rsc);
int promoted_node_max = pe__clone_promoted_node_max(rsc);
name = crm_meta_name(PCMK_META_PROMOTED_MAX);
crm_xml_add_int(xml, name, promoted_max);
free(name);
name = crm_meta_name(PCMK_META_PROMOTED_NODE_MAX);
crm_xml_add_int(xml, name, promoted_node_max);
free(name);
/* @COMPAT Maintain backward compatibility with resource agents that
* expect the old names (deprecated since 2.0.0).
*/
name = crm_meta_name(PCMK__META_PROMOTED_MAX_LEGACY);
crm_xml_add_int(xml, name, promoted_max);
free(name);
name = crm_meta_name(PCMK__META_PROMOTED_NODE_MAX_LEGACY);
crm_xml_add_int(xml, name, promoted_node_max);
free(name);
}
}
// Clone implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__clone_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
bool existing = false;
pcmk_resource_t *child = NULL;
CRM_ASSERT(pcmk__is_clone(rsc) && (orig_rsc != NULL)
&& (utilization != NULL));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
// Look for any child already existing in the list
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
child = (pcmk_resource_t *) iter->data;
if (g_list_find(all_rscs, child)) {
existing = true; // Keep checking remaining children
} else {
// If this is a clone of a group, look for group's members
for (GList *member_iter = child->priv->children;
member_iter != NULL; member_iter = member_iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) member_iter->data;
if (g_list_find(all_rscs, member) != NULL) {
// Add *child's* utilization, not group member's
child->priv->cmds->add_utilization(child, orig_rsc,
all_rscs, utilization);
existing = true;
break;
}
}
}
}
if (!existing && (rsc->priv->children != NULL)) {
// If nothing was found, still add first child's utilization
child = (pcmk_resource_t *) rsc->priv->children->data;
child->priv->cmds->add_utilization(child, orig_rsc, all_rscs,
utilization);
}
}
// Clone implementation of pcmk__assignment_methods_t:shutdown_lock()
void
pcmk__clone_shutdown_lock(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_clone(rsc));
return; // Clones currently don't support shutdown locks
}
diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c
index ad7a710abb..76776f11f0 100644
--- a/lib/pacemaker/pcmk_sched_colocation.c
+++ b/lib/pacemaker/pcmk_sched_colocation.c
@@ -1,1997 +1,2015 @@
/*
* 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 "crm/common/util.h"
#include "crm/common/xml_internal.h"
#include "crm/common/xml.h"
#include "libpacemaker_private.h"
// Used to temporarily mark a node as unusable
#define INFINITY_HACK (PCMK_SCORE_INFINITY * -100)
/*!
* \internal
* \brief Get the value of a colocation's node attribute
*
* \param[in] node Node on which to look up the attribute
* \param[in] attr Name of attribute to look up
* \param[in] rsc Resource on whose behalf to look up the attribute
*
* \return Value of \p attr on \p node or on the host of \p node, as appropriate
*/
const char *
pcmk__colocation_node_attr(const pcmk_node_t *node, const char *attr,
const pcmk_resource_t *rsc)
{
const char *target = NULL;
/* A resource colocated with a bundle or its primitive can't run on the
* bundle node itself (where only the primitive, if any, can run). Instead,
* we treat it as a colocation with the bundle's containers, so always look
* up colocation node attributes on the container host.
*/
if (pcmk__is_bundle_node(node) && pcmk__is_bundled(rsc)
&& (pe__const_top_resource(rsc, false) == pe__bundled_resource(rsc))) {
target = PCMK_VALUE_HOST;
} else if (rsc != NULL) {
target = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
}
return pcmk__node_attr(node, attr, target, pcmk__rsc_node_assigned);
}
/*!
* \internal
* \brief Compare two colocations according to priority
*
* Compare two colocations according to the order in which they should be
* considered, based on either their dependent resources or their primary
* resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose resource has higher priority
* * Colocation whose resource is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose resource is promotable, if both are clones
* * Colocation whose resource has lower ID in lexicographic order
*
* \param[in] colocation1 First colocation to compare
* \param[in] colocation2 Second colocation to compare
* \param[in] dependent If \c true, compare colocations by dependent
* priority; otherwise compare them by primary priority
*
* \return A negative number if \p colocation1 should be considered first,
* a positive number if \p colocation2 should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_colocation_priority(const pcmk__colocation_t *colocation1,
const pcmk__colocation_t *colocation2, bool dependent)
{
const pcmk_resource_t *rsc1 = NULL;
const pcmk_resource_t *rsc2 = NULL;
if (colocation1 == NULL) {
return 1;
}
if (colocation2 == NULL) {
return -1;
}
if (dependent) {
rsc1 = colocation1->dependent;
rsc2 = colocation2->dependent;
CRM_ASSERT(colocation1->primary != NULL);
} else {
rsc1 = colocation1->primary;
rsc2 = colocation2->primary;
CRM_ASSERT(colocation1->dependent != NULL);
}
CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
if (rsc1->priv->priority > rsc2->priv->priority) {
return -1;
}
if (rsc1->priv->priority < rsc2->priv->priority) {
return 1;
}
// Process clones before primitives and groups
if (rsc1->priv->variant > rsc2->priv->variant) {
return -1;
}
if (rsc1->priv->variant < rsc2->priv->variant) {
return 1;
}
/* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
* clones (probably unnecessary, but avoids having to update regression
* tests)
*/
if (pcmk__is_clone(rsc1)) {
if (pcmk_is_set(rsc1->flags, pcmk__rsc_promotable)
&& !pcmk_is_set(rsc2->flags, pcmk__rsc_promotable)) {
return -1;
}
if (!pcmk_is_set(rsc1->flags, pcmk__rsc_promotable)
&& pcmk_is_set(rsc2->flags, pcmk__rsc_promotable)) {
return 1;
}
}
return strcmp(rsc1->id, rsc2->id);
}
/*!
* \internal
* \brief Compare two colocations according to priority based on dependents
*
* Compare two colocations according to the order in which they should be
* considered, based on their dependent resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose resource has higher priority
* * Colocation whose resource is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose resource is promotable, if both are clones
* * Colocation whose resource has lower ID in lexicographic order
*
* \param[in] a First colocation to compare
* \param[in] b Second colocation to compare
*
* \return A negative number if \p a should be considered first,
* a positive number if \p b should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_dependent_priority(gconstpointer a, gconstpointer b)
{
return cmp_colocation_priority(a, b, true);
}
/*!
* \internal
* \brief Compare two colocations according to priority based on primaries
*
* Compare two colocations according to the order in which they should be
* considered, based on their primary resources -- preferring (in order):
* * Colocation that is not \c NULL
* * Colocation whose primary has higher priority
* * Colocation whose primary is of a higher-level variant
* (bundle > clone > group > primitive)
* * Colocation whose primary is promotable, if both are clones
* * Colocation whose primary has lower ID in lexicographic order
*
* \param[in] a First colocation to compare
* \param[in] b Second colocation to compare
*
* \return A negative number if \p a should be considered first,
* a positive number if \p b should be considered first,
* or 0 if order doesn't matter
*/
static gint
cmp_primary_priority(gconstpointer a, gconstpointer b)
{
return cmp_colocation_priority(a, b, false);
}
/*!
* \internal
* \brief Add a "this with" colocation constraint to a sorted list
*
* \param[in,out] list List of constraints to add \p colocation to
* \param[in] colocation Colocation constraint to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The list will be sorted using cmp_primary_priority().
*/
void
pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
pcmk__rsc_trace(rsc,
"Adding colocation %s (%s with %s using %s @%s) to "
"'this with' list for %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, colocation->node_attribute,
pcmk_readable_score(colocation->score), rsc->id);
*list = g_list_insert_sorted(*list, (gpointer) colocation,
cmp_primary_priority);
}
/*!
* \internal
* \brief Add a list of "this with" colocation constraints to a list
*
* \param[in,out] list List of constraints to add \p addition to
* \param[in] addition List of colocation constraints to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The lists must be pre-sorted by cmp_primary_priority().
*/
void
pcmk__add_this_with_list(GList **list, GList *addition,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (rsc != NULL));
pcmk__if_tracing(
{}, // Always add each colocation individually if tracing
{
if (*list == NULL) {
// Trivial case for efficiency if not tracing
*list = g_list_copy(addition);
return;
}
}
);
for (const GList *iter = addition; iter != NULL; iter = iter->next) {
pcmk__add_this_with(list, addition->data, rsc);
}
}
/*!
* \internal
* \brief Add a "with this" colocation constraint to a sorted list
*
* \param[in,out] list List of constraints to add \p colocation to
* \param[in] colocation Colocation constraint to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The list will be sorted using cmp_dependent_priority().
*/
void
pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
pcmk__rsc_trace(rsc,
"Adding colocation %s (%s with %s using %s @%s) to "
"'with this' list for %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, colocation->node_attribute,
pcmk_readable_score(colocation->score), rsc->id);
*list = g_list_insert_sorted(*list, (gpointer) colocation,
cmp_dependent_priority);
}
/*!
* \internal
* \brief Add a list of "with this" colocation constraints to a list
*
* \param[in,out] list List of constraints to add \p addition to
* \param[in] addition List of colocation constraints to add to \p list
* \param[in] rsc Resource whose colocations we're getting (for
* logging only)
*
* \note The lists must be pre-sorted by cmp_dependent_priority().
*/
void
pcmk__add_with_this_list(GList **list, GList *addition,
const pcmk_resource_t *rsc)
{
CRM_ASSERT((list != NULL) && (rsc != NULL));
pcmk__if_tracing(
{}, // Always add each colocation individually if tracing
{
if (*list == NULL) {
// Trivial case for efficiency if not tracing
*list = g_list_copy(addition);
return;
}
}
);
for (const GList *iter = addition; iter != NULL; iter = iter->next) {
pcmk__add_with_this(list, addition->data, rsc);
}
}
/*!
* \internal
* \brief Add orderings necessary for an anti-colocation constraint
*
* \param[in,out] first_rsc One resource in an anti-colocation
* \param[in] first_role Anti-colocation role of \p first_rsc
* \param[in] then_rsc Other resource in the anti-colocation
* \param[in] then_role Anti-colocation role of \p then_rsc
*/
static void
anti_colocation_order(pcmk_resource_t *first_rsc, int first_role,
pcmk_resource_t *then_rsc, int then_role)
{
const char *first_tasks[] = { NULL, NULL };
const char *then_tasks[] = { NULL, NULL };
/* Actions to make first_rsc lose first_role */
if (first_role == pcmk_role_promoted) {
first_tasks[0] = PCMK_ACTION_DEMOTE;
} else {
first_tasks[0] = PCMK_ACTION_STOP;
if (first_role == pcmk_role_unpromoted) {
first_tasks[1] = PCMK_ACTION_PROMOTE;
}
}
/* Actions to make then_rsc gain then_role */
if (then_role == pcmk_role_promoted) {
then_tasks[0] = PCMK_ACTION_PROMOTE;
} else {
then_tasks[0] = PCMK_ACTION_START;
if (then_role == pcmk_role_unpromoted) {
then_tasks[1] = PCMK_ACTION_DEMOTE;
}
}
for (int first_lpc = 0;
(first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) {
for (int then_lpc = 0;
(then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) {
pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc],
then_rsc, then_tasks[then_lpc],
pcmk__ar_if_required_on_same_node);
}
}
}
/*!
* \internal
* \brief Add a new colocation constraint to scheduler data
*
* \param[in] id XML ID for this constraint
* \param[in] node_attr Colocate by this attribute (NULL for #uname)
* \param[in] score Constraint score
* \param[in,out] dependent Resource to be colocated
* \param[in,out] primary Resource to colocate \p dependent with
* \param[in] dependent_role Current role of \p dependent
* \param[in] primary_role Current role of \p primary
* \param[in] flags Group of enum pcmk__coloc_flags
*/
void
pcmk__new_colocation(const char *id, const char *node_attr, int score,
pcmk_resource_t *dependent, pcmk_resource_t *primary,
const char *dependent_role, const char *primary_role,
uint32_t flags)
{
pcmk__colocation_t *new_con = NULL;
CRM_CHECK(id != NULL, return);
if ((dependent == NULL) || (primary == NULL)) {
pcmk__config_err("Ignoring colocation '%s' because resource "
"does not exist", id);
return;
}
if (score == 0) {
pcmk__rsc_trace(dependent,
"Ignoring colocation '%s' (%s with %s) because score is 0",
id, dependent->id, primary->id);
return;
}
new_con = pcmk__assert_alloc(1, sizeof(pcmk__colocation_t));
if (pcmk__str_eq(dependent_role, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
dependent_role = PCMK__ROLE_UNKNOWN;
}
if (pcmk__str_eq(primary_role, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
primary_role = PCMK__ROLE_UNKNOWN;
}
new_con->id = id;
new_con->dependent = dependent;
new_con->primary = primary;
new_con->score = score;
new_con->dependent_role = pcmk_parse_role(dependent_role);
new_con->primary_role = pcmk_parse_role(primary_role);
new_con->node_attribute = pcmk__s(node_attr, CRM_ATTR_UNAME);
new_con->flags = flags;
pcmk__add_this_with(&(dependent->priv->this_with_colocations), new_con,
dependent);
pcmk__add_with_this(&(primary->priv->with_this_colocations), new_con,
primary);
dependent->priv->scheduler->priv->colocation_constraints =
g_list_prepend(dependent->priv->scheduler->priv->colocation_constraints,
new_con);
if (score <= -PCMK_SCORE_INFINITY) {
anti_colocation_order(dependent, new_con->dependent_role, primary,
new_con->primary_role);
anti_colocation_order(primary, new_con->primary_role, dependent,
new_con->dependent_role);
}
}
/*!
* \internal
* \brief Return the boolean influence corresponding to configuration
*
* \param[in] coloc_id Colocation XML ID (for error logging)
* \param[in] rsc Resource involved in constraint (for default)
* \param[in] influence_s String value of \c PCMK_XA_INFLUENCE option
*
* \return \c pcmk__coloc_influence if string evaluates true, or string is
* \c NULL or invalid and resource's \c PCMK_META_CRITICAL option
* evaluates true, otherwise \c pcmk__coloc_none
*/
static uint32_t
unpack_influence(const char *coloc_id, const pcmk_resource_t *rsc,
const char *influence_s)
{
if (influence_s != NULL) {
int influence_i = 0;
if (crm_str_to_boolean(influence_s, &influence_i) < 0) {
pcmk__config_err("Constraint '%s' has invalid value for "
PCMK_XA_INFLUENCE " (using default)",
coloc_id);
} else {
return (influence_i == 0)? pcmk__coloc_none : pcmk__coloc_influence;
}
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_critical)) {
return pcmk__coloc_influence;
}
return pcmk__coloc_none;
}
static void
unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
const char *influence_s, pcmk_scheduler_t *scheduler)
{
xmlNode *xml_rsc = NULL;
pcmk_resource_t *other = NULL;
pcmk_resource_t *resource = NULL;
const char *set_id = pcmk__xe_id(set);
const char *role = crm_element_value(set, PCMK_XA_ROLE);
bool with_previous = false;
int local_score = score;
bool sequential = false;
uint32_t flags = pcmk__coloc_none;
const char *xml_rsc_id = NULL;
const char *score_s = crm_element_value(set, PCMK_XA_SCORE);
if (score_s) {
local_score = char2score(score_s);
}
if (local_score == 0) {
crm_trace("Ignoring colocation '%s' for set '%s' because score is 0",
coloc_id, set_id);
return;
}
/* @COMPAT The deprecated PCMK__XA_ORDERING attribute specifies whether
* resources in a positive-score set are colocated with the previous or next
* resource.
*/
if (pcmk__str_eq(crm_element_value(set, PCMK__XA_ORDERING),
PCMK__VALUE_GROUP,
pcmk__str_null_matches|pcmk__str_casei)) {
with_previous = true;
} else {
pcmk__warn_once(pcmk__wo_set_ordering,
"Support for '" PCMK__XA_ORDERING "' other than"
" '" PCMK__VALUE_GROUP "' in " PCMK_XE_RESOURCE_SET
" (such as %s) is deprecated and will be removed in a"
" future release",
set_id);
}
if ((pcmk__xe_get_bool_attr(set, PCMK_XA_SEQUENTIAL,
&sequential) == pcmk_rc_ok)
&& !sequential) {
return;
}
if (local_score > 0) {
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)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
resource =
pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (resource == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring %s and later resources in set %s: "
"No such resource", xml_rsc_id, set_id);
return;
}
if (other != NULL) {
flags = pcmk__coloc_explicit
| unpack_influence(coloc_id, resource, influence_s);
if (with_previous) {
pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
resource->id, other->id, set_id);
pcmk__new_colocation(set_id, NULL, local_score, resource,
other, role, role, flags);
} else {
pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
other->id, resource->id, set_id);
pcmk__new_colocation(set_id, NULL, local_score, other,
resource, role, role, flags);
}
}
other = resource;
}
} else {
/* Anti-colocating with every prior resource is
* the only way to ensure the intuitive result
* (i.e. that no one in the set can run with anyone else in the set)
*/
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)) {
xmlNode *xml_rsc_with = NULL;
xml_rsc_id = pcmk__xe_id(xml_rsc);
resource =
pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (resource == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring %s and later resources in set %s: "
"No such resource", xml_rsc_id, set_id);
return;
}
flags = pcmk__coloc_explicit
| unpack_influence(coloc_id, resource, influence_s);
for (xml_rsc_with = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
NULL, NULL);
xml_rsc_with != NULL;
xml_rsc_with = pcmk__xe_next_same(xml_rsc_with)) {
xml_rsc_id = pcmk__xe_id(xml_rsc_with);
if (pcmk__str_eq(resource->id, xml_rsc_id, pcmk__str_none)) {
break;
}
other =
pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
CRM_ASSERT(other != NULL); // We already processed it
pcmk__new_colocation(set_id, NULL, local_score,
resource, other, role, role, flags);
}
}
}
}
/*!
* \internal
* \brief Colocate two resource sets relative to each other
*
* \param[in] id Colocation XML ID
* \param[in] set1 Dependent set
* \param[in] set2 Primary set
* \param[in] score Colocation score
* \param[in] influence_s Value of colocation's \c PCMK_XA_INFLUENCE
* attribute
* \param[in,out] scheduler Scheduler data
*/
static void
colocate_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
int score, const char *influence_s,
pcmk_scheduler_t *scheduler)
{
xmlNode *xml_rsc = NULL;
pcmk_resource_t *rsc_1 = NULL;
pcmk_resource_t *rsc_2 = NULL;
const char *xml_rsc_id = NULL;
const char *role_1 = crm_element_value(set1, PCMK_XA_ROLE);
const char *role_2 = crm_element_value(set2, PCMK_XA_ROLE);
int rc = pcmk_rc_ok;
bool sequential = false;
uint32_t flags = pcmk__coloc_none;
if (score == 0) {
crm_trace("Ignoring colocation '%s' between sets %s and %s "
"because score is 0",
id, pcmk__xe_id(set1), pcmk__xe_id(set2));
return;
}
rc = pcmk__xe_get_bool_attr(set1, PCMK_XA_SEQUENTIAL, &sequential);
if ((rc != pcmk_rc_ok) || sequential) {
// Get the first one
xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL);
if (xml_rsc != NULL) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s with set %s "
"because first resource %s not found",
pcmk__xe_id(set1), pcmk__xe_id(set2),
xml_rsc_id);
return;
}
}
}
rc = pcmk__xe_get_bool_attr(set2, PCMK_XA_SEQUENTIAL, &sequential);
if ((rc != pcmk_rc_ok) || sequential) {
// Get the last one
for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
}
rsc_2 = pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s with set %s "
"because last resource %s not found",
pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id);
return;
}
}
if ((rsc_1 != NULL) && (rsc_2 != NULL)) { // Both sets are sequential
flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
flags);
} else if (rsc_1 != NULL) { // Only set1 is sequential
flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_2 = pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring set %s colocation with resource %s "
"in set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2, flags);
}
} else if (rsc_2 != NULL) { // Only set2 is sequential
for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource %s "
"with set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
flags = pcmk__coloc_explicit
| unpack_influence(id, rsc_1, influence_s);
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2, flags);
}
} else { // Neither set is sequential
for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
NULL);
xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
xmlNode *xml_rsc_2 = NULL;
xml_rsc_id = pcmk__xe_id(xml_rsc);
rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_1 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource %s "
"with set %s: No such resource",
pcmk__xe_id(set1), xml_rsc_id,
pcmk__xe_id(set2));
continue;
}
flags = pcmk__coloc_explicit
| unpack_influence(id, rsc_1, influence_s);
for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF,
NULL, NULL);
xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
xml_rsc_id = pcmk__xe_id(xml_rsc_2);
rsc_2 =
pcmk__find_constraint_resource(scheduler->priv->resources,
xml_rsc_id);
if (rsc_2 == NULL) {
// Should be possible only with validation disabled
pcmk__config_err("Ignoring colocation of set %s resource "
"%s with set %s resource %s: No such "
"resource",
pcmk__xe_id(set1), pcmk__xe_id(xml_rsc),
pcmk__xe_id(set2), xml_rsc_id);
continue;
}
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
role_1, role_2, flags);
}
}
}
}
static void
unpack_simple_colocation(xmlNode *xml_obj, const char *id,
const char *influence_s, pcmk_scheduler_t *scheduler)
{
int score_i = 0;
uint32_t flags = pcmk__coloc_none;
const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
const char *dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
const char *primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
const char *dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
const char *primary_role = crm_element_value(xml_obj,
PCMK_XA_WITH_RSC_ROLE);
const char *attr = crm_element_value(xml_obj, PCMK_XA_NODE_ATTRIBUTE);
const char *primary_instance = NULL;
const char *dependent_instance = NULL;
pcmk_resource_t *primary = NULL;
pcmk_resource_t *dependent = NULL;
primary = pcmk__find_constraint_resource(scheduler->priv->resources,
primary_id);
dependent = pcmk__find_constraint_resource(scheduler->priv->resources,
dependent_id);
// @COMPAT: Deprecated since 2.1.5
primary_instance = crm_element_value(xml_obj, PCMK__XA_WITH_RSC_INSTANCE);
dependent_instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
if (dependent_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");
}
if (primary_instance != NULL) {
pcmk__warn_once(pcmk__wo_coloc_inst,
"Support for " PCMK__XA_WITH_RSC_INSTANCE " is "
"deprecated and will be removed in a future release");
}
if (dependent == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", id, dependent_id);
return;
} else if (primary == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", id, primary_id);
return;
} else if ((dependent_instance != NULL) && !pcmk__is_clone(dependent)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
id, dependent_id, dependent_instance);
return;
} else if ((primary_instance != NULL) && !pcmk__is_clone(primary)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
id, primary_id, primary_instance);
return;
}
if (dependent_instance != NULL) {
dependent = find_clone_instance(dependent, dependent_instance);
if (dependent == NULL) {
pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
id, dependent_id, dependent_instance);
return;
}
}
if (primary_instance != NULL) {
primary = find_clone_instance(primary, primary_instance);
if (primary == NULL) {
pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
"'%s'", id, primary_id, primary_instance);
return;
}
}
if (pcmk__xe_attr_is_true(xml_obj, PCMK_XA_SYMMETRICAL)) {
pcmk__config_warn("The colocation constraint "
"'" PCMK_XA_SYMMETRICAL "' attribute has been "
"removed");
}
if (score) {
score_i = char2score(score);
}
flags = pcmk__coloc_explicit | unpack_influence(id, dependent, influence_s);
pcmk__new_colocation(id, attr, score_i, dependent, primary,
dependent_role, primary_role, flags);
}
// \return Standard Pacemaker return code
static int
unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
pcmk_scheduler_t *scheduler)
{
const char *id = NULL;
const char *dependent_id = NULL;
const char *primary_id = NULL;
const char *dependent_role = NULL;
const char *primary_role = NULL;
pcmk_resource_t *dependent = NULL;
pcmk_resource_t *primary = NULL;
pcmk__idref_t *dependent_tag = NULL;
pcmk__idref_t *primary_tag = NULL;
xmlNode *dependent_set = NULL;
xmlNode *primary_set = NULL;
bool any_sets = false;
*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_COLOCATION);
return pcmk_rc_ok;
}
dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
if ((dependent_id == NULL) || (primary_id == NULL)) {
return pcmk_rc_ok;
}
if (!pcmk__valid_resource_or_tag(scheduler, dependent_id, &dependent,
&dependent_tag)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", id, dependent_id);
return pcmk_rc_unpack_error;
}
if (!pcmk__valid_resource_or_tag(scheduler, primary_id, &primary,
&primary_tag)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", id, primary_id);
return pcmk_rc_unpack_error;
}
if ((dependent != NULL) && (primary != NULL)) {
/* Neither side references any template/tag. */
return pcmk_rc_ok;
}
if ((dependent_tag != NULL) && (primary_tag != NULL)) {
// A colocation constraint between two templates/tags makes no sense
pcmk__config_err("Ignoring constraint '%s' because two templates or "
"tags cannot be colocated", id);
return pcmk_rc_unpack_error;
}
dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE);
*expanded_xml = pcmk__xml_copy(NULL, xml_obj);
/* Convert dependent's template/tag reference into constraint
* PCMK_XE_RESOURCE_SET
*/
if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, PCMK_XA_RSC, true,
scheduler)) {
pcmk__xml_free(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (dependent_set != NULL) {
if (dependent_role != NULL) {
/* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
* PCMK_XA_ROLE
*/
crm_xml_add(dependent_set, PCMK_XA_ROLE, dependent_role);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
}
any_sets = true;
}
/* Convert primary's template/tag reference into constraint
* PCMK_XE_RESOURCE_SET
*/
if (!pcmk__tag_to_set(*expanded_xml, &primary_set, PCMK_XA_WITH_RSC, true,
scheduler)) {
pcmk__xml_free(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (primary_set != NULL) {
if (primary_role != NULL) {
/* Move PCMK_XA_WITH_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
* PCMK_XA_ROLE
*/
crm_xml_add(primary_set, PCMK_XA_ROLE, primary_role);
pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_WITH_RSC_ROLE);
}
any_sets = true;
}
if (any_sets) {
crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
} else {
pcmk__xml_free(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Parse a colocation constraint from XML into scheduler data
*
* \param[in,out] xml_obj Colocation constraint XML to unpack
* \param[in,out] scheduler Scheduler data to add constraint to
*/
void
pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
int score_i = 0;
xmlNode *set = NULL;
xmlNode *last = NULL;
xmlNode *orig_xml = NULL;
xmlNode *expanded_xml = NULL;
const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
const char *score = NULL;
const char *influence_s = NULL;
if (pcmk__str_empty(id)) {
pcmk__config_err("Ignoring " PCMK_XE_RSC_COLOCATION
" without " CRM_ATTR_ID);
return;
}
if (unpack_colocation_tags(xml_obj, &expanded_xml,
scheduler) != pcmk_rc_ok) {
return;
}
if (expanded_xml != NULL) {
orig_xml = xml_obj;
xml_obj = expanded_xml;
}
score = crm_element_value(xml_obj, PCMK_XA_SCORE);
if (score != NULL) {
score_i = char2score(score);
}
influence_s = crm_element_value(xml_obj, PCMK_XA_INFLUENCE);
for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
set != NULL; set = pcmk__xe_next_same(set)) {
set = pcmk__xe_resolve_idref(set, scheduler->input);
if (set == NULL) { // Configuration error, message already logged
if (expanded_xml != NULL) {
pcmk__xml_free(expanded_xml);
}
return;
}
if (pcmk__str_empty(pcmk__xe_id(set))) {
pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET
" without " CRM_ATTR_ID);
continue;
}
unpack_colocation_set(set, score_i, id, influence_s, scheduler);
if (last != NULL) {
colocate_rsc_sets(id, last, set, score_i, influence_s, scheduler);
}
last = set;
}
if (expanded_xml) {
pcmk__xml_free(expanded_xml);
xml_obj = orig_xml;
}
if (last == NULL) {
unpack_simple_colocation(xml_obj, id, influence_s, scheduler);
}
}
/*!
* \internal
* \brief Make actions of a given type unrunnable for a given resource
*
* \param[in,out] rsc Resource whose actions should be blocked
* \param[in] task Name of action to block
* \param[in] reason Unrunnable start action causing the block
*/
static void
mark_action_blocked(pcmk_resource_t *rsc, const char *task,
const pcmk_resource_t *reason)
{
GList *iter = NULL;
char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
for (iter = rsc->priv->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = iter->data;
if (pcmk_is_set(action->flags, pcmk__action_runnable)
&& pcmk__str_eq(action->task, task, pcmk__str_none)) {
pcmk__clear_action_flags(action, pcmk__action_runnable);
pe_action_set_reason(action, reason_text, false);
pcmk__block_colocation_dependents(action);
pcmk__update_action_for_orderings(action, rsc->priv->scheduler);
}
}
// If parent resource can't perform an action, neither can any children
for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason);
}
free(reason_text);
}
/*!
* \internal
* \brief If an action is unrunnable, block any relevant dependent actions
*
* If a given action is an unrunnable start or promote, block the start or
* promote actions of resources colocated with it, as appropriate to the
* colocations' configured roles.
*
* \param[in,out] action Action to check
*/
void
pcmk__block_colocation_dependents(pcmk_action_t *action)
{
GList *iter = NULL;
GList *colocations = NULL;
pcmk_resource_t *rsc = NULL;
bool is_start = false;
if (pcmk_is_set(action->flags, pcmk__action_runnable)) {
return; // Only unrunnable actions block dependents
}
is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none);
if (!is_start
&& !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
return; // Only unrunnable starts and promotes block dependents
}
CRM_ASSERT(action->rsc != NULL); // Start and promote are resource actions
/* If this resource is part of a collective resource, dependents are blocked
* only if all instances of the collective are unrunnable, so check the
* collective resource.
*/
rsc = uber_parent(action->rsc);
if (rsc->priv->parent != NULL) {
rsc = rsc->priv->parent; // Bundle
}
// Colocation fails only if entire primary can't reach desired role
for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *child = iter->data;
pcmk_action_t *child_action = NULL;
child_action = find_first_action(child->priv->actions, NULL,
action->task, NULL);
if ((child_action == NULL)
|| pcmk_is_set(child_action->flags, pcmk__action_runnable)) {
crm_trace("Not blocking %s colocation dependents because "
"at least %s has runnable %s",
rsc->id, child->id, action->task);
return; // At least one child can reach desired role
}
}
crm_trace("Blocking %s colocation dependents due to unrunnable %s %s",
rsc->id, action->rsc->id, action->task);
// Check each colocation where this resource is primary
colocations = pcmk__with_this_colocations(rsc);
for (iter = colocations; iter != NULL; iter = iter->next) {
pcmk__colocation_t *colocation = iter->data;
if (colocation->score < PCMK_SCORE_INFINITY) {
continue; // Only mandatory colocations block dependent
}
/* If the primary can't start, the dependent can't reach its colocated
* role, regardless of what the primary or dependent colocation role is.
*
* If the primary can't be promoted, the dependent can't reach its
* colocated role if the primary's colocation role is promoted.
*/
if (!is_start && (colocation->primary_role != pcmk_role_promoted)) {
continue;
}
// Block the dependent from reaching its colocated role
if (colocation->dependent_role == pcmk_role_promoted) {
mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE,
action->rsc);
} else {
mark_action_blocked(colocation->dependent, PCMK_ACTION_START,
action->rsc);
}
}
g_list_free(colocations);
}
/*!
* \internal
* \brief Get the resource to use for role comparisons
*
* A bundle replica includes a container and possibly an instance of the bundled
* resource. The dependent in a "with bundle" colocation is colocated with a
* particular bundle container. However, if the colocation includes a role, then
* the role must be checked on the bundled resource instance inside the
* container. The container itself will never be promoted; the bundled resource
* may be.
*
* If the given resource is a bundle replica container, return the resource
* inside it, if any. Otherwise, return the resource itself.
*
* \param[in] rsc Resource to check
*
* \return Resource to use for role comparisons
*/
static const pcmk_resource_t *
get_resource_for_role(const pcmk_resource_t *rsc)
{
if (pcmk_is_set(rsc->flags, pcmk__rsc_replica_container)) {
const pcmk_resource_t *child = pe__get_rsc_in_container(rsc);
if (child != NULL) {
return child;
}
}
return rsc;
}
/*!
* \internal
* \brief Determine how a colocation constraint should affect a resource
*
* Colocation constraints have different effects at different points in the
* scheduler sequence. Initially, they affect a resource's location; once that
* is determined, then for promotable clones they can affect a resource
* instance's role; after both are determined, the constraints no longer matter.
* Given a specific colocation constraint, check what has been done so far to
* determine what should be affected at the current point in the scheduler.
*
* \param[in] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
* \param[in] preview If true, pretend resources have already been assigned
*
* \return How colocation constraint should be applied at this point
*/
enum pcmk__coloc_affects
pcmk__colocation_affects(const pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation, bool preview)
{
const pcmk_resource_t *dependent_role_rsc = NULL;
const pcmk_resource_t *primary_role_rsc = NULL;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (!preview && pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
// Primary resource has not been assigned yet, so we can't do anything
return pcmk__coloc_affects_nothing;
}
dependent_role_rsc = get_resource_for_role(dependent);
primary_role_rsc = get_resource_for_role(primary);
if ((colocation->dependent_role >= pcmk_role_unpromoted)
&& (dependent_role_rsc->priv->parent != NULL)
&& pcmk_is_set(dependent_role_rsc->priv->parent->flags,
pcmk__rsc_promotable)
&& !pcmk_is_set(dependent_role_rsc->flags, pcmk__rsc_unassigned)) {
/* This is a colocation by role, and the dependent is a promotable clone
* that has already been assigned, so the colocation should now affect
* the role.
*/
return pcmk__coloc_affects_role;
}
if (!preview && !pcmk_is_set(dependent->flags, pcmk__rsc_unassigned)) {
/* The dependent resource has already been through assignment, so the
* constraint no longer matters.
*/
return pcmk__coloc_affects_nothing;
}
if ((colocation->dependent_role != pcmk_role_unknown)
&& (colocation->dependent_role != dependent_role_rsc->priv->next_role)) {
crm_trace("Skipping %scolocation '%s': dependent limited to %s role "
"but %s next role is %s",
((colocation->score < 0)? "anti-" : ""),
colocation->id, pcmk_role_text(colocation->dependent_role),
dependent_role_rsc->id,
pcmk_role_text(dependent_role_rsc->priv->next_role));
return pcmk__coloc_affects_nothing;
}
if ((colocation->primary_role != pcmk_role_unknown)
&& (colocation->primary_role != primary_role_rsc->priv->next_role)) {
crm_trace("Skipping %scolocation '%s': primary limited to %s role "
"but %s next role is %s",
((colocation->score < 0)? "anti-" : ""),
colocation->id, pcmk_role_text(colocation->primary_role),
primary_role_rsc->id,
pcmk_role_text(primary_role_rsc->priv->next_role));
return pcmk__coloc_affects_nothing;
}
return pcmk__coloc_affects_location;
}
/*!
* \internal
* \brief Apply colocation to dependent for assignment purposes
*
* Update the allowed node scores of the dependent resource in a colocation,
* for the purposes of assigning it to a node.
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
*/
void
pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *attr = colocation->node_attribute;
const char *value = NULL;
GHashTable *work = NULL;
GHashTableIter iter;
pcmk_node_t *node = NULL;
if (primary->priv->assigned_node != NULL) {
value = pcmk__colocation_node_attr(primary->priv->assigned_node,
attr, primary);
} else if (colocation->score < 0) {
// Nothing to do (anti-colocation with something that is not running)
return;
}
work = pcmk__copy_node_table(dependent->priv->allowed_nodes);
g_hash_table_iter_init(&iter, work);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (primary->priv->assigned_node == NULL) {
node->assign->score = pcmk__add_scores(-colocation->score,
node->assign->score);
pcmk__rsc_trace(dependent,
"Applied %s to %s score on %s (now %s after "
"subtracting %s because primary %s inactive)",
colocation->id, dependent->id,
pcmk__node_name(node),
pcmk_readable_score(node->assign->score),
pcmk_readable_score(colocation->score), primary->id);
continue;
}
if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent),
value, pcmk__str_casei)) {
/* Add colocation score only if optional (or minus infinity). A
* mandatory colocation is a requirement rather than a preference,
* so we don't need to consider it for relative assignment purposes.
* The resource will simply be forbidden from running on the node if
* the primary isn't active there (via the condition above).
*/
if (colocation->score < PCMK_SCORE_INFINITY) {
node->assign->score = pcmk__add_scores(colocation->score,
node->assign->score);
pcmk__rsc_trace(dependent,
"Applied %s to %s score on %s (now %s after "
"adding %s)",
colocation->id, dependent->id,
pcmk__node_name(node),
pcmk_readable_score(node->assign->score),
pcmk_readable_score(colocation->score));
}
continue;
}
if (colocation->score >= PCMK_SCORE_INFINITY) {
/* Only mandatory colocations are relevant when the colocation
* attribute doesn't match, because an attribute not matching is not
* a negative preference -- the colocation is simply relevant only
* where it matches.
*/
node->assign->score = -PCMK_SCORE_INFINITY;
pcmk__rsc_trace(dependent,
"Banned %s from %s because colocation %s attribute %s "
"does not match",
dependent->id, pcmk__node_name(node),
colocation->id, attr);
}
}
if ((colocation->score <= -PCMK_SCORE_INFINITY)
|| (colocation->score >= PCMK_SCORE_INFINITY)
|| pcmk__any_node_available(work)) {
g_hash_table_destroy(dependent->priv->allowed_nodes);
dependent->priv->allowed_nodes = work;
work = NULL;
} else {
pcmk__rsc_info(dependent,
"%s: Rolling back scores from %s (no available nodes)",
dependent->id, primary->id);
}
if (work != NULL) {
g_hash_table_destroy(work);
}
}
/*!
* \internal
* \brief Apply colocation to dependent for role purposes
*
* Update the priority of the dependent resource in a colocation, for the
* purposes of selecting its role
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *dependent_value = NULL;
const char *primary_value = NULL;
const char *attr = colocation->node_attribute;
int score_multiplier = 1;
+ int priority_delta = 0;
const pcmk_node_t *primary_node = NULL;
const pcmk_node_t *dependent_node = NULL;
- const pcmk_resource_t *primary_role_rsc = NULL;
-
- CRM_ASSERT((dependent != NULL) && (primary != NULL) &&
- (colocation != NULL));
+ CRM_ASSERT((dependent != NULL) && (primary != NULL)
+ && (colocation != NULL));
primary_node = primary->priv->assigned_node;
dependent_node = dependent->priv->assigned_node;
if ((primary_node == NULL) || (dependent_node == NULL)) {
- return;
+ return 0;
+ }
+
+ if (colocation->primary_role != pcmk_role_unknown) {
+ /* Colocation applies only if the primary's next role matches
+ *
+ * @TODO Why ignore a mandatory colocation in this case when we apply
+ * its negation in the mismatched value case?
+ */
+ const pcmk_resource_t *role_rsc = get_resource_for_role(primary);
+
+ if (colocation->primary_role != role_rsc->priv->next_role) {
+ return 0;
+ }
}
dependent_value = pcmk__colocation_node_attr(dependent_node, attr,
dependent);
primary_value = pcmk__colocation_node_attr(primary_node, attr, primary);
- primary_role_rsc = get_resource_for_role(primary);
-
if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
if ((colocation->score == PCMK_SCORE_INFINITY)
&& (colocation->dependent_role == pcmk_role_promoted)) {
- dependent->priv->priority = -PCMK_SCORE_INFINITY;
- }
- return;
- }
+ /* For a mandatory promoted-role colocation, mark the dependent node
+ * ineligible to promote the dependent if its attribute value
+ * doesn't match the primary node's
+ */
+ score_multiplier = -1;
- if ((colocation->primary_role != pcmk_role_unknown)
- && (colocation->primary_role != primary_role_rsc->priv->next_role)) {
- return;
- }
+ } else {
+ // Otherwise, ignore the colocation if attribute values don't match
+ return 0;
+ }
- if (colocation->dependent_role == pcmk_role_unpromoted) {
+ } else if (colocation->dependent_role == pcmk_role_unpromoted) {
+ /* Node attribute values matched, so we want to avoid promoting the
+ * dependent on this node
+ */
score_multiplier = -1;
}
- dependent->priv->priority =
- pcmk__add_scores(score_multiplier * colocation->score,
- dependent->priv->priority);
+ priority_delta = score_multiplier * colocation->score;
+ dependent->priv->priority = pcmk__add_scores(priority_delta,
+ dependent->priv->priority);
pcmk__rsc_trace(dependent,
- "Applied %s to %s promotion priority (now %s after %s %s)",
+ "Applied %s to %s promotion priority (now %s after %s %d)",
colocation->id, dependent->id,
pcmk_readable_score(dependent->priv->priority),
((score_multiplier == 1)? "adding" : "subtracting"),
- pcmk_readable_score(colocation->score));
+ colocation->score);
+
+ return priority_delta;
}
/*!
* \internal
* \brief Find score of highest-scored node that matches colocation attribute
*
* \param[in] colocation Colocation constraint being applied
* \param[in,out] rsc Resource whose allowed nodes should be searched
* \param[in] attr Colocation attribute name (must not be NULL)
* \param[in] value Colocation attribute value to require
*/
static int
best_node_score_matching_attr(const pcmk__colocation_t *colocation,
pcmk_resource_t *rsc, const char *attr,
const char *value)
{
GHashTable *allowed_nodes_orig = NULL;
GHashTableIter iter;
pcmk_node_t *node = NULL;
int best_score = -PCMK_SCORE_INFINITY;
const char *best_node = NULL;
if ((colocation != NULL) && (rsc == colocation->dependent)
&& pcmk_is_set(colocation->flags, pcmk__coloc_explicit)
&& pcmk__is_group(rsc->priv->parent)
&& (rsc != rsc->priv->parent->priv->children->data)) {
/* The resource is a user-configured colocation's explicit dependent,
* and a group member other than the first, which means the group's
* location constraint scores were not applied to it (see
* pcmk__group_apply_location()). Explicitly consider those scores now.
*
* @TODO This does leave one suboptimal case: if the group itself or
* another member other than the first is explicitly colocated with
* the same primary, the primary will count the group's location scores
* multiple times. This is much less likely than a single member being
* explicitly colocated, so it's an acceptable tradeoff for now.
*/
allowed_nodes_orig = rsc->priv->allowed_nodes;
rsc->priv->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig);
for (GList *loc_iter = rsc->priv->scheduler->priv->location_constraints;
loc_iter != NULL; loc_iter = loc_iter->next) {
pcmk__location_t *location = loc_iter->data;
if (location->rsc == rsc->priv->parent) {
rsc->priv->cmds->apply_location(rsc, location);
}
}
}
// Find best allowed node with matching attribute
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if ((node->assign->score > best_score)
&& pcmk__node_available(node, false, false)
&& pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc),
pcmk__str_casei)) {
best_score = node->assign->score;
best_node = node->priv->name;
}
}
if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) {
if (best_node == NULL) {
crm_info("No allowed node for %s matches node attribute %s=%s",
rsc->id, attr, value);
} else {
crm_info("Allowed node %s for %s had best score (%d) "
"of those matching node attribute %s=%s",
best_node, rsc->id, best_score, attr, value);
}
}
if (allowed_nodes_orig != NULL) {
g_hash_table_destroy(rsc->priv->allowed_nodes);
rsc->priv->allowed_nodes = allowed_nodes_orig;
}
return best_score;
}
/*!
* \internal
* \brief Check whether a resource is allowed only on a single node
*
* \param[in] rsc Resource to check
*
* \return \c true if \p rsc is allowed only on one node, otherwise \c false
*/
static bool
allowed_on_one(const pcmk_resource_t *rsc)
{
GHashTableIter iter;
pcmk_node_t *allowed_node = NULL;
int allowed_nodes = 0;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) {
if ((allowed_node->assign->score >= 0) && (++allowed_nodes > 1)) {
pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id);
return false;
}
}
pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id,
((allowed_nodes == 1)? "on a single node" : "nowhere"));
return (allowed_nodes == 1);
}
/*!
* \internal
* \brief Add resource's colocation matches to current node assignment scores
*
* For each node in a given table, if any of a given resource's allowed nodes
* have a matching value for the colocation attribute, add the highest of those
* nodes' scores to the node's score.
*
* \param[in,out] nodes Table of nodes with assignment scores so far
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p nodes
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; pass NULL to
* ignore stickiness and use default attribute)
* \param[in] factor Factor by which to multiply scores being added
* \param[in] only_positive Whether to add only positive scores
*/
static void
add_node_scores_matching_attr(GHashTable *nodes,
pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const pcmk__colocation_t *colocation,
float factor, bool only_positive)
{
GHashTableIter iter;
pcmk_node_t *node = NULL;
const char *attr = colocation->node_attribute;
// Iterate through each node
g_hash_table_iter_init(&iter, nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
float delta_f = 0;
int delta = 0;
int score = 0;
int new_score = 0;
const char *value = pcmk__colocation_node_attr(node, attr, target_rsc);
score = best_node_score_matching_attr(colocation, source_rsc, attr, value);
if ((factor < 0) && (score < 0)) {
/* If the dependent is anti-colocated, we generally don't want the
* primary to prefer nodes that the dependent avoids. That could
* lead to unnecessary shuffling of the primary when the dependent
* hits its migration threshold somewhere, for example.
*
* However, there are cases when it is desirable. If the dependent
* can't run anywhere but where the primary is, it would be
* worthwhile to move the primary for the sake of keeping the
* dependent active.
*
* We can't know that exactly at this point since we don't know
* where the primary will be assigned, but we can limit considering
* the preference to when the dependent is allowed only on one node.
* This is less than ideal for multiple reasons:
*
* - the dependent could be allowed on more than one node but have
* anti-colocation primaries on each;
* - the dependent could be a clone or bundle with multiple
* instances, and the dependent as a whole is allowed on multiple
* nodes but some instance still can't run
* - the dependent has considered node-specific criteria such as
* location constraints and stickiness by this point, but might
* have other factors that end up disallowing a node
*
* but the alternative is making the primary move when it doesn't
* need to.
*
* We also consider the primary's stickiness and influence, so the
* user has some say in the matter. (This is the configured primary,
* not a particular instance of the primary, but that doesn't matter
* unless stickiness uses a rule to vary by node, and that seems
* acceptable to ignore.)
*/
if ((colocation->primary->priv->stickiness >= -score)
|| !pcmk__colocation_has_influence(colocation, NULL)
|| !allowed_on_one(colocation->dependent)) {
crm_trace("%s: Filtering %d + %f * %d "
"(double negative disallowed)",
pcmk__node_name(node), node->assign->score, factor,
score);
continue;
}
}
if (node->assign->score == INFINITY_HACK) {
crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
pcmk__node_name(node), node->assign->score, factor,
score);
continue;
}
delta_f = factor * score;
// Round the number; see http://c-faq.com/fp/round.html
delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5));
/* Small factors can obliterate the small scores that are often actually
* used in configurations. If the score and factor are nonzero, ensure
* that the result is nonzero as well.
*/
if ((delta == 0) && (score != 0)) {
if (factor > 0.0) {
delta = 1;
} else if (factor < 0.0) {
delta = -1;
}
}
new_score = pcmk__add_scores(delta, node->assign->score);
if (only_positive && (new_score < 0) && (node->assign->score > 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d "
"(negative disallowed, marking node unusable)",
pcmk__node_name(node), node->assign->score, factor, score,
new_score);
node->assign->score = INFINITY_HACK;
continue;
}
if (only_positive && (new_score < 0) && (node->assign->score == 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
pcmk__node_name(node), node->assign->score, factor, score,
new_score);
continue;
}
crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node),
node->assign->score, factor, score, new_score);
node->assign->score = new_score;
}
}
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to \c NULL
* to copy allowed nodes from \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* source_rsc's own matching node scores
* will not be added, and \p *nodes must be \c NULL
* as well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
* the \c pcmk__coloc_select_this_with flag are used together (and only by
* \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
* \note This is the shared implementation of
* \c pcmk__assignment_methods_t:add_colocated_node_scores().
*/
void
pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id,
GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags)
{
GHashTable *work = NULL;
CRM_ASSERT((source_rsc != NULL) && (nodes != NULL)
&& ((colocation != NULL)
|| ((target_rsc == NULL) && (*nodes == NULL))));
if (log_id == NULL) {
log_id = source_rsc->id;
}
// Avoid infinite recursion
if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) {
pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
log_id, source_rsc->id);
return;
}
pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
if (*nodes == NULL) {
work = pcmk__copy_node_table(source_rsc->priv->allowed_nodes);
target_rsc = source_rsc;
} else {
const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative);
pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)",
log_id, (pos? "positive" : "all"), source_rsc->id, factor);
work = pcmk__copy_node_table(*nodes);
add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation,
factor, pos);
}
if (work == NULL) {
pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
return;
}
if (pcmk__any_node_available(work)) {
GList *colocations = NULL;
if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
colocations = pcmk__this_with_colocations(source_rsc);
pcmk__rsc_trace(source_rsc,
"Checking additional %d optional '%s with' "
"constraints",
g_list_length(colocations), source_rsc->id);
} else {
colocations = pcmk__with_this_colocations(source_rsc);
pcmk__rsc_trace(source_rsc,
"Checking additional %d optional 'with %s' "
"constraints",
g_list_length(colocations), source_rsc->id);
}
flags |= pcmk__coloc_select_active;
for (GList *iter = colocations; iter != NULL; iter = iter->next) {
pcmk__colocation_t *constraint = iter->data;
pcmk_resource_t *other = NULL;
float other_factor = factor * constraint->score
/ (float) PCMK_SCORE_INFINITY;
if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
other = constraint->primary;
} else if (!pcmk__colocation_has_influence(constraint, NULL)) {
continue;
} else {
other = constraint->dependent;
}
pcmk__rsc_trace(source_rsc,
"Optionally merging score of '%s' constraint "
"(%s with %s)",
constraint->id, constraint->dependent->id,
constraint->primary->id);
other->priv->cmds->add_colocated_node_scores(other, target_rsc,
log_id, &work,
constraint,
other_factor, flags);
pe__show_node_scores(true, NULL, log_id, work,
source_rsc->priv->scheduler);
}
g_list_free(colocations);
} else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s",
log_id, source_rsc->id);
g_hash_table_destroy(work);
pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
return;
}
if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, work);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (node->assign->score == INFINITY_HACK) {
node->assign->score = 1;
}
}
}
if (*nodes != NULL) {
g_hash_table_destroy(*nodes);
}
*nodes = work;
pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
}
/*!
* \internal
* \brief Apply a "with this" colocation to a resource's allowed node scores
*
* \param[in,out] data Colocation to apply
* \param[in,out] user_data Resource being assigned
*/
void
pcmk__add_dependent_scores(gpointer data, gpointer user_data)
{
pcmk__colocation_t *colocation = data;
pcmk_resource_t *primary = user_data;
pcmk_resource_t *dependent = colocation->dependent;
const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
uint32_t flags = pcmk__coloc_select_active;
if (!pcmk__colocation_has_influence(colocation, NULL)) {
return;
}
if (pcmk__is_clone(primary)) {
flags |= pcmk__coloc_select_nonnegative;
}
pcmk__rsc_trace(primary,
"%s: Incorporating attenuated %s assignment scores due "
"to colocation %s",
primary->id, dependent->id, colocation->id);
dependent->priv->cmds->add_colocated_node_scores(dependent, primary,
dependent->id,
&(primary->priv->allowed_nodes),
colocation, factor, flags);
}
/*!
* \internal
* \brief Exclude nodes from a dependent's node table if not in a given list
*
* Given a dependent resource in a colocation and a list of nodes where the
* primary resource will run, set a node's score to \c -INFINITY in the
* dependent's node table if not found in the primary nodes list.
*
* \param[in,out] dependent Dependent resource
* \param[in] primary Primary resource (for logging only)
* \param[in] colocation Colocation constraint (for logging only)
* \param[in] primary_nodes List of nodes where the primary will have
* unblocked instances in a suitable role
* \param[in] merge_scores If \c true and a node is found in both \p table
* and \p list, add the node's score in \p list to
* the node's score in \p table
*/
void
pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
const GList *primary_nodes, bool merge_scores)
{
GHashTableIter iter;
pcmk_node_t *dependent_node = NULL;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
g_hash_table_iter_init(&iter, dependent->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) {
const pcmk_node_t *primary_node = NULL;
primary_node = pe_find_node_id(primary_nodes,
dependent_node->priv->id);
if (primary_node == NULL) {
dependent_node->assign->score = -PCMK_SCORE_INFINITY;
pcmk__rsc_trace(dependent,
"Banning %s from %s (no primary instance) for %s",
dependent->id, pcmk__node_name(dependent_node),
colocation->id);
} else if (merge_scores) {
dependent_node->assign->score =
pcmk__add_scores(dependent_node->assign->score,
primary_node->assign->score);
pcmk__rsc_trace(dependent,
"Added %s's score %s to %s's score for %s (now %d) "
"for colocation %s",
primary->id,
pcmk_readable_score(primary_node->assign->score),
dependent->id, pcmk__node_name(dependent_node),
dependent_node->assign->score, colocation->id);
}
}
}
/*!
* \internal
* \brief Get all colocations affecting a resource as the primary
*
* \param[in] rsc Resource to get colocations for
*
* \return Newly allocated list of colocations affecting \p rsc as primary
*
* \note This is a convenience wrapper for the with_this_colocations() method.
*/
GList *
pcmk__with_this_colocations(const pcmk_resource_t *rsc)
{
GList *list = NULL;
rsc->priv->cmds->with_this_colocations(rsc, rsc, &list);
return list;
}
/*!
* \internal
* \brief Get all colocations affecting a resource as the dependent
*
* \param[in] rsc Resource to get colocations for
*
* \return Newly allocated list of colocations affecting \p rsc as dependent
*
* \note This is a convenience wrapper for the this_with_colocations() method.
*/
GList *
pcmk__this_with_colocations(const pcmk_resource_t *rsc)
{
GList *list = NULL;
rsc->priv->cmds->this_with_colocations(rsc, rsc, &list);
return list;
}
diff --git a/lib/pacemaker/pcmk_sched_group.c b/lib/pacemaker/pcmk_sched_group.c
index 74f7e54142..0cb84d21cd 100644
--- a/lib/pacemaker/pcmk_sched_group.c
+++ b/lib/pacemaker/pcmk_sched_group.c
@@ -1,1011 +1,1051 @@
/*
* 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 // QB_ABS()
+
#include
#include
#include "libpacemaker_private.h"
/*!
* \internal
* \brief Assign a group resource to a node
*
* \param[in,out] rsc Group resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and a child of \p rsc can't be
* assigned to a node, set the child's next role to
* stopped and update existing actions
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
pcmk_node_t *first_assigned_node = NULL;
pcmk_resource_t *first_member = NULL;
CRM_ASSERT(pcmk__is_group(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return rsc->priv->assigned_node; // Assignment already done
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s",
rsc->id);
return NULL;
}
if (rsc->priv->children == NULL) {
// No members to assign
pcmk__clear_rsc_flags(rsc, pcmk__rsc_unassigned);
return NULL;
}
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
first_member = (pcmk_resource_t *) rsc->priv->children->data;
rsc->priv->orig_role = first_member->priv->orig_role;
pe__show_node_scores(!pcmk_is_set(rsc->priv->scheduler->flags,
pcmk__sched_output_scores),
rsc, __func__, rsc->priv->allowed_nodes,
rsc->priv->scheduler);
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
pcmk_node_t *node = NULL;
pcmk__rsc_trace(rsc, "Assigning group %s member %s",
rsc->id, member->id);
node = member->priv->cmds->assign(member, prefer, stop_if_fail);
if (first_assigned_node == NULL) {
first_assigned_node = node;
}
}
pe__set_next_role(rsc, first_member->priv->next_role,
"first group member");
pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned);
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
return NULL;
}
return first_assigned_node;
}
/*!
* \internal
* \brief Create a pseudo-operation for a group as an ordering point
*
* \param[in,out] group Group resource to create action for
* \param[in] action Action name
*
* \return Newly created pseudo-operation
*/
static pcmk_action_t *
create_group_pseudo_op(pcmk_resource_t *group, const char *action)
{
pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
action, NULL, TRUE,
group->priv->scheduler);
pcmk__set_action_flags(op, pcmk__action_pseudo|pcmk__action_runnable);
return op;
}
/*!
* \internal
* \brief Create all actions needed for a given group resource
*
* \param[in,out] rsc Group resource to create actions for
*/
void
pcmk__group_create_actions(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_group(rsc));
pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id);
// Create actions for individual group members
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
member->priv->cmds->create_actions(member);
}
// Create pseudo-actions for group itself to serve as ordering points
create_group_pseudo_op(rsc, PCMK_ACTION_START);
create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING);
create_group_pseudo_op(rsc, PCMK_ACTION_STOP);
create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED);
if (crm_is_true(g_hash_table_lookup(rsc->priv->meta,
PCMK_META_PROMOTABLE))) {
create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE);
create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED);
create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE);
create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED);
}
}
// User data for member_internal_constraints()
struct member_data {
// These could be derived from member but this avoids some function calls
bool ordered;
bool colocated;
bool promotable;
pcmk_resource_t *last_active;
pcmk_resource_t *previous_member;
};
/*!
* \internal
* \brief Create implicit constraints needed for a group member
*
* \param[in,out] data Group member to create implicit constraints for
* \param[in,out] user_data Member data (struct member_data *)
*/
static void
member_internal_constraints(gpointer data, gpointer user_data)
{
pcmk_resource_t *member = (pcmk_resource_t *) data;
struct member_data *member_data = (struct member_data *) user_data;
// For ordering demote vs demote or stop vs stop
uint32_t down_flags = pcmk__ar_then_implies_first_graphed;
// For ordering demote vs demoted or stop vs stopped
uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed;
// Create the individual member's implicit constraints
member->priv->cmds->internal_constraints(member);
if (member_data->previous_member == NULL) {
// This is first member
if (member_data->ordered) {
pcmk__set_relation_flags(down_flags, pcmk__ar_ordered);
post_down_flags = pcmk__ar_first_implies_then;
}
} else if (member_data->colocated) {
uint32_t flags = pcmk__coloc_none;
if (pcmk_is_set(member->flags, pcmk__rsc_critical)) {
flags |= pcmk__coloc_influence;
}
// Colocate this member with the previous one
pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY,
member, member_data->previous_member, NULL, NULL,
flags);
}
if (member_data->promotable) {
// Demote group -> demote member -> group is demoted
pcmk__order_resource_actions(member->priv->parent,
PCMK_ACTION_DEMOTE,
member, PCMK_ACTION_DEMOTE, down_flags);
pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
member->priv->parent,
PCMK_ACTION_DEMOTED, post_down_flags);
// Promote group -> promote member -> group is promoted
pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE,
member->priv->parent,
PCMK_ACTION_PROMOTED,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_first_implies_then
|pcmk__ar_first_implies_then_graphed);
pcmk__order_resource_actions(member->priv->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_then_implies_first_graphed);
}
// Stop group -> stop member -> group is stopped
pcmk__order_stops(member->priv->parent, member, down_flags);
pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
member->priv->parent, PCMK_ACTION_STOPPED,
post_down_flags);
// Start group -> start member -> group is started
pcmk__order_starts(member->priv->parent, member,
pcmk__ar_then_implies_first_graphed);
pcmk__order_resource_actions(member, PCMK_ACTION_START,
member->priv->parent, PCMK_ACTION_RUNNING,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_first_implies_then
|pcmk__ar_first_implies_then_graphed);
if (!member_data->ordered) {
pcmk__order_starts(member->priv->parent, member,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
if (member_data->promotable) {
pcmk__order_resource_actions(member->priv->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
}
} else if (member_data->previous_member == NULL) {
pcmk__order_starts(member->priv->parent, member, pcmk__ar_none);
if (member_data->promotable) {
pcmk__order_resource_actions(member->priv->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_none);
}
} else {
// Order this member relative to the previous one
pcmk__order_starts(member_data->previous_member, member,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks);
pcmk__order_stops(member, member_data->previous_member,
pcmk__ar_ordered|pcmk__ar_intermediate_stop);
/* In unusual circumstances (such as adding a new member to the middle
* of a group with unmanaged later members), this member may be active
* while the previous (new) member is inactive. In this situation, the
* usual restart orderings will be irrelevant, so we need to order this
* member's stop before the previous member's start.
*/
if ((member->priv->active_nodes != NULL)
&& (member_data->previous_member->priv->active_nodes == NULL)) {
pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
member_data->previous_member,
PCMK_ACTION_START,
pcmk__ar_then_implies_first
|pcmk__ar_unrunnable_first_blocks);
}
if (member_data->promotable) {
pcmk__order_resource_actions(member_data->previous_member,
PCMK_ACTION_PROMOTE, member,
PCMK_ACTION_PROMOTE,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks);
pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
member_data->previous_member,
PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
}
}
// Make sure partially active groups shut down in sequence
if (member->priv->active_nodes != NULL) {
if (member_data->ordered && (member_data->previous_member != NULL)
&& (member_data->previous_member->priv->active_nodes == NULL)
&& (member_data->last_active != NULL)
&& (member_data->last_active->priv->active_nodes != NULL)) {
pcmk__order_stops(member, member_data->last_active,
pcmk__ar_ordered);
}
member_data->last_active = member;
}
member_data->previous_member = member;
}
/*!
* \internal
* \brief Create implicit constraints needed for a group resource
*
* \param[in,out] rsc Group resource to create implicit constraints for
*/
void
pcmk__group_internal_constraints(pcmk_resource_t *rsc)
{
struct member_data member_data = { false, };
const pcmk_resource_t *top = NULL;
CRM_ASSERT(pcmk__is_group(rsc));
/* Order group pseudo-actions relative to each other for restarting:
* stop group -> group is stopped -> start group -> group is started
*/
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_unrunnable_first_blocks);
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
rsc, PCMK_ACTION_START,
pcmk__ar_ordered);
pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_unrunnable_first_blocks);
top = pe__const_top_resource(rsc, false);
member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered);
member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated);
member_data.promotable = pcmk_is_set(top->flags, pcmk__rsc_promotable);
g_list_foreach(rsc->priv->children, member_internal_constraints,
&member_data);
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint for a group with some other resource, apply the
* score to the dependent's allowed node scores (if we are still placing
* resources) or priority (if we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent group resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
+ *
+ * \return The score added to the dependent's priority
*/
-static void
+static int
colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
- pcmk_resource_t *member = NULL;
+ int priority_delta = 0;
if (dependent->priv->children == NULL) {
- return;
+ return 0;
}
pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
colocation->id, dependent->id, primary->id);
if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) {
// Colocate first member (internal colocations will handle the rest)
- member = (pcmk_resource_t *) dependent->priv->children->data;
- member->priv->cmds->apply_coloc_score(member, primary, colocation,
- true);
- return;
- }
+ pcmk_resource_t *member = dependent->priv->children->data;
+ priority_delta = member->priv->cmds->apply_coloc_score(member, primary,
+ colocation,
+ true);
- if (colocation->score >= PCMK_SCORE_INFINITY) {
- pcmk__config_err("%s: Cannot perform mandatory colocation between "
- "non-colocated group and %s",
- dependent->id, primary->id);
- return;
- }
+ } else {
+ if (colocation->score >= PCMK_SCORE_INFINITY) {
+ pcmk__config_err("%s: Cannot perform mandatory colocation between "
+ "non-colocated group and %s",
+ dependent->id, primary->id);
+ return 0;
+ }
- // Colocate each member individually
- for (GList *iter = dependent->priv->children;
- iter != NULL; iter = iter->next) {
+ // Colocate each member individually
+ for (GList *iter = dependent->priv->children; iter != NULL;
+ iter = iter->next) {
- member = (pcmk_resource_t *) iter->data;
- member->priv->cmds->apply_coloc_score(member, primary, colocation,
- true);
+ int instance_delta = 0;
+ pcmk_resource_t *member = iter->data;
+
+ instance_delta =
+ member->priv->cmds->apply_coloc_score(member, primary,
+ colocation, false);
+
+ /* priority_delta is used for determining which instances of a
+ * promotable clone to promote. It's possible that colocations
+ * involving promotable cloned non-colocated groups may not behave
+ * correctly in all circumstances. Non-colocated groups are
+ * deprecated, and testing focused on colocated groups.
+ */
+ priority_delta = pcmk__add_scores(priority_delta, instance_delta);
+ }
}
+
+ if (priority_delta != 0) {
+ dependent->priv->priority =
+ pcmk__add_scores(priority_delta, dependent->priv->priority);
+
+ pcmk__rsc_trace(dependent,
+ "Applied %s to %s promotion priority "
+ "(now %s after %s %d)",
+ colocation->id, dependent->id,
+ pcmk_readable_score(dependent->priv->priority),
+ ((priority_delta > 0)? "adding" : "subtracting"),
+ QB_ABS(priority_delta));
+ }
+ return priority_delta;
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint for some other resource with a group, apply the
* score to the dependent's allowed node scores (if we are still placing
* resources) or priority (if we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary group resource in colocation
* \param[in] colocation Colocation constraint to apply
+ *
+ * \return The score added to the dependent's priority
*/
-static void
+static int
colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
+ int priority_delta = 0;
const pcmk_resource_t *member = NULL;
pcmk__rsc_trace(primary,
"Processing colocation %s (%s with group %s) for primary",
colocation->id, dependent->id, primary->id);
if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
- return;
+ return 0;
}
if (pe__group_flag_is_set(primary, pcmk__group_colocated)) {
if (colocation->score >= PCMK_SCORE_INFINITY) {
/* For mandatory colocations, the entire group must be assignable
* (and in the specified role if any), so apply the colocation based
* on the last member.
*/
member = pe__last_group_member(primary);
} else if (primary->priv->children != NULL) {
/* For optional colocations, whether the group is partially or fully
* up doesn't matter, so apply the colocation based on the first
* member.
*/
member = (pcmk_resource_t *) primary->priv->children->data;
}
if (member == NULL) {
- return; // Nothing to colocate with
+ return 0; // Nothing to colocate with
}
- member->priv->cmds->apply_coloc_score(dependent, member, colocation,
- false);
- return;
+ return member->priv->cmds->apply_coloc_score(dependent, member,
+ colocation, false);
}
if (colocation->score >= PCMK_SCORE_INFINITY) {
pcmk__config_err("%s: Cannot perform mandatory colocation with"
" non-colocated group %s",
dependent->id, primary->id);
- return;
+ return 0;
}
// Colocate dependent with each member individually
for (const GList *iter = primary->priv->children;
iter != NULL; iter = iter->next) {
+ int instance_delta = 0;
+
member = iter->data;
- member->priv->cmds->apply_coloc_score(dependent, member, colocation,
- false);
+ instance_delta = member->priv->cmds->apply_coloc_score(dependent,
+ member,
+ colocation,
+ false);
+ priority_delta = pcmk__add_scores(priority_delta, instance_delta);
}
+ return priority_delta;
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (for_dependent) {
- colocate_group_with(dependent, primary, colocation);
+ return colocate_group_with(dependent, primary, colocation);
} else {
// Method should only be called for primitive dependents
CRM_ASSERT(pcmk__is_primitive(dependent));
- colocate_with_group(dependent, primary, colocation);
+ return colocate_with_group(dependent, primary, colocation);
}
}
/*!
* \internal
* \brief Return action flags for a given group resource action
*
* \param[in,out] action Group action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
// Default flags for a group action
uint32_t flags = pcmk__action_optional
|pcmk__action_runnable
|pcmk__action_pseudo;
CRM_ASSERT(action != NULL);
// Update flags considering each member's own flags for same action
for (GList *iter = action->rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
// Check whether member has the same action
enum pcmk__action_type task = get_complex_task(member, action->task);
const char *task_s = pcmk__action_text(task);
pcmk_action_t *member_action = NULL;
member_action = find_first_action(member->priv->actions, NULL,
task_s, node);
if (member_action != NULL) {
uint32_t member_flags = 0U;
member_flags = member->priv->cmds->action_flags(member_action,
node);
// Group action is mandatory if any member action is
if (pcmk_is_set(flags, pcmk__action_optional)
&& !pcmk_is_set(member_flags, pcmk__action_optional)) {
pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is",
action->uuid, member_action->uuid);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk__action_optional);
pcmk__clear_action_flags(action, pcmk__action_optional);
}
// Group action is unrunnable if any member action is
if (!pcmk__str_eq(task_s, action->task, pcmk__str_none)
&& pcmk_is_set(flags, pcmk__action_runnable)
&& !pcmk_is_set(member_flags, pcmk__action_runnable)) {
pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is",
action->uuid, member_action->uuid);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk__action_runnable);
pcmk__clear_action_flags(action, pcmk__action_runnable);
}
/* Group (pseudo-)actions other than stop or demote are unrunnable
* unless every member will do it.
*/
} else if ((task != pcmk__action_stop)
&& (task != pcmk__action_demote)) {
pcmk__rsc_trace(action->rsc,
"%s is not runnable because %s will not %s",
action->uuid, member->id, task_s);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk__action_runnable);
}
}
return flags;
}
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions' flags
* (and runnable_before members if appropriate) as appropriate for the ordering.
* Effects may cascade to other orderings involving the actions as well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk__action_optional to affect only
* mandatory actions, and pcmk__action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler)
{
uint32_t changed = pcmk__updated_none;
// Group method can be called only on behalf of "then" action
CRM_ASSERT((first != NULL) && (then != NULL) && (then->rsc != NULL)
&& (scheduler != NULL));
// Update the actions for the group itself
changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
type, scheduler);
// Update the actions for each group member
for (GList *iter = then->rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
pcmk_action_t *member_action = NULL;
member_action = find_first_action(member->priv->actions, NULL,
then->task, node);
if (member_action == NULL) {
continue;
}
changed |= member->priv->cmds->update_ordered_actions(first,
member_action,
node, flags,
filter, type,
scheduler);
}
return changed;
}
/*!
* \internal
* \brief Apply a location constraint to a group's allowed node scores
*
* \param[in,out] rsc Group resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void
pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
{
GList *node_list_orig = NULL;
GList *node_list_copy = NULL;
CRM_ASSERT(pcmk__is_group(rsc) && (location != NULL));
// Save the constraint's original node list (with the constraint score)
node_list_orig = location->nodes;
// Make a copy of the nodes with all zero scores
node_list_copy = pcmk__copy_node_list(node_list_orig, true);
/* Apply the constraint to the group itself. This ensures that any nodes
* affected by the constraint are in the group's allowed nodes, with the
* constraint score added.
*/
pcmk__apply_location(rsc, location);
// Apply the constraint for each member
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
&& (iter != rsc->priv->children)) {
/* When apply_location() is called below for the first member (iter
* == rsc->priv->children), the constraint score will be added to
* the member's affected allowed nodes.
*
* For subsequent members, we reset the constraint's node table to
* the copy with all 0 scores. Otherwise, when assigning the member,
* the constraint score would be counted multiple times (once for
* each later member) due to internal group colocations. Though the
* 0 score will not affect these members' allowed node scores, it
* ensures that affected nodes are in each member's allowed nodes,
* enabling the member on those nodes in asymmetric clusters.
*/
location->nodes = node_list_copy;
}
member->priv->cmds->apply_location(member, location);
}
location->nodes = node_list_orig;
g_list_free_full(node_list_copy, free);
}
// Group implementation of pcmk__assignment_methods_t:colocated_resources()
GList *
pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs)
{
CRM_ASSERT(pcmk__is_group(rsc));
if (orig_rsc == NULL) {
orig_rsc = rsc;
}
if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
|| pcmk__is_clone(rsc->priv->parent)) {
/* This group has colocated members and/or is cloned -- either way,
* add every child's colocated resources to the list. The first and last
* members will include the group's own colocations.
*/
colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
for (const GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *member = iter->data;
colocated_rscs = member->priv->cmds->colocated_resources(member,
orig_rsc,
colocated_rscs);
}
} else if (rsc->priv->children != NULL) {
/* This group's members are not colocated, and the group is not cloned,
* so just add the group's own colocations to the list.
*/
colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc,
colocated_rscs);
}
return colocated_rscs;
}
// Group implementation of pcmk__assignment_methods_t:with_this_colocations()
void
pcmk__with_group_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
parent = rsc->priv->parent;
// Ignore empty groups
if (rsc->priv->children == NULL) {
return;
}
/* "With this" colocations are needed only for the group itself and for its
* last member. (Previous members will chain via the group internal
* colocations.)
*/
if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) {
return;
}
pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s",
rsc->id, orig_rsc->id);
// Add the group's own colocations
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
// If cloned, add any relevant colocations with the clone
if (parent != NULL) {
parent->priv->cmds->with_this_colocations(parent, orig_rsc, list);
}
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
// @COMPAT Non-colocated groups are deprecated
return;
}
// Add explicit colocations with the group's (other) children
for (const GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *member = iter->data;
if (member == orig_rsc) {
continue;
}
member->priv->cmds->with_this_colocations(member, orig_rsc, list);
}
}
// Group implementation of pcmk__assignment_methods_t:this_with_colocations()
void
pcmk__group_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
const pcmk_resource_t *member = NULL;
CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
parent = rsc->priv->parent;
// Ignore empty groups
if (rsc->priv->children == NULL) {
return;
}
/* "This with" colocations are normally needed only for the group itself and
* for its first member.
*/
if ((rsc == orig_rsc) || (orig_rsc == rsc->priv->children->data)) {
pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s",
rsc->id, orig_rsc->id);
// Add the group's own colocations
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
// If cloned, add any relevant colocations involving the clone
if (parent != NULL) {
parent->priv->cmds->this_with_colocations(parent, orig_rsc, list);
}
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
// @COMPAT Non-colocated groups are deprecated
return;
}
// Add explicit colocations involving the group's (other) children
for (const GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
member = iter->data;
if (member == orig_rsc) {
continue;
}
member->priv->cmds->this_with_colocations(member, orig_rsc, list);
}
return;
}
/* Later group members honor the group's colocations indirectly, due to the
* internal group colocations that chain everything from the first member.
* However, if an earlier group member is unmanaged, this chaining will not
* happen, so the group's mandatory colocations must be explicitly added.
*/
for (const GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
member = iter->data;
if (orig_rsc == member) {
break; // We've seen all earlier members, and none are unmanaged
}
if (!pcmk_is_set(member->flags, pcmk__rsc_managed)) {
crm_trace("Adding mandatory '%s with' colocations to list for "
"member %s because earlier member %s is unmanaged",
rsc->id, orig_rsc->id, member->id);
for (const GList *cons_iter = rsc->priv->this_with_colocations;
cons_iter != NULL; cons_iter = cons_iter->next) {
const pcmk__colocation_t *colocation = NULL;
colocation = (const pcmk__colocation_t *) cons_iter->data;
if (colocation->score == PCMK_SCORE_INFINITY) {
pcmk__add_this_with(list, colocation, orig_rsc);
}
}
// @TODO Add mandatory (or all?) clone constraints if cloned
break;
}
}
}
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Group resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to \c NULL
* to copy allowed nodes from \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* source_rsc's own matching node scores will
* not be added, and \p *nodes must be \c NULL as
* well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
* the \c pcmk__coloc_select_this_with flag are used together (and only by
* \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
* \note This is the group implementation of
* \c pcmk__assignment_methods_t:add_colocated_node_scores().
*/
void
pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id, GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags)
{
pcmk_resource_t *member = NULL;
CRM_ASSERT(pcmk__is_group(source_rsc) && (nodes != NULL)
&& ((colocation != NULL)
|| ((target_rsc == NULL) && (*nodes == NULL))));
if (log_id == NULL) {
log_id = source_rsc->id;
}
// Avoid infinite recursion
if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) {
pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
log_id, source_rsc->id);
return;
}
pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
// Ignore empty groups (only possible with schema validation disabled)
if (source_rsc->priv->children == NULL) {
return;
}
/* Refer the operation to the first or last member as appropriate.
*
* cmp_resources() is the only caller that passes a NULL nodes table,
* and is also the only caller using pcmk__coloc_select_this_with.
* For "this with" colocations, the last member will recursively incorporate
* all the other members' "this with" colocations via the internal group
* colocations (and via the first member, the group's own colocations).
*
* For "with this" colocations, the first member works similarly.
*/
if (*nodes == NULL) {
member = pe__last_group_member(source_rsc);
} else {
member = source_rsc->priv->children->data;
}
pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s "
"(at %.6f)", log_id, source_rsc->id, member->id, factor);
member->priv->cmds->add_colocated_node_scores(member, target_rsc, log_id,
nodes, colocation, factor,
flags);
pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
}
// Group implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__group_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
pcmk_resource_t *member = NULL;
CRM_ASSERT((orig_rsc != NULL) && (utilization != NULL)
&& pcmk__is_group(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
orig_rsc->id, rsc->id);
if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
|| pcmk__is_clone(rsc->priv->parent)) {
// Every group member will be on same node, so sum all members
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
member = (pcmk_resource_t *) iter->data;
if (pcmk_is_set(member->flags, pcmk__rsc_unassigned)
&& (g_list_find(all_rscs, member) == NULL)) {
member->priv->cmds->add_utilization(member, orig_rsc, all_rscs,
utilization);
}
}
} else if (rsc->priv->children != NULL) {
// Just add first member's utilization
member = (pcmk_resource_t *) rsc->priv->children->data;
if ((member != NULL)
&& pcmk_is_set(member->flags, pcmk__rsc_unassigned)
&& (g_list_find(all_rscs, member) == NULL)) {
member->priv->cmds->add_utilization(member, orig_rsc, all_rscs,
utilization);
}
}
}
void
pcmk__group_shutdown_lock(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_group(rsc));
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
member->priv->cmds->shutdown_lock(member);
}
}
diff --git a/lib/pacemaker/pcmk_sched_primitive.c b/lib/pacemaker/pcmk_sched_primitive.c
index 8f0d95a18f..e23c0f4054 100644
--- a/lib/pacemaker/pcmk_sched_primitive.c
+++ b/lib/pacemaker/pcmk_sched_primitive.c
@@ -1,1716 +1,1719 @@
/*
* 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 // uint8_t, uint32_t
#include
#include
#include "libpacemaker_private.h"
static void stop_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
static void start_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
static void demote_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
static void promote_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
static void assert_role_error(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
#define RSC_ROLE_MAX (pcmk_role_promoted + 1)
static enum rsc_role_e rsc_state_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
/* This array lists the immediate next role when transitioning from one role
* to a target role. For example, when going from Stopped to Promoted, the
* next role is Unpromoted, because the resource must be started before it
* can be promoted. The current state then becomes Started, which is fed
* into this array again, giving a next role of Promoted.
*
* Current role Immediate next role Final target role
* ------------ ------------------- -----------------
*/
/* Unknown */ { pcmk_role_unknown, /* Unknown */
pcmk_role_stopped, /* Stopped */
pcmk_role_stopped, /* Started */
pcmk_role_stopped, /* Unpromoted */
pcmk_role_stopped, /* Promoted */
},
/* Stopped */ { pcmk_role_stopped, /* Unknown */
pcmk_role_stopped, /* Stopped */
pcmk_role_started, /* Started */
pcmk_role_unpromoted, /* Unpromoted */
pcmk_role_unpromoted, /* Promoted */
},
/* Started */ { pcmk_role_stopped, /* Unknown */
pcmk_role_stopped, /* Stopped */
pcmk_role_started, /* Started */
pcmk_role_unpromoted, /* Unpromoted */
pcmk_role_promoted, /* Promoted */
},
/* Unpromoted */ { pcmk_role_stopped, /* Unknown */
pcmk_role_stopped, /* Stopped */
pcmk_role_stopped, /* Started */
pcmk_role_unpromoted, /* Unpromoted */
pcmk_role_promoted, /* Promoted */
},
/* Promoted */ { pcmk_role_stopped, /* Unknown */
pcmk_role_unpromoted, /* Stopped */
pcmk_role_unpromoted, /* Started */
pcmk_role_unpromoted, /* Unpromoted */
pcmk_role_promoted, /* Promoted */
},
};
/*!
* \internal
* \brief Function to schedule actions needed for a role change
*
* \param[in,out] rsc Resource whose role is changing
* \param[in,out] node Node where resource will be in its next role
* \param[in] optional Whether scheduled actions should be optional
*/
typedef void (*rsc_transition_fn)(pcmk_resource_t *rsc, pcmk_node_t *node,
bool optional);
static rsc_transition_fn rsc_action_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
/* This array lists the function needed to transition directly from one role
* to another. NULL indicates that nothing is needed.
*
* Current role Transition function Next role
* ------------ ------------------- ----------
*/
/* Unknown */ { assert_role_error, /* Unknown */
stop_resource, /* Stopped */
assert_role_error, /* Started */
assert_role_error, /* Unpromoted */
assert_role_error, /* Promoted */
},
/* Stopped */ { assert_role_error, /* Unknown */
NULL, /* Stopped */
start_resource, /* Started */
start_resource, /* Unpromoted */
assert_role_error, /* Promoted */
},
/* Started */ { assert_role_error, /* Unknown */
stop_resource, /* Stopped */
NULL, /* Started */
NULL, /* Unpromoted */
promote_resource, /* Promoted */
},
/* Unpromoted */ { assert_role_error, /* Unknown */
stop_resource, /* Stopped */
stop_resource, /* Started */
NULL, /* Unpromoted */
promote_resource, /* Promoted */
},
/* Promoted */ { assert_role_error, /* Unknown */
demote_resource, /* Stopped */
demote_resource, /* Started */
demote_resource, /* Unpromoted */
NULL, /* Promoted */
},
};
/*!
* \internal
* \brief Get a list of a resource's allowed nodes sorted by node score
*
* \param[in] rsc Resource to check
*
* \return List of allowed nodes sorted by node score
*/
static GList *
sorted_allowed_nodes(const pcmk_resource_t *rsc)
{
if (rsc->priv->allowed_nodes != NULL) {
GList *nodes = g_hash_table_get_values(rsc->priv->allowed_nodes);
if (nodes != NULL) {
return pcmk__sort_nodes(nodes, pcmk__current_node(rsc));
}
}
return NULL;
}
/*!
* \internal
* \brief Assign a resource to its best allowed node, if possible
*
* \param[in,out] rsc Resource to choose a node for
* \param[in] prefer If not \c NULL, prefer this node when all else
* equal
* \param[in] stop_if_fail If \c true and \p rsc can't be assigned to a
* node, set next role to stopped and update
* existing actions
*
* \return true if \p rsc could be assigned to a node, otherwise false
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
static bool
assign_best_node(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
GList *nodes = NULL;
pcmk_node_t *chosen = NULL;
pcmk_node_t *best = NULL;
const pcmk_node_t *most_free_node = pcmk__ban_insufficient_capacity(rsc);
if (prefer == NULL) {
prefer = most_free_node;
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
// We've already finished assignment of resources to nodes
return rsc->priv->assigned_node != NULL;
}
// Sort allowed nodes by score
nodes = sorted_allowed_nodes(rsc);
if (nodes != NULL) {
best = (pcmk_node_t *) nodes->data; // First node has best score
}
if ((prefer != NULL) && (nodes != NULL)) {
// Get the allowed node version of prefer
chosen = g_hash_table_lookup(rsc->priv->allowed_nodes,
prefer->priv->id);
if (chosen == NULL) {
pcmk__rsc_trace(rsc, "Preferred node %s for %s was unknown",
pcmk__node_name(prefer), rsc->id);
/* Favor the preferred node as long as its score is at least as good as
* the best allowed node's.
*
* An alternative would be to favor the preferred node even if the best
* node is better, when the best node's score is less than INFINITY.
*/
} else if (chosen->assign->score < best->assign->score) {
pcmk__rsc_trace(rsc, "Preferred node %s for %s was unsuitable",
pcmk__node_name(chosen), rsc->id);
chosen = NULL;
} else if (!pcmk__node_available(chosen, true, false)) {
pcmk__rsc_trace(rsc, "Preferred node %s for %s was unavailable",
pcmk__node_name(chosen), rsc->id);
chosen = NULL;
} else {
pcmk__rsc_trace(rsc,
"Chose preferred node %s for %s "
"(ignoring %d candidates)",
pcmk__node_name(chosen), rsc->id,
g_list_length(nodes));
}
}
if ((chosen == NULL) && (best != NULL)) {
/* Either there is no preferred node, or the preferred node is not
* suitable, but another node is allowed to run the resource.
*/
chosen = best;
if (!pcmk__is_unique_clone(rsc->priv->parent)
&& (chosen->assign->score > 0) // Zero not acceptable
&& pcmk__node_available(chosen, false, false)) {
/* If the resource is already running on a node, prefer that node if
* it is just as good as the chosen node.
*
* We don't do this for unique clone instances, because
* pcmk__assign_instances() has already assigned instances to their
* running nodes when appropriate, and if we get here, we don't want
* remaining unassigned instances to prefer a node that's already
* running another instance.
*/
pcmk_node_t *running = pcmk__current_node(rsc);
if (running == NULL) {
// Nothing to do
} else if (!pcmk__node_available(running, true, false)) {
pcmk__rsc_trace(rsc,
"Current node for %s (%s) can't run resources",
rsc->id, pcmk__node_name(running));
} else {
int nodes_with_best_score = 1;
for (GList *iter = nodes->next; iter; iter = iter->next) {
pcmk_node_t *allowed = (pcmk_node_t *) iter->data;
if (allowed->assign->score != chosen->assign->score) {
// The nodes are sorted by score, so no more are equal
break;
}
if (pcmk__same_node(allowed, running)) {
// Scores are equal, so prefer the current node
chosen = allowed;
}
nodes_with_best_score++;
}
if (nodes_with_best_score > 1) {
uint8_t log_level = LOG_INFO;
if (chosen->assign->score >= PCMK_SCORE_INFINITY) {
log_level = LOG_WARNING;
}
do_crm_log(log_level,
"Chose %s for %s from %d nodes with score %s",
pcmk__node_name(chosen), rsc->id,
nodes_with_best_score,
pcmk_readable_score(chosen->assign->score));
}
}
}
pcmk__rsc_trace(rsc, "Chose %s for %s from %d candidates",
pcmk__node_name(chosen), rsc->id, g_list_length(nodes));
}
pcmk__assign_resource(rsc, chosen, false, stop_if_fail);
g_list_free(nodes);
return rsc->priv->assigned_node != NULL;
}
/*!
* \internal
* \brief Apply a "this with" colocation to a node's allowed node scores
*
* \param[in,out] colocation Colocation to apply
* \param[in,out] rsc Resource being assigned
*/
static void
apply_this_with(pcmk__colocation_t *colocation, pcmk_resource_t *rsc)
{
GHashTable *archive = NULL;
pcmk_resource_t *other = colocation->primary;
// In certain cases, we will need to revert the node scores
if ((colocation->dependent_role >= pcmk_role_promoted)
|| ((colocation->score < 0)
&& (colocation->score > -PCMK_SCORE_INFINITY))) {
archive = pcmk__copy_node_table(rsc->priv->allowed_nodes);
}
if (pcmk_is_set(other->flags, pcmk__rsc_unassigned)) {
pcmk__rsc_trace(rsc,
"%s: Assigning colocation %s primary %s first"
"(score=%d role=%s)",
rsc->id, colocation->id, other->id,
colocation->score,
pcmk_role_text(colocation->dependent_role));
other->priv->cmds->assign(other, NULL, true);
}
// Apply the colocation score to this resource's allowed node scores
rsc->priv->cmds->apply_coloc_score(rsc, other, colocation, true);
if ((archive != NULL)
&& !pcmk__any_node_available(rsc->priv->allowed_nodes)) {
pcmk__rsc_info(rsc,
"%s: Reverting scores from colocation with %s "
"because no nodes allowed",
rsc->id, other->id);
g_hash_table_destroy(rsc->priv->allowed_nodes);
rsc->priv->allowed_nodes = archive;
archive = NULL;
}
if (archive != NULL) {
g_hash_table_destroy(archive);
}
}
/*!
* \internal
* \brief Update a Pacemaker Remote node once its connection has been assigned
*
* \param[in] connection Connection resource that has been assigned
*/
static void
remote_connection_assigned(const pcmk_resource_t *connection)
{
pcmk_node_t *remote_node = pcmk_find_node(connection->priv->scheduler,
connection->id);
CRM_CHECK(remote_node != NULL, return);
if ((connection->priv->assigned_node != NULL)
&& (connection->priv->next_role != pcmk_role_stopped)) {
crm_trace("Pacemaker Remote node %s will be online",
remote_node->priv->id);
remote_node->details->online = TRUE;
if (!pcmk_is_set(remote_node->priv->flags, pcmk__node_seen)) {
// Avoid unnecessary fence, since we will attempt connection
remote_node->details->unclean = FALSE;
}
} else {
crm_trace("Pacemaker Remote node %s will be shut down "
"(%sassigned connection's next role is %s)",
remote_node->priv->id,
((connection->priv->assigned_node == NULL)? "un" : ""),
pcmk_role_text(connection->priv->next_role));
remote_node->details->shutdown = TRUE;
}
}
/*!
* \internal
* \brief Assign a primitive resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and \p rsc can't be assigned to a
* node, set next role to stopped and update
* existing actions
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__primitive_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
GList *this_with_colocations = NULL;
GList *with_this_colocations = NULL;
GList *iter = NULL;
pcmk_resource_t *parent = NULL;
pcmk__colocation_t *colocation = NULL;
pcmk_scheduler_t *scheduler = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc));
scheduler = rsc->priv->scheduler;
parent = rsc->priv->parent;
// Never assign a child without parent being assigned first
if ((parent != NULL) && !pcmk_is_set(parent->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(rsc, "%s: Assigning parent %s first",
rsc->id, parent->id);
parent->priv->cmds->assign(parent, prefer, stop_if_fail);
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
// Assignment has already been done
const char *node_name = "no node";
if (rsc->priv->assigned_node != NULL) {
node_name = pcmk__node_name(rsc->priv->assigned_node);
}
pcmk__rsc_debug(rsc, "%s: pre-assigned to %s", rsc->id, node_name);
return rsc->priv->assigned_node;
}
// Ensure we detect assignment loops
if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
return NULL;
}
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
pe__show_node_scores(true, rsc, "Pre-assignment",
rsc->priv->allowed_nodes, scheduler);
this_with_colocations = pcmk__this_with_colocations(rsc);
with_this_colocations = pcmk__with_this_colocations(rsc);
// Apply mandatory colocations first, to satisfy as many as possible
for (iter = this_with_colocations; iter != NULL; iter = iter->next) {
colocation = iter->data;
if ((colocation->score <= -PCMK_SCORE_INFINITY)
|| (colocation->score >= PCMK_SCORE_INFINITY)) {
apply_this_with(colocation, rsc);
}
}
for (iter = with_this_colocations; iter != NULL; iter = iter->next) {
colocation = iter->data;
if ((colocation->score <= -PCMK_SCORE_INFINITY)
|| (colocation->score >= PCMK_SCORE_INFINITY)) {
pcmk__add_dependent_scores(colocation, rsc);
}
}
pe__show_node_scores(true, rsc, "Mandatory-colocations",
rsc->priv->allowed_nodes, scheduler);
// Then apply optional colocations
for (iter = this_with_colocations; iter != NULL; iter = iter->next) {
colocation = iter->data;
if ((colocation->score > -PCMK_SCORE_INFINITY)
&& (colocation->score < PCMK_SCORE_INFINITY)) {
apply_this_with(colocation, rsc);
}
}
for (iter = with_this_colocations; iter != NULL; iter = iter->next) {
colocation = iter->data;
if ((colocation->score > -PCMK_SCORE_INFINITY)
&& (colocation->score < PCMK_SCORE_INFINITY)) {
pcmk__add_dependent_scores(colocation, rsc);
}
}
g_list_free(this_with_colocations);
g_list_free(with_this_colocations);
if (rsc->priv->next_role == pcmk_role_stopped) {
pcmk__rsc_trace(rsc,
"Banning %s from all nodes because it will be stopped",
rsc->id);
resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
PCMK_META_TARGET_ROLE, scheduler);
} else if ((rsc->priv->next_role > rsc->priv->orig_role)
&& !pcmk_is_set(scheduler->flags, pcmk__sched_quorate)
&& (scheduler->no_quorum_policy == pcmk_no_quorum_freeze)) {
crm_notice("Resource %s cannot be elevated from %s to %s due to "
PCMK_OPT_NO_QUORUM_POLICY "=" PCMK_VALUE_FREEZE,
rsc->id, pcmk_role_text(rsc->priv->orig_role),
pcmk_role_text(rsc->priv->next_role));
pe__set_next_role(rsc, rsc->priv->orig_role,
PCMK_OPT_NO_QUORUM_POLICY "=" PCMK_VALUE_FREEZE);
}
pe__show_node_scores(!pcmk_is_set(scheduler->flags,
pcmk__sched_output_scores),
rsc, __func__, rsc->priv->allowed_nodes, scheduler);
// Unmanage resource if fencing is enabled but no device is configured
if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)
&& !pcmk_is_set(scheduler->flags, pcmk__sched_have_fencing)) {
pcmk__clear_rsc_flags(rsc, pcmk__rsc_managed);
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
// Unmanaged resources stay on their current node
const char *reason = NULL;
pcmk_node_t *assign_to = NULL;
pe__set_next_role(rsc, rsc->priv->orig_role, "unmanaged");
assign_to = pcmk__current_node(rsc);
if (assign_to == NULL) {
reason = "inactive";
} else if (rsc->priv->orig_role == pcmk_role_promoted) {
reason = "promoted";
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_failed)) {
reason = "failed";
} else {
reason = "active";
}
pcmk__rsc_info(rsc, "Unmanaged resource %s assigned to %s: %s", rsc->id,
(assign_to? assign_to->priv->name : "no node"),
reason);
pcmk__assign_resource(rsc, assign_to, true, stop_if_fail);
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_stop_all)) {
// Must stop at some point, but be consistent with stop_if_fail
if (stop_if_fail) {
pcmk__rsc_debug(rsc,
"Forcing %s to stop: " PCMK_OPT_STOP_ALL_RESOURCES,
rsc->id);
}
pcmk__assign_resource(rsc, NULL, true, stop_if_fail);
} else if (!assign_best_node(rsc, prefer, stop_if_fail)) {
// Assignment failed
if (!pcmk_is_set(rsc->flags, pcmk__rsc_removed)) {
pcmk__rsc_info(rsc, "Resource %s cannot run anywhere", rsc->id);
} else if ((rsc->priv->active_nodes != NULL) && stop_if_fail) {
pcmk__rsc_info(rsc, "Stopping removed resource %s", rsc->id);
}
}
pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning);
if (pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)) {
remote_connection_assigned(rsc);
}
return rsc->priv->assigned_node;
}
/*!
* \internal
* \brief Schedule actions to bring resource down and back to current role
*
* \param[in,out] rsc Resource to restart
* \param[in,out] current Node that resource should be brought down on
* \param[in] need_stop Whether the resource must be stopped
* \param[in] need_promote Whether the resource must be promoted
*
* \return Role that resource would have after scheduled actions are taken
*/
static void
schedule_restart_actions(pcmk_resource_t *rsc, pcmk_node_t *current,
bool need_stop, bool need_promote)
{
enum rsc_role_e role = rsc->priv->orig_role;
enum rsc_role_e next_role;
rsc_transition_fn fn = NULL;
pcmk__set_rsc_flags(rsc, pcmk__rsc_restarting);
// Bring resource down to a stop on its current node
while (role != pcmk_role_stopped) {
next_role = rsc_state_matrix[role][pcmk_role_stopped];
pcmk__rsc_trace(rsc, "Creating %s action to take %s down from %s to %s",
(need_stop? "required" : "optional"), rsc->id,
pcmk_role_text(role), pcmk_role_text(next_role));
fn = rsc_action_matrix[role][next_role];
if (fn == NULL) {
break;
}
fn(rsc, current, !need_stop);
role = next_role;
}
// Bring resource up to its next role on its next node
while ((rsc->priv->orig_role <= rsc->priv->next_role)
&& (role != rsc->priv->orig_role)
&& !pcmk_is_set(rsc->flags, pcmk__rsc_blocked)) {
bool required = need_stop;
next_role = rsc_state_matrix[role][rsc->priv->orig_role];
if ((next_role == pcmk_role_promoted) && need_promote) {
required = true;
}
pcmk__rsc_trace(rsc, "Creating %s action to take %s up from %s to %s",
(required? "required" : "optional"), rsc->id,
pcmk_role_text(role), pcmk_role_text(next_role));
fn = rsc_action_matrix[role][next_role];
if (fn == NULL) {
break;
}
fn(rsc, rsc->priv->assigned_node, !required);
role = next_role;
}
pcmk__clear_rsc_flags(rsc, pcmk__rsc_restarting);
}
/*!
* \internal
* \brief If a resource's next role is not explicitly specified, set a default
*
* \param[in,out] rsc Resource to set next role for
*
* \return "explicit" if next role was explicitly set, otherwise "implicit"
*/
static const char *
set_default_next_role(pcmk_resource_t *rsc)
{
if (rsc->priv->next_role != pcmk_role_unknown) {
return "explicit";
}
if (rsc->priv->assigned_node == NULL) {
pe__set_next_role(rsc, pcmk_role_stopped, "assignment");
} else {
pe__set_next_role(rsc, pcmk_role_started, "assignment");
}
return "implicit";
}
/*!
* \internal
* \brief Create an action to represent an already pending start
*
* \param[in,out] rsc Resource to create start action for
*/
static void
create_pending_start(pcmk_resource_t *rsc)
{
pcmk_action_t *start = NULL;
pcmk__rsc_trace(rsc,
"Creating action for %s to represent already pending start",
rsc->id);
start = start_action(rsc, rsc->priv->assigned_node, TRUE);
pcmk__set_action_flags(start, pcmk__action_always_in_graph);
}
/*!
* \internal
* \brief Schedule actions needed to take a resource to its next role
*
* \param[in,out] rsc Resource to schedule actions for
*/
static void
schedule_role_transition_actions(pcmk_resource_t *rsc)
{
enum rsc_role_e role = rsc->priv->orig_role;
while (role != rsc->priv->next_role) {
enum rsc_role_e next_role =
rsc_state_matrix[role][rsc->priv->next_role];
rsc_transition_fn fn = NULL;
pcmk__rsc_trace(rsc,
"Creating action to take %s from %s to %s "
"(ending at %s)",
rsc->id, pcmk_role_text(role),
pcmk_role_text(next_role),
pcmk_role_text(rsc->priv->next_role));
fn = rsc_action_matrix[role][next_role];
if (fn == NULL) {
break;
}
fn(rsc, rsc->priv->assigned_node, false);
role = next_role;
}
}
/*!
* \internal
* \brief Create all actions needed for a given primitive resource
*
* \param[in,out] rsc Primitive resource to create actions for
*/
void
pcmk__primitive_create_actions(pcmk_resource_t *rsc)
{
bool need_stop = false;
bool need_promote = false;
bool is_moving = false;
bool allow_migrate = false;
bool multiply_active = false;
pcmk_node_t *current = NULL;
pcmk_node_t *migration_target = NULL;
unsigned int num_all_active = 0;
unsigned int num_clean_active = 0;
const char *next_role_source = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc));
next_role_source = set_default_next_role(rsc);
pcmk__rsc_trace(rsc,
"Creating all actions for %s transition from %s to %s "
"(%s) on %s",
rsc->id, pcmk_role_text(rsc->priv->orig_role),
pcmk_role_text(rsc->priv->next_role), next_role_source,
pcmk__node_name(rsc->priv->assigned_node));
current = rsc->priv->fns->active_node(rsc, &num_all_active,
&num_clean_active);
g_list_foreach(rsc->priv->dangling_migration_sources,
pcmk__abort_dangling_migration, rsc);
if ((current != NULL) && (rsc->priv->assigned_node != NULL)
&& !pcmk__same_node(current, rsc->priv->assigned_node)
&& (rsc->priv->next_role >= pcmk_role_started)) {
pcmk__rsc_trace(rsc, "Moving %s from %s to %s",
rsc->id, pcmk__node_name(current),
pcmk__node_name(rsc->priv->assigned_node));
is_moving = true;
allow_migrate = pcmk__rsc_can_migrate(rsc, current);
// This is needed even if migrating (though I'm not sure why ...)
need_stop = true;
}
// Check whether resource is partially migrated and/or multiply active
migration_target = rsc->priv->partial_migration_target;
if ((rsc->priv->partial_migration_source != NULL)
&& (migration_target != NULL) && allow_migrate && (num_all_active == 2)
&& pcmk__same_node(current, rsc->priv->partial_migration_source)
&& pcmk__same_node(rsc->priv->assigned_node, migration_target)) {
/* A partial migration is in progress, and the migration target remains
* the same as when the migration began.
*/
pcmk__rsc_trace(rsc,
"Partial migration of %s from %s to %s will continue",
rsc->id,
pcmk__node_name(rsc->priv->partial_migration_source),
pcmk__node_name(migration_target));
} else if ((rsc->priv->partial_migration_source != NULL)
|| (migration_target != NULL)) {
// A partial migration is in progress but can't be continued
if (num_all_active > 2) {
// The resource is migrating *and* multiply active!
crm_notice("Forcing recovery of %s because it is migrating "
"from %s to %s and possibly active elsewhere",
rsc->id,
pcmk__node_name(rsc->priv->partial_migration_source),
pcmk__node_name(migration_target));
} else {
// The migration source or target isn't available
crm_notice("Forcing recovery of %s because it can no longer "
"migrate from %s to %s",
rsc->id,
pcmk__node_name(rsc->priv->partial_migration_source),
pcmk__node_name(migration_target));
}
need_stop = true;
rsc->priv->partial_migration_source = NULL;
rsc->priv->partial_migration_target = NULL;
allow_migrate = false;
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
multiply_active = (num_all_active > 1);
} else {
/* If a resource has PCMK_META_REQUIRES set to PCMK_VALUE_NOTHING or
* PCMK_VALUE_QUORUM, don't consider it active on unclean nodes (similar
* to how all resources behave when PCMK_OPT_STONITH_ENABLED is false).
* We can start such resources elsewhere before fencing completes, and
* if we considered the resource active on the failed node, we would
* attempt recovery for being active on multiple nodes.
*/
multiply_active = (num_clean_active > 1);
}
if (multiply_active) {
const char *class = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS);
// Resource was (possibly) incorrectly multiply active
pcmk__sched_err(rsc->priv->scheduler,
"%s resource %s might be active on %u nodes (%s)",
pcmk__s(class, "Untyped"), rsc->id, num_all_active,
pcmk__multiply_active_text(rsc));
crm_notice("For more information, see \"What are multiply active "
"resources?\" at "
"https://projects.clusterlabs.org/w/clusterlabs/faq/");
switch (rsc->priv->multiply_active_policy) {
case pcmk__multiply_active_restart:
need_stop = true;
break;
case pcmk__multiply_active_unexpected:
need_stop = true; // stop_resource() will skip expected node
pcmk__set_rsc_flags(rsc, pcmk__rsc_stop_unexpected);
break;
default:
break;
}
} else {
pcmk__clear_rsc_flags(rsc, pcmk__rsc_stop_unexpected);
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_start_pending)) {
create_pending_start(rsc);
}
if (is_moving) {
// Remaining tests are only for resources staying where they are
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_failed)) {
if (pcmk_is_set(rsc->flags, pcmk__rsc_stop_if_failed)) {
need_stop = true;
pcmk__rsc_trace(rsc, "Recovering %s", rsc->id);
} else {
pcmk__rsc_trace(rsc, "Recovering %s by demotion", rsc->id);
if (rsc->priv->next_role == pcmk_role_promoted) {
need_promote = true;
}
}
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_blocked)) {
pcmk__rsc_trace(rsc, "Blocking further actions on %s", rsc->id);
need_stop = true;
} else if ((rsc->priv->orig_role > pcmk_role_started)
&& (current != NULL)
&& (rsc->priv->assigned_node != NULL)) {
pcmk_action_t *start = NULL;
pcmk__rsc_trace(rsc, "Creating start action for promoted resource %s",
rsc->id);
start = start_action(rsc, rsc->priv->assigned_node, TRUE);
if (!pcmk_is_set(start->flags, pcmk__action_optional)) {
// Recovery of a promoted resource
pcmk__rsc_trace(rsc, "%s restart is required for recovery", rsc->id);
need_stop = true;
}
}
// Create any actions needed to bring resource down and back up to same role
schedule_restart_actions(rsc, current, need_stop, need_promote);
// Create any actions needed to take resource from this role to the next
schedule_role_transition_actions(rsc);
pcmk__create_recurring_actions(rsc);
if (allow_migrate) {
pcmk__create_migration_actions(rsc, current);
}
}
/*!
* \internal
* \brief Ban a resource from any allowed nodes that are Pacemaker Remote nodes
*
* \param[in] rsc Resource to check
*/
static void
rsc_avoids_remote_nodes(const pcmk_resource_t *rsc)
{
GHashTableIter iter;
pcmk_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (node->priv->remote != NULL) {
node->assign->score = -PCMK_SCORE_INFINITY;
}
}
}
/*!
* \internal
* \brief Return allowed nodes as (possibly sorted) list
*
* Convert a resource's hash table of allowed nodes to a list. If printing to
* stdout, sort the list, to keep action ID numbers consistent for regression
* test output (while avoiding the performance hit on a live cluster).
*
* \param[in] rsc Resource to check for allowed nodes
*
* \return List of resource's allowed nodes
* \note Callers should take care not to rely on the list being sorted.
*/
static GList *
allowed_nodes_as_list(const pcmk_resource_t *rsc)
{
GList *allowed_nodes = NULL;
if (rsc->priv->allowed_nodes != NULL) {
allowed_nodes = g_hash_table_get_values(rsc->priv->allowed_nodes);
}
if (!pcmk__is_daemon) {
allowed_nodes = g_list_sort(allowed_nodes, pe__cmp_node_name);
}
return allowed_nodes;
}
/*!
* \internal
* \brief Create implicit constraints needed for a primitive resource
*
* \param[in,out] rsc Primitive resource to create implicit constraints for
*/
void
pcmk__primitive_internal_constraints(pcmk_resource_t *rsc)
{
GList *allowed_nodes = NULL;
bool check_unfencing = false;
bool check_utilization = false;
pcmk_scheduler_t *scheduler = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc));
scheduler = rsc->priv->scheduler;
if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
pcmk__rsc_trace(rsc,
"Skipping implicit constraints for unmanaged resource "
"%s", rsc->id);
return;
}
// Whether resource requires unfencing
check_unfencing = !pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)
&& pcmk_is_set(scheduler->flags,
pcmk__sched_enable_unfencing)
&& pcmk_is_set(rsc->flags, pcmk__rsc_needs_unfencing);
// Whether a non-default placement strategy is used
check_utilization = (g_hash_table_size(rsc->priv->utilization) > 0)
&& !pcmk__str_eq(scheduler->priv->placement_strategy,
PCMK_VALUE_DEFAULT, pcmk__str_casei);
// Order stops before starts (i.e. restart)
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0), NULL,
rsc, pcmk__op_key(rsc->id, PCMK_ACTION_START, 0), NULL,
pcmk__ar_ordered
|pcmk__ar_first_implies_then
|pcmk__ar_intermediate_stop, scheduler);
// Promotable ordering: demote before stop, start before promote
if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
pcmk__rsc_promotable)
|| (rsc->priv->orig_role > pcmk_role_unpromoted)) {
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_DEMOTE, 0),
NULL,
rsc, pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0),
NULL,
pcmk__ar_promoted_then_implies_first, scheduler);
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_START, 0),
NULL,
rsc, pcmk__op_key(rsc->id, PCMK_ACTION_PROMOTE, 0),
NULL,
pcmk__ar_unrunnable_first_blocks, scheduler);
}
// Don't clear resource history if probing on same node
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
NULL, rsc,
pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0),
NULL,
pcmk__ar_if_on_same_node|pcmk__ar_then_cancels_first,
scheduler);
// Certain checks need allowed nodes
if (check_unfencing || check_utilization
|| (rsc->priv->launcher != NULL)) {
allowed_nodes = allowed_nodes_as_list(rsc);
}
if (check_unfencing) {
g_list_foreach(allowed_nodes, pcmk__order_restart_vs_unfence, rsc);
}
if (check_utilization) {
pcmk__create_utilization_constraints(rsc, allowed_nodes);
}
if (rsc->priv->launcher != NULL) {
pcmk_resource_t *remote_rsc = NULL;
if (pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)) {
// rsc is the implicit remote connection for a guest or bundle node
/* Guest resources are not allowed to run on Pacemaker Remote nodes,
* to avoid nesting remotes. However, bundles are allowed.
*/
if (!pcmk_is_set(rsc->flags, pcmk__rsc_remote_nesting_allowed)) {
rsc_avoids_remote_nodes(rsc->priv->launcher);
}
/* If someone cleans up a guest or bundle node's launcher, we will
* likely schedule a (re-)probe of the launcher and recovery of the
* connection. Order the connection stop after the launcher probe,
* so that if we detect the launcher running, we will trigger a new
* transition and avoid the unnecessary recovery.
*/
pcmk__order_resource_actions(rsc->priv->launcher,
PCMK_ACTION_MONITOR,
rsc, PCMK_ACTION_STOP,
pcmk__ar_ordered);
/* A user can specify that a resource must start on a Pacemaker Remote
* node by explicitly configuring it with the PCMK__META_CONTAINER
* meta-attribute. This is of questionable merit, since location
* constraints can accomplish the same thing. But we support it, so here
* we check whether a resource (that is not itself a remote connection)
* has PCMK__META_CONTAINER set to a remote node or guest node resource.
*/
} else if (pcmk_is_set(rsc->priv->launcher->flags,
pcmk__rsc_is_remote_connection)) {
remote_rsc = rsc->priv->launcher;
} else {
remote_rsc =
pe__resource_contains_guest_node(scheduler,
rsc->priv->launcher);
}
if (remote_rsc != NULL) {
/* Force the resource on the Pacemaker Remote node instead of
* colocating the resource with the launcher.
*/
for (GList *item = allowed_nodes; item; item = item->next) {
pcmk_node_t *node = item->data;
if (node->priv->remote != remote_rsc) {
node->assign->score = -PCMK_SCORE_INFINITY;
}
}
} else {
/* This resource is either launched by a resource that does NOT
* represent a Pacemaker Remote node, or a Pacemaker Remote
* connection resource for a guest node or bundle.
*/
int score;
crm_trace("Order and colocate %s relative to its launcher %s",
rsc->id, rsc->priv->launcher->id);
pcmk__new_ordering(rsc->priv->launcher,
pcmk__op_key(rsc->priv->launcher->id,
PCMK_ACTION_START, 0),
NULL, rsc,
pcmk__op_key(rsc->id, PCMK_ACTION_START, 0),
NULL,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks, scheduler);
pcmk__new_ordering(rsc,
pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0),
NULL,
rsc->priv->launcher,
pcmk__op_key(rsc->priv->launcher->id,
PCMK_ACTION_STOP, 0),
NULL, pcmk__ar_then_implies_first, scheduler);
if (pcmk_is_set(rsc->flags, pcmk__rsc_remote_nesting_allowed)) {
score = 10000; /* Highly preferred but not essential */
} else {
score = PCMK_SCORE_INFINITY; // Force to run on same host
}
pcmk__new_colocation("#resource-with-container", NULL, score, rsc,
rsc->priv->launcher, NULL, NULL,
pcmk__coloc_influence);
}
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)
|| pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
/* Remote connections and fencing devices are not allowed to run on
* Pacemaker Remote nodes
*/
rsc_avoids_remote_nodes(rsc);
}
g_list_free(allowed_nodes);
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
enum pcmk__coloc_affects filter_results;
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (for_dependent) {
// Always process on behalf of primary resource
- primary->priv->cmds->apply_coloc_score(dependent, primary, colocation,
- false);
- return;
+ return primary->priv->cmds->apply_coloc_score(dependent, primary,
+ colocation, false);
}
filter_results = pcmk__colocation_affects(dependent, primary, colocation,
false);
pcmk__rsc_trace(dependent, "%s %s with %s (%s, score=%d, filter=%d)",
((colocation->score > 0)? "Colocating" : "Anti-colocating"),
dependent->id, primary->id, colocation->id,
colocation->score,
filter_results);
switch (filter_results) {
case pcmk__coloc_affects_role:
- pcmk__apply_coloc_to_priority(dependent, primary, colocation);
- break;
+ return pcmk__apply_coloc_to_priority(dependent, primary,
+ colocation);
+
case pcmk__coloc_affects_location:
pcmk__apply_coloc_to_scores(dependent, primary, colocation);
- break;
+ return 0;
+
default: // pcmk__coloc_affects_nothing
- return;
+ return 0;
}
}
/* Primitive implementation of
* pcmk__assignment_methods_t:with_this_colocations()
*/
void
pcmk__with_primitive_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc) && (list != NULL));
parent = rsc->priv->parent;
if (rsc == orig_rsc) {
/* For the resource itself, add all of its own colocations and relevant
* colocations from its parent (if any).
*/
pcmk__add_with_this_list(list, rsc->priv->with_this_colocations,
orig_rsc);
if (parent != NULL) {
parent->priv->cmds->with_this_colocations(parent, orig_rsc, list);
}
} else {
// For an ancestor, add only explicitly configured constraints
for (GList *iter = rsc->priv->with_this_colocations;
iter != NULL; iter = iter->next) {
pcmk__colocation_t *colocation = iter->data;
if (pcmk_is_set(colocation->flags, pcmk__coloc_explicit)) {
pcmk__add_with_this(list, colocation, orig_rsc);
}
}
}
}
/* Primitive implementation of
* pcmk__assignment_methods_t:this_with_colocations()
*/
void
pcmk__primitive_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc) && (list != NULL));
parent = rsc->priv->parent;
if (rsc == orig_rsc) {
/* For the resource itself, add all of its own colocations and relevant
* colocations from its parent (if any).
*/
pcmk__add_this_with_list(list, rsc->priv->this_with_colocations,
orig_rsc);
if (parent != NULL) {
parent->priv->cmds->this_with_colocations(parent, orig_rsc, list);
}
} else {
// For an ancestor, add only explicitly configured constraints
for (GList *iter = rsc->priv->this_with_colocations;
iter != NULL; iter = iter->next) {
pcmk__colocation_t *colocation = iter->data;
if (pcmk_is_set(colocation->flags, pcmk__coloc_explicit)) {
pcmk__add_this_with(list, colocation, orig_rsc);
}
}
}
}
/*!
* \internal
* \brief Return action flags for a given primitive resource action
*
* \param[in,out] action Action to get flags for
* \param[in] node If not NULL, limit effects to this node (ignored)
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__primitive_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
CRM_ASSERT(action != NULL);
return (uint32_t) action->flags;
}
/*!
* \internal
* \brief Check whether a node is a multiply active resource's expected node
*
* \param[in] rsc Resource to check
* \param[in] node Node to check
*
* \return \c true if \p rsc is multiply active with
* \c PCMK_META_MULTIPLE_ACTIVE set to \c PCMK_VALUE_STOP_UNEXPECTED,
* and \p node is the node where it will remain active
* \note This assumes that the resource's next role cannot be changed to stopped
* after this is called, which should be reasonable if status has already
* been unpacked and resources have been assigned to nodes.
*/
static bool
is_expected_node(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
return pcmk_all_flags_set(rsc->flags,
pcmk__rsc_stop_unexpected|pcmk__rsc_restarting)
&& (rsc->priv->next_role > pcmk_role_stopped)
&& pcmk__same_node(rsc->priv->assigned_node, node);
}
/*!
* \internal
* \brief Schedule actions needed to stop a resource wherever it is active
*
* \param[in,out] rsc Resource being stopped
* \param[in] node Node where resource is being stopped (ignored)
* \param[in] optional Whether actions should be optional
*/
static void
stop_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
{
for (GList *iter = rsc->priv->active_nodes;
iter != NULL; iter = iter->next) {
pcmk_node_t *current = (pcmk_node_t *) iter->data;
pcmk_action_t *stop = NULL;
if (is_expected_node(rsc, current)) {
/* We are scheduling restart actions for a multiply active resource
* with PCMK_META_MULTIPLE_ACTIVE=PCMK_VALUE_STOP_UNEXPECTED, and
* this is where it should not be stopped.
*/
pcmk__rsc_trace(rsc,
"Skipping stop of multiply active resource %s "
"on expected node %s",
rsc->id, pcmk__node_name(current));
continue;
}
if (rsc->priv->partial_migration_target != NULL) {
// Continue migration if node originally was and remains target
if (pcmk__same_node(current, rsc->priv->partial_migration_target)
&& pcmk__same_node(current, rsc->priv->assigned_node)) {
pcmk__rsc_trace(rsc,
"Skipping stop of %s on %s "
"because partial migration there will continue",
rsc->id, pcmk__node_name(current));
continue;
} else {
pcmk__rsc_trace(rsc,
"Forcing stop of %s on %s "
"because migration target changed",
rsc->id, pcmk__node_name(current));
optional = false;
}
}
pcmk__rsc_trace(rsc, "Scheduling stop of %s on %s",
rsc->id, pcmk__node_name(current));
stop = stop_action(rsc, current, optional);
if (rsc->priv->assigned_node == NULL) {
pe_action_set_reason(stop, "node availability", true);
} else if (pcmk_all_flags_set(rsc->flags, pcmk__rsc_restarting
|pcmk__rsc_stop_unexpected)) {
/* We are stopping a multiply active resource on a node that is
* not its expected node, and we are still scheduling restart
* actions, so the stop is for being multiply active.
*/
pe_action_set_reason(stop, "being multiply active", true);
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
pcmk__clear_action_flags(stop, pcmk__action_runnable);
}
if (pcmk_is_set(rsc->priv->scheduler->flags,
pcmk__sched_remove_after_stop)) {
pcmk__schedule_cleanup(rsc, current, optional);
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_unfencing)) {
pcmk_action_t *unfence = pe_fence_op(current, PCMK_ACTION_ON, true,
NULL, false,
rsc->priv->scheduler);
order_actions(stop, unfence, pcmk__ar_then_implies_first);
if (!pcmk__node_unfenced(current)) {
pcmk__sched_err(rsc->priv->scheduler,
"Stopping %s until %s can be unfenced",
rsc->id, pcmk__node_name(current));
}
}
}
}
/*!
* \internal
* \brief Schedule actions needed to start a resource on a node
*
* \param[in,out] rsc Resource being started
* \param[in,out] node Node where resource should be started
* \param[in] optional Whether actions should be optional
*/
static void
start_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
{
pcmk_action_t *start = NULL;
CRM_ASSERT(node != NULL);
pcmk__rsc_trace(rsc, "Scheduling %s start of %s on %s (score %d)",
(optional? "optional" : "required"), rsc->id,
pcmk__node_name(node), node->assign->score);
start = start_action(rsc, node, TRUE);
pcmk__order_vs_unfence(rsc, node, start, pcmk__ar_first_implies_then);
if (pcmk_is_set(start->flags, pcmk__action_runnable) && !optional) {
pcmk__clear_action_flags(start, pcmk__action_optional);
}
if (is_expected_node(rsc, node)) {
/* This could be a problem if the start becomes necessary for other
* reasons later.
*/
pcmk__rsc_trace(rsc,
"Start of multiply active resouce %s "
"on expected node %s will be a pseudo-action",
rsc->id, pcmk__node_name(node));
pcmk__set_action_flags(start, pcmk__action_pseudo);
}
}
/*!
* \internal
* \brief Schedule actions needed to promote a resource on a node
*
* \param[in,out] rsc Resource being promoted
* \param[in] node Node where resource should be promoted
* \param[in] optional Whether actions should be optional
*/
static void
promote_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
{
GList *iter = NULL;
GList *action_list = NULL;
bool runnable = true;
CRM_ASSERT(node != NULL);
// Any start must be runnable for promotion to be runnable
action_list = pe__resource_actions(rsc, node, PCMK_ACTION_START, true);
for (iter = action_list; iter != NULL; iter = iter->next) {
pcmk_action_t *start = (pcmk_action_t *) iter->data;
if (!pcmk_is_set(start->flags, pcmk__action_runnable)) {
runnable = false;
}
}
g_list_free(action_list);
if (runnable) {
pcmk_action_t *promote = promote_action(rsc, node, optional);
pcmk__rsc_trace(rsc, "Scheduling %s promotion of %s on %s",
(optional? "optional" : "required"), rsc->id,
pcmk__node_name(node));
if (is_expected_node(rsc, node)) {
/* This could be a problem if the promote becomes necessary for
* other reasons later.
*/
pcmk__rsc_trace(rsc,
"Promotion of multiply active resouce %s "
"on expected node %s will be a pseudo-action",
rsc->id, pcmk__node_name(node));
pcmk__set_action_flags(promote, pcmk__action_pseudo);
}
} else {
pcmk__rsc_trace(rsc, "Not promoting %s on %s: start unrunnable",
rsc->id, pcmk__node_name(node));
action_list = pe__resource_actions(rsc, node, PCMK_ACTION_PROMOTE,
true);
for (iter = action_list; iter != NULL; iter = iter->next) {
pcmk_action_t *promote = (pcmk_action_t *) iter->data;
pcmk__clear_action_flags(promote, pcmk__action_runnable);
}
g_list_free(action_list);
}
}
/*!
* \internal
* \brief Schedule actions needed to demote a resource wherever it is active
*
* \param[in,out] rsc Resource being demoted
* \param[in] node Node where resource should be demoted (ignored)
* \param[in] optional Whether actions should be optional
*/
static void
demote_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
{
/* Since this will only be called for a primitive (possibly as an instance
* of a collective resource), the resource is multiply active if it is
* running on more than one node, so we want to demote on all of them as
* part of recovery, regardless of which one is the desired node.
*/
for (GList *iter = rsc->priv->active_nodes;
iter != NULL; iter = iter->next) {
pcmk_node_t *current = (pcmk_node_t *) iter->data;
if (is_expected_node(rsc, current)) {
pcmk__rsc_trace(rsc,
"Skipping demote of multiply active resource %s "
"on expected node %s",
rsc->id, pcmk__node_name(current));
} else {
pcmk__rsc_trace(rsc, "Scheduling %s demotion of %s on %s",
(optional? "optional" : "required"), rsc->id,
pcmk__node_name(current));
demote_action(rsc, current, optional);
}
}
}
static void
assert_role_error(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
{
CRM_ASSERT(false);
}
/*!
* \internal
* \brief Schedule cleanup of a resource
*
* \param[in,out] rsc Resource to clean up
* \param[in] node Node to clean up on
* \param[in] optional Whether clean-up should be optional
*/
void
pcmk__schedule_cleanup(pcmk_resource_t *rsc, const pcmk_node_t *node,
bool optional)
{
/* If the cleanup is required, its orderings are optional, because they're
* relevant only if both actions are required. Conversely, if the cleanup is
* optional, the orderings make the then action required if the first action
* becomes required.
*/
uint32_t flag = optional? pcmk__ar_first_implies_then : pcmk__ar_ordered;
CRM_CHECK((rsc != NULL) && (node != NULL), return);
if (pcmk_is_set(rsc->flags, pcmk__rsc_failed)) {
pcmk__rsc_trace(rsc, "Skipping clean-up of %s on %s: resource failed",
rsc->id, pcmk__node_name(node));
return;
}
if (node->details->unclean || !node->details->online) {
pcmk__rsc_trace(rsc, "Skipping clean-up of %s on %s: node unavailable",
rsc->id, pcmk__node_name(node));
return;
}
crm_notice("Scheduling clean-up of %s on %s",
rsc->id, pcmk__node_name(node));
delete_action(rsc, node, optional);
// stop -> clean-up -> start
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
rsc, PCMK_ACTION_DELETE, flag);
pcmk__order_resource_actions(rsc, PCMK_ACTION_DELETE,
rsc, PCMK_ACTION_START, flag);
}
/*!
* \internal
* \brief Add primitive meta-attributes relevant to graph actions to XML
*
* \param[in] rsc Primitive resource whose meta-attributes should be added
* \param[in,out] xml Transition graph action attributes XML to add to
*/
void
pcmk__primitive_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml)
{
char *name = NULL;
char *value = NULL;
const pcmk_resource_t *parent = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc) && (xml != NULL));
/* Clone instance numbers get set internally as meta-attributes, and are
* needed in the transition graph (for example, to tell unique clone
* instances apart).
*/
value = g_hash_table_lookup(rsc->priv->meta, PCMK__META_CLONE);
if (value != NULL) {
name = crm_meta_name(PCMK__META_CLONE);
crm_xml_add(xml, name, value);
free(name);
}
// Not sure if this one is really needed ...
value = g_hash_table_lookup(rsc->priv->meta, PCMK_META_REMOTE_NODE);
if (value != NULL) {
name = crm_meta_name(PCMK_META_REMOTE_NODE);
crm_xml_add(xml, name, value);
free(name);
}
/* The PCMK__META_CONTAINER meta-attribute can be set on the primitive
* itself or one of its ancestors, so check them all and keep the highest.
*/
for (parent = rsc; parent != NULL; parent = parent->priv->parent) {
if (parent->priv->launcher != NULL) {
crm_xml_add(xml, CRM_META "_" PCMK__META_CONTAINER,
parent->priv->launcher->id);
}
}
/* Bundle replica children will get their external-ip set internally as a
* meta-attribute. The graph action needs it, but under a different naming
* convention than other meta-attributes.
*/
value = g_hash_table_lookup(rsc->priv->meta, "external-ip");
if (value != NULL) {
crm_xml_add(xml, "pcmk_external_ip", value);
}
}
// Primitive implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__primitive_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization)
{
CRM_ASSERT(pcmk__is_primitive(rsc)
&& (orig_rsc != NULL) && (utilization != NULL));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
pcmk__rsc_trace(orig_rsc,
"%s: Adding primitive %s as colocated utilization",
orig_rsc->id, rsc->id);
pcmk__release_node_capacity(utilization, rsc);
}
/*!
* \internal
* \brief Get epoch time of node's shutdown attribute (or now if none)
*
* \param[in,out] node Node to check
*
* \return Epoch time corresponding to shutdown attribute if set or now if not
*/
static time_t
shutdown_time(pcmk_node_t *node)
{
const char *shutdown = pcmk__node_attr(node, PCMK__NODE_ATTR_SHUTDOWN, NULL,
pcmk__rsc_node_current);
time_t result = 0;
if (shutdown != NULL) {
long long result_ll;
if (pcmk__scan_ll(shutdown, &result_ll, 0LL) == pcmk_rc_ok) {
result = (time_t) result_ll;
}
}
return (result == 0)? get_effective_time(node->priv->scheduler) : result;
}
/*!
* \internal
* \brief Ban a resource from a node if it's not locked to the node
*
* \param[in] data Node to check
* \param[in,out] user_data Resource to check
*/
static void
ban_if_not_locked(gpointer data, gpointer user_data)
{
const pcmk_node_t *node = (const pcmk_node_t *) data;
pcmk_resource_t *rsc = (pcmk_resource_t *) user_data;
if (!pcmk__same_node(node, rsc->priv->lock_node)) {
resource_location(rsc, node, -PCMK_SCORE_INFINITY,
PCMK_OPT_SHUTDOWN_LOCK, rsc->priv->scheduler);
}
}
// Primitive implementation of pcmk__assignment_methods_t:shutdown_lock()
void
pcmk__primitive_shutdown_lock(pcmk_resource_t *rsc)
{
const char *class = NULL;
pcmk_scheduler_t *scheduler = NULL;
CRM_ASSERT(pcmk__is_primitive(rsc));
scheduler = rsc->priv->scheduler;
class = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS);
// Fence devices and remote connections can't be locked
if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_null_matches)
|| pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)) {
return;
}
if (rsc->priv->lock_node != NULL) {
// The lock was obtained from resource history
if (rsc->priv->active_nodes != NULL) {
/* The resource was started elsewhere even though it is now
* considered locked. This shouldn't be possible, but as a
* failsafe, we don't want to disturb the resource now.
*/
pcmk__rsc_info(rsc,
"Cancelling shutdown lock "
"because %s is already active", rsc->id);
pe__clear_resource_history(rsc, rsc->priv->lock_node);
rsc->priv->lock_node = NULL;
rsc->priv->lock_time = 0;
}
// Only a resource active on exactly one node can be locked
} else if (pcmk__list_of_1(rsc->priv->active_nodes)) {
pcmk_node_t *node = rsc->priv->active_nodes->data;
if (node->details->shutdown) {
if (node->details->unclean) {
pcmk__rsc_debug(rsc,
"Not locking %s to unclean %s for shutdown",
rsc->id, pcmk__node_name(node));
} else {
rsc->priv->lock_node = node;
rsc->priv->lock_time = shutdown_time(node);
}
}
}
if (rsc->priv->lock_node == NULL) {
// No lock needed
return;
}
if (scheduler->priv->shutdown_lock_ms > 0U) {
time_t lock_expiration = rsc->priv->lock_time
+ (scheduler->priv->shutdown_lock_ms / 1000U);
pcmk__rsc_info(rsc, "Locking %s to %s due to shutdown (expires @%lld)",
rsc->id, pcmk__node_name(rsc->priv->lock_node),
(long long) lock_expiration);
pe__update_recheck_time(++lock_expiration, scheduler,
"shutdown lock expiration");
} else {
pcmk__rsc_info(rsc, "Locking %s to %s due to shutdown",
rsc->id, pcmk__node_name(rsc->priv->lock_node));
}
// If resource is locked to one node, ban it from all other nodes
g_list_foreach(scheduler->nodes, ban_if_not_locked, rsc);
}
diff --git a/lib/pacemaker/pcmk_sched_promotable.c b/lib/pacemaker/pcmk_sched_promotable.c
index ab163f43a8..be25def889 100644
--- a/lib/pacemaker/pcmk_sched_promotable.c
+++ b/lib/pacemaker/pcmk_sched_promotable.c
@@ -1,1377 +1,1383 @@
/*
* 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 "libpacemaker_private.h"
/*!
* \internal
* \brief Add implicit promotion ordering for a promotable instance
*
* \param[in,out] clone Clone resource
* \param[in,out] child Instance of \p clone being ordered
* \param[in,out] last Previous instance ordered (NULL if \p child is first)
*/
static void
order_instance_promotion(pcmk_resource_t *clone, pcmk_resource_t *child,
pcmk_resource_t *last)
{
// "Promote clone" -> promote instance -> "clone promoted"
pcmk__order_resource_actions(clone, PCMK_ACTION_PROMOTE,
child, PCMK_ACTION_PROMOTE,
pcmk__ar_ordered);
pcmk__order_resource_actions(child, PCMK_ACTION_PROMOTE,
clone, PCMK_ACTION_PROMOTED,
pcmk__ar_ordered);
// If clone is ordered, order this instance relative to last
if ((last != NULL) && pe__clone_is_ordered(clone)) {
pcmk__order_resource_actions(last, PCMK_ACTION_PROMOTE,
child, PCMK_ACTION_PROMOTE,
pcmk__ar_ordered);
}
}
/*!
* \internal
* \brief Add implicit demotion ordering for a promotable instance
*
* \param[in,out] clone Clone resource
* \param[in,out] child Instance of \p clone being ordered
* \param[in] last Previous instance ordered (NULL if \p child is first)
*/
static void
order_instance_demotion(pcmk_resource_t *clone, pcmk_resource_t *child,
pcmk_resource_t *last)
{
// "Demote clone" -> demote instance -> "clone demoted"
pcmk__order_resource_actions(clone, PCMK_ACTION_DEMOTE, child,
PCMK_ACTION_DEMOTE,
pcmk__ar_then_implies_first_graphed);
pcmk__order_resource_actions(child, PCMK_ACTION_DEMOTE,
clone, PCMK_ACTION_DEMOTED,
pcmk__ar_first_implies_then_graphed);
// If clone is ordered, order this instance relative to last
if ((last != NULL) && pe__clone_is_ordered(clone)) {
pcmk__order_resource_actions(child, PCMK_ACTION_DEMOTE, last,
PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
}
}
/*!
* \internal
* \brief Check whether an instance will be promoted or demoted
*
* \param[in] rsc Instance to check
* \param[out] demoting If \p rsc will be demoted, this will be set to true
* \param[out] promoting If \p rsc will be promoted, this will be set to true
*/
static void
check_for_role_change(const pcmk_resource_t *rsc, bool *demoting,
bool *promoting)
{
const GList *iter = NULL;
// If this is a cloned group, check group members recursively
if (rsc->priv->children != NULL) {
for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
check_for_role_change((const pcmk_resource_t *) iter->data,
demoting, promoting);
}
return;
}
for (iter = rsc->priv->actions; iter != NULL; iter = iter->next) {
const pcmk_action_t *action = (const pcmk_action_t *) iter->data;
if (*promoting && *demoting) {
return;
} else if (pcmk_is_set(action->flags, pcmk__action_optional)) {
continue;
} else if (pcmk__str_eq(PCMK_ACTION_DEMOTE, action->task,
pcmk__str_none)) {
*demoting = true;
} else if (pcmk__str_eq(PCMK_ACTION_PROMOTE, action->task,
pcmk__str_none)) {
*promoting = true;
}
}
}
/*!
* \internal
* \brief Add promoted-role location constraint scores to an instance's priority
*
* Adjust a promotable clone instance's promotion priority by the scores of any
* location constraints in a list that are both limited to the promoted role and
* for the node where the instance will be placed.
*
* \param[in,out] child Promotable clone instance
* \param[in] location_constraints List of location constraints to apply
* \param[in] chosen Node where \p child will be placed
*/
static void
apply_promoted_locations(pcmk_resource_t *child,
const GList *location_constraints,
const pcmk_node_t *chosen)
{
for (const GList *iter = location_constraints; iter; iter = iter->next) {
const pcmk__location_t *location = iter->data;
const pcmk_node_t *constraint_node = NULL;
if (location->role_filter == pcmk_role_promoted) {
constraint_node = pe_find_node_id(location->nodes,
chosen->priv->id);
}
if (constraint_node != NULL) {
int new_priority = pcmk__add_scores(child->priv->priority,
constraint_node->assign->score);
pcmk__rsc_trace(child,
"Applying location %s to %s promotion priority on "
"%s: %s + %s = %s",
location->id, child->id,
pcmk__node_name(constraint_node),
pcmk_readable_score(child->priv->priority),
pcmk_readable_score(constraint_node->assign->score),
pcmk_readable_score(new_priority));
child->priv->priority = new_priority;
}
}
}
/*!
* \internal
* \brief Get the node that an instance will be promoted on
*
* \param[in] rsc Promotable clone instance to check
*
* \return Node that \p rsc will be promoted on, or NULL if none
*/
static pcmk_node_t *
node_to_be_promoted_on(const pcmk_resource_t *rsc)
{
pcmk_node_t *node = NULL;
pcmk_node_t *local_node = NULL;
const pcmk_resource_t *parent = NULL;
// If this is a cloned group, bail if any group member can't be promoted
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
if (node_to_be_promoted_on(child) == NULL) {
pcmk__rsc_trace(rsc,
"%s can't be promoted because member %s can't",
rsc->id, child->id);
return NULL;
}
}
node = rsc->priv->fns->location(rsc, NULL, FALSE);
if (node == NULL) {
pcmk__rsc_trace(rsc, "%s can't be promoted because it won't be active",
rsc->id);
return NULL;
} else if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
if (rsc->priv->fns->state(rsc, TRUE) == pcmk_role_promoted) {
crm_notice("Unmanaged instance %s will be left promoted on %s",
rsc->id, pcmk__node_name(node));
} else {
pcmk__rsc_trace(rsc, "%s can't be promoted because it is unmanaged",
rsc->id);
return NULL;
}
} else if (rsc->priv->priority < 0) {
pcmk__rsc_trace(rsc,
"%s can't be promoted because its promotion priority "
"%d is negative",
rsc->id, rsc->priv->priority);
return NULL;
} else if (!pcmk__node_available(node, false, true)) {
pcmk__rsc_trace(rsc,
"%s can't be promoted because %s can't run resources",
rsc->id, pcmk__node_name(node));
return NULL;
}
parent = pe__const_top_resource(rsc, false);
local_node = g_hash_table_lookup(parent->priv->allowed_nodes,
node->priv->id);
if (local_node == NULL) {
/* It should not be possible for the scheduler to have assigned the
* instance to a node where its parent is not allowed, but it's good to
* have a fail-safe.
*/
if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
pcmk__sched_err(node->priv->scheduler,
"%s can't be promoted because %s is not allowed "
"on %s (scheduler bug?)",
rsc->id, parent->id, pcmk__node_name(node));
} // else the instance is unmanaged and already promoted
return NULL;
} else if ((local_node->assign->count >= pe__clone_promoted_node_max(parent))
&& pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
pcmk__rsc_trace(rsc,
"%s can't be promoted because %s has "
"maximum promoted instances already",
rsc->id, pcmk__node_name(node));
return NULL;
}
return local_node;
}
/*!
* \internal
* \brief Compare two promotable clone instances by promotion priority
*
* \param[in] a First instance to compare
* \param[in] b Second instance to compare
*
* \return A negative number if \p a has higher promotion priority,
* a positive number if \p b has higher promotion priority,
* or 0 if promotion priorities are equal
*/
static gint
cmp_promotable_instance(gconstpointer a, gconstpointer b)
{
const pcmk_resource_t *rsc1 = (const pcmk_resource_t *) a;
const pcmk_resource_t *rsc2 = (const pcmk_resource_t *) b;
enum rsc_role_e role1 = pcmk_role_unknown;
enum rsc_role_e role2 = pcmk_role_unknown;
CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
// Check promotion priority set by pcmk__set_instance_roles()
if (rsc1->priv->promotion_priority > rsc2->priv->promotion_priority) {
pcmk__rsc_trace(rsc1,
"%s has higher promotion priority (%s) than %s (%d)",
rsc1->id,
pcmk_readable_score(rsc1->priv->promotion_priority),
rsc2->id, rsc2->priv->promotion_priority);
return -1;
}
if (rsc1->priv->promotion_priority < rsc2->priv->promotion_priority) {
pcmk__rsc_trace(rsc1,
"%s has lower promotion priority (%s) than %s (%d)",
rsc1->id,
pcmk_readable_score(rsc1->priv->promotion_priority),
rsc2->id, rsc2->priv->promotion_priority);
return 1;
}
// If those are the same, prefer instance whose current role is higher
role1 = rsc1->priv->fns->state(rsc1, TRUE);
role2 = rsc2->priv->fns->state(rsc2, TRUE);
if (role1 > role2) {
pcmk__rsc_trace(rsc1,
"%s has higher promotion priority than %s "
"(higher current role)",
rsc1->id, rsc2->id);
return -1;
} else if (role1 < role2) {
pcmk__rsc_trace(rsc1,
"%s has lower promotion priority than %s "
"(lower current role)",
rsc1->id, rsc2->id);
return 1;
}
// Finally, do normal clone instance sorting
return pcmk__cmp_instance(a, b);
}
/*!
* \internal
* \brief Add promotable clone instance's promotion priority to its node's score
*
* Add a promotable clone instance's promotion priority (which sums its
* promotion preferences and scores of relevant location constraints for the
* promoted role) to the node score of the instance's assigned node.
*
* \param[in] data Promotable clone instance
* \param[in,out] user_data Clone parent of \p data
*/
static void
add_promotion_priority_to_node_score(gpointer data, gpointer user_data)
{
const pcmk_resource_t *child = (const pcmk_resource_t *) data;
pcmk_resource_t *clone = (pcmk_resource_t *) user_data;
pcmk_node_t *node = NULL;
const pcmk_node_t *chosen = NULL;
const int promotion_priority = child->priv->promotion_priority;
if (promotion_priority < 0) {
pcmk__rsc_trace(clone,
"Not adding promotion priority of %s: negative (%s)",
child->id, pcmk_readable_score(promotion_priority));
return;
}
chosen = child->priv->fns->location(child, NULL, FALSE);
if (chosen == NULL) {
pcmk__rsc_trace(clone, "Not adding promotion priority of %s: inactive",
child->id);
return;
}
node = g_hash_table_lookup(clone->priv->allowed_nodes,
chosen->priv->id);
CRM_ASSERT(node != NULL);
node->assign->score = pcmk__add_scores(promotion_priority,
node->assign->score);
pcmk__rsc_trace(clone,
"Added cumulative priority of %s (%s) to score on %s "
"(now %d)",
child->id, pcmk_readable_score(promotion_priority),
pcmk__node_name(node), node->assign->score);
}
/*!
* \internal
* \brief Apply colocation to dependent's node scores if for promoted role
*
* \param[in,out] data Colocation constraint to apply
* \param[in,out] user_data Promotable clone that is constraint's dependent
*/
static void
apply_coloc_to_dependent(gpointer data, gpointer user_data)
{
pcmk__colocation_t *colocation = data;
pcmk_resource_t *clone = user_data;
pcmk_resource_t *primary = colocation->primary;
uint32_t flags = pcmk__coloc_select_default;
float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
if (colocation->dependent_role != pcmk_role_promoted) {
return;
}
if (colocation->score < PCMK_SCORE_INFINITY) {
flags = pcmk__coloc_select_active;
}
pcmk__rsc_trace(clone, "Applying colocation %s (promoted %s with %s) @%s",
colocation->id, colocation->dependent->id,
colocation->primary->id,
pcmk_readable_score(colocation->score));
primary->priv->cmds->add_colocated_node_scores(primary, clone, clone->id,
&(clone->priv->allowed_nodes),
colocation, factor, flags);
}
/*!
* \internal
* \brief Apply colocation to primary's node scores if for promoted role
*
* \param[in,out] data Colocation constraint to apply
* \param[in,out] user_data Promotable clone that is constraint's primary
*/
static void
apply_coloc_to_primary(gpointer data, gpointer user_data)
{
pcmk__colocation_t *colocation = data;
pcmk_resource_t *clone = user_data;
pcmk_resource_t *dependent = colocation->dependent;
const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
const uint32_t flags = pcmk__coloc_select_active
|pcmk__coloc_select_nonnegative;
if ((colocation->primary_role != pcmk_role_promoted)
|| !pcmk__colocation_has_influence(colocation, NULL)) {
return;
}
pcmk__rsc_trace(clone, "Applying colocation %s (%s with promoted %s) @%s",
colocation->id, colocation->dependent->id,
colocation->primary->id,
pcmk_readable_score(colocation->score));
dependent->priv->cmds->add_colocated_node_scores(dependent, clone,
clone->id,
&(clone->priv->allowed_nodes),
colocation, factor, flags);
}
/*!
* \internal
* \brief Set clone instance's promotion priority to its node's score
*
* \param[in,out] data Promotable clone instance
* \param[in] user_data Parent clone of \p data
*/
static void
set_promotion_priority_to_node_score(gpointer data, gpointer user_data)
{
pcmk_resource_t *child = (pcmk_resource_t *) data;
const pcmk_resource_t *clone = (const pcmk_resource_t *) user_data;
pcmk_node_t *chosen = child->priv->fns->location(child, NULL, FALSE);
if (!pcmk_is_set(child->flags, pcmk__rsc_managed)
&& (child->priv->next_role == pcmk_role_promoted)) {
child->priv->promotion_priority = PCMK_SCORE_INFINITY;
pcmk__rsc_trace(clone,
"Final promotion priority for %s is %s "
"(unmanaged promoted)",
child->id, pcmk_readable_score(PCMK_SCORE_INFINITY));
} else if (chosen == NULL) {
child->priv->promotion_priority = -PCMK_SCORE_INFINITY;
pcmk__rsc_trace(clone,
"Final promotion priority for %s is %s "
"(will not be active)",
child->id, pcmk_readable_score(-PCMK_SCORE_INFINITY));
} else if (child->priv->promotion_priority < 0) {
pcmk__rsc_trace(clone,
"Final promotion priority for %s is %s "
"(ignoring node score)",
child->id,
pcmk_readable_score(child->priv->promotion_priority));
} else {
const pcmk_node_t *node = NULL;
node = g_hash_table_lookup(clone->priv->allowed_nodes,
chosen->priv->id);
CRM_ASSERT(node != NULL);
child->priv->promotion_priority = node->assign->score;
pcmk__rsc_trace(clone,
"Adding scores for %s: "
"final promotion priority for %s is %s",
clone->id, child->id,
pcmk_readable_score(child->priv->promotion_priority));
}
}
/*!
* \internal
* \brief Sort a promotable clone's instances by descending promotion priority
*
* \param[in,out] clone Promotable clone to sort
*/
static void
sort_promotable_instances(pcmk_resource_t *clone)
{
GList *colocations = NULL;
if (pe__set_clone_flag(clone, pcmk__clone_promotion_constrained)
== pcmk_rc_already) {
return;
}
pcmk__set_rsc_flags(clone, pcmk__rsc_updating_nodes);
for (GList *iter = clone->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
pcmk__rsc_trace(clone,
"Adding scores for %s: "
"initial promotion priority for %s is %s",
clone->id, child->id,
pcmk_readable_score(child->priv->promotion_priority));
}
pe__show_node_scores(true, clone, "Before", clone->priv->allowed_nodes,
clone->priv->scheduler);
g_list_foreach(clone->priv->children,
add_promotion_priority_to_node_score, clone);
colocations = pcmk__this_with_colocations(clone);
g_list_foreach(colocations, apply_coloc_to_dependent, clone);
g_list_free(colocations);
colocations = pcmk__with_this_colocations(clone);
g_list_foreach(colocations, apply_coloc_to_primary, clone);
g_list_free(colocations);
// Ban resource from all nodes if it needs a ticket but doesn't have it
pcmk__require_promotion_tickets(clone);
pe__show_node_scores(true, clone, "After", clone->priv->allowed_nodes,
clone->priv->scheduler);
// Reset promotion priorities to final node scores
g_list_foreach(clone->priv->children,
set_promotion_priority_to_node_score, clone);
// Finally, sort instances in descending order of promotion priority
clone->priv->children = g_list_sort(clone->priv->children,
cmp_promotable_instance);
pcmk__clear_rsc_flags(clone, pcmk__rsc_updating_nodes);
}
/*!
* \internal
* \brief Find the active instance (if any) of an anonymous clone on a node
*
* \param[in] clone Anonymous clone to check
* \param[in] id Instance ID (without instance number) to check
* \param[in] node Node to check
*
* \return
*/
static pcmk_resource_t *
find_active_anon_instance(const pcmk_resource_t *clone, const char *id,
const pcmk_node_t *node)
{
for (GList *iter = clone->priv->children; iter; iter = iter->next) {
pcmk_resource_t *child = iter->data;
pcmk_resource_t *active = NULL;
// Use ->find_rsc() in case this is a cloned group
active = clone->priv->fns->find_rsc(child, id, node,
pcmk_rsc_match_clone_only
|pcmk_rsc_match_current_node);
if (active != NULL) {
return active;
}
}
return NULL;
}
/*
* \brief Check whether an anonymous clone instance is known on a node
*
* \param[in] clone Anonymous clone to check
* \param[in] id Instance ID (without instance number) to check
* \param[in] node Node to check
*
* \return true if \p id instance of \p clone is known on \p node,
* otherwise false
*/
static bool
anonymous_known_on(const pcmk_resource_t *clone, const char *id,
const pcmk_node_t *node)
{
for (GList *iter = clone->priv->children; iter; iter = iter->next) {
pcmk_resource_t *child = iter->data;
/* Use ->find_rsc() because this might be a cloned group, and knowing
* that other members of the group are known here implies nothing.
*/
child = clone->priv->fns->find_rsc(child, id, NULL,
pcmk_rsc_match_clone_only);
CRM_LOG_ASSERT(child != NULL);
if (child != NULL) {
if (g_hash_table_lookup(child->priv->probed_nodes,
node->priv->id)) {
return true;
}
}
}
return false;
}
/*!
* \internal
* \brief Check whether a node is allowed to run a resource
*
* \param[in] rsc Resource to check
* \param[in] node Node to check
*
* \return true if \p node is allowed to run \p rsc, otherwise false
*/
static bool
is_allowed(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
pcmk_node_t *allowed = g_hash_table_lookup(rsc->priv->allowed_nodes,
node->priv->id);
return (allowed != NULL) && (allowed->assign->score >= 0);
}
/*!
* \brief Check whether a clone instance's promotion score should be considered
*
* \param[in] rsc Promotable clone instance to check
* \param[in] node Node where score would be applied
*
* \return true if \p rsc's promotion score should be considered on \p node,
* otherwise false
*/
static bool
promotion_score_applies(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
char *id = clone_strip(rsc->id);
const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
pcmk_resource_t *active = NULL;
const char *reason = "allowed";
// Some checks apply only to anonymous clone instances
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
// If instance is active on the node, its score definitely applies
active = find_active_anon_instance(parent, id, node);
if (active == rsc) {
reason = "active";
goto check_allowed;
}
/* If *no* instance is active on this node, this instance's score will
* count if it has been probed on this node.
*/
if ((active == NULL) && anonymous_known_on(parent, id, node)) {
reason = "probed";
goto check_allowed;
}
}
/* If this clone's status is unknown on *all* nodes (e.g. cluster startup),
* take all instances' scores into account, to make sure we use any
* permanent promotion scores.
*/
if ((rsc->priv->active_nodes == NULL)
&& (g_hash_table_size(rsc->priv->probed_nodes) == 0)) {
reason = "none probed";
goto check_allowed;
}
/* Otherwise, we've probed and/or started the resource *somewhere*, so
* consider promotion scores on nodes where we know the status.
*/
if ((g_hash_table_lookup(rsc->priv->probed_nodes,
node->priv->id) != NULL)
|| (pe_find_node_id(rsc->priv->active_nodes,
node->priv->id) != NULL)) {
reason = "known";
} else {
pcmk__rsc_trace(rsc,
"Ignoring %s promotion score (for %s) on %s: "
"not probed",
rsc->id, id, pcmk__node_name(node));
free(id);
return false;
}
check_allowed:
if (is_allowed(rsc, node)) {
pcmk__rsc_trace(rsc, "Counting %s promotion score (for %s) on %s: %s",
rsc->id, id, pcmk__node_name(node), reason);
free(id);
return true;
}
pcmk__rsc_trace(rsc,
"Ignoring %s promotion score (for %s) on %s: not allowed",
rsc->id, id, pcmk__node_name(node));
free(id);
return false;
}
/*!
* \internal
* \brief Get the value of a promotion score node attribute
*
* \param[in] rsc Promotable clone instance to get promotion score for
* \param[in] node Node to get promotion score for
* \param[in] name Resource name to use in promotion score attribute name
*
* \return Value of promotion score node attribute for \p rsc on \p node
*/
static const char *
promotion_attr_value(const pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *name)
{
char *attr_name = NULL;
const char *attr_value = NULL;
const char *target = NULL;
enum pcmk__rsc_node node_type = pcmk__rsc_node_assigned;
if (pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
// Not assigned yet
node_type = pcmk__rsc_node_current;
}
target = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
attr_name = pcmk_promotion_score_name(name);
attr_value = pcmk__node_attr(node, attr_name, target, node_type);
free(attr_name);
return attr_value;
}
/*!
* \internal
* \brief Get the promotion score for a clone instance on a node
*
* \param[in] rsc Promotable clone instance to get score for
* \param[in] node Node to get score for
* \param[out] is_default If non-NULL, will be set true if no score available
*
* \return Promotion score for \p rsc on \p node (or 0 if none)
*/
static int
promotion_score(const pcmk_resource_t *rsc, const pcmk_node_t *node,
bool *is_default)
{
const char *name = NULL;
const char *attr_value = NULL;
if (is_default != NULL) {
*is_default = true;
}
CRM_CHECK((rsc != NULL) && (node != NULL), return 0);
/* If this is an instance of a cloned group, the promotion score is the sum
* of all members' promotion scores.
*/
if (rsc->priv->children != NULL) {
int score = 0;
for (const GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
bool child_default = false;
int child_score = promotion_score(child, node, &child_default);
if (!child_default && (is_default != NULL)) {
*is_default = false;
}
score += child_score;
}
return score;
}
if (!promotion_score_applies(rsc, node)) {
return 0;
}
/* For the promotion score attribute name, use the name the resource is
* known as in resource history, since that's what crm_attribute --promotion
* would have used.
*/
name = pcmk__s(rsc->priv->history_id, rsc->id);
attr_value = promotion_attr_value(rsc, node, name);
if (attr_value != NULL) {
pcmk__rsc_trace(rsc, "Promotion score for %s on %s = %s",
name, pcmk__node_name(node),
pcmk__s(attr_value, "(unset)"));
} else if (!pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
/* If we don't have any resource history yet, we won't have history_id.
* In that case, for anonymous clones, try the resource name without
* any instance number.
*/
char *rsc_name = clone_strip(rsc->id);
if (strcmp(rsc->id, rsc_name) != 0) {
attr_value = promotion_attr_value(rsc, node, rsc_name);
pcmk__rsc_trace(rsc, "Promotion score for %s on %s (for %s) = %s",
rsc_name, pcmk__node_name(node), rsc->id,
pcmk__s(attr_value, "(unset)"));
}
free(rsc_name);
}
if (attr_value == NULL) {
return 0;
}
if (is_default != NULL) {
*is_default = false;
}
return char2score(attr_value);
}
/*!
* \internal
* \brief Include promotion scores in instances' node scores and priorities
*
* \param[in,out] rsc Promotable clone resource to update
*/
void
pcmk__add_promotion_scores(pcmk_resource_t *rsc)
{
if (pe__set_clone_flag(rsc,
pcmk__clone_promotion_added) == pcmk_rc_already) {
return;
}
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
GHashTableIter iter;
pcmk_node_t *node = NULL;
int score, new_score;
g_hash_table_iter_init(&iter, child_rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (!pcmk__node_available(node, false, false)) {
/* This node will never be promoted, so don't apply the
* promotion score, as that may lead to clone shuffling.
*/
continue;
}
score = promotion_score(child_rsc, node, NULL);
if (score > 0) {
new_score = pcmk__add_scores(node->assign->score, score);
if (new_score != node->assign->score) { // Could remain INFINITY
node->assign->score = new_score;
pcmk__rsc_trace(rsc,
"Added %s promotion priority (%s) to score "
"on %s (now %s)",
child_rsc->id, pcmk_readable_score(score),
pcmk__node_name(node),
pcmk_readable_score(new_score));
}
}
if (score > child_rsc->priv->priority) {
pcmk__rsc_trace(rsc,
"Updating %s priority to promotion score "
"(%d->%d)",
child_rsc->id, child_rsc->priv->priority,
score);
child_rsc->priv->priority = score;
}
}
}
}
/*!
* \internal
* \brief If a resource's current role is started, change it to unpromoted
*
* \param[in,out] data Resource to update
* \param[in] user_data Ignored
*/
static void
set_current_role_unpromoted(void *data, void *user_data)
{
pcmk_resource_t *rsc = (pcmk_resource_t *) data;
if (rsc->priv->orig_role == pcmk_role_started) {
// Promotable clones should use unpromoted role instead of started
rsc->priv->orig_role = pcmk_role_unpromoted;
}
g_list_foreach(rsc->priv->children, set_current_role_unpromoted, NULL);
}
/*!
* \internal
* \brief Set a resource's next role to unpromoted (or stopped if unassigned)
*
* \param[in,out] data Resource to update
* \param[in] user_data Ignored
*/
static void
set_next_role_unpromoted(void *data, void *user_data)
{
pcmk_resource_t *rsc = (pcmk_resource_t *) data;
GList *assigned = NULL;
rsc->priv->fns->location(rsc, &assigned, FALSE);
if (assigned == NULL) {
pe__set_next_role(rsc, pcmk_role_stopped, "stopped instance");
} else {
pe__set_next_role(rsc, pcmk_role_unpromoted, "unpromoted instance");
g_list_free(assigned);
}
g_list_foreach(rsc->priv->children, set_next_role_unpromoted, NULL);
}
/*!
* \internal
* \brief Set a resource's next role to promoted if not already set
*
* \param[in,out] data Resource to update
* \param[in] user_data Ignored
*/
static void
set_next_role_promoted(void *data, gpointer user_data)
{
pcmk_resource_t *rsc = (pcmk_resource_t *) data;
if (rsc->priv->next_role == pcmk_role_unknown) {
pe__set_next_role(rsc, pcmk_role_promoted, "promoted instance");
}
g_list_foreach(rsc->priv->children, set_next_role_promoted, NULL);
}
/*!
* \internal
* \brief Show instance's promotion score on node where it will be active
*
* \param[in,out] instance Promotable clone instance to show
*/
static void
show_promotion_score(pcmk_resource_t *instance)
{
pcmk_node_t *chosen = instance->priv->fns->location(instance, NULL, FALSE);
const char *score_s = NULL;
score_s = pcmk_readable_score(instance->priv->promotion_priority);
if (pcmk_is_set(instance->priv->scheduler->flags,
pcmk__sched_output_scores)
&& !pcmk__is_daemon
&& (instance->priv->scheduler->priv->out != NULL)) {
pcmk__output_t *out = instance->priv->scheduler->priv->out;
out->message(out, "promotion-score", instance, chosen, score_s);
} else if (chosen == NULL) {
pcmk__rsc_debug(pe__const_top_resource(instance, false),
"%s promotion score (inactive): %s (priority=%d)",
instance->id, score_s, instance->priv->priority);
} else {
pcmk__rsc_debug(pe__const_top_resource(instance, false),
"%s promotion score on %s: %s (priority=%d)",
instance->id, pcmk__node_name(chosen),
score_s, instance->priv->priority);
}
}
/*!
* \internal
* \brief Set a clone instance's promotion priority
*
* \param[in,out] data Promotable clone instance to update
* \param[in] user_data Instance's parent clone
*/
static void
set_instance_priority(gpointer data, gpointer user_data)
{
pcmk_resource_t *instance = (pcmk_resource_t *) data;
const pcmk_resource_t *clone = (const pcmk_resource_t *) user_data;
const pcmk_node_t *chosen = NULL;
enum rsc_role_e next_role = pcmk_role_unknown;
GList *list = NULL;
pcmk__rsc_trace(clone, "Assigning priority for %s: %s", instance->id,
pcmk_role_text(instance->priv->next_role));
if (instance->priv->fns->state(instance, TRUE) == pcmk_role_started) {
set_current_role_unpromoted(instance, NULL);
}
// Only an instance that will be active can be promoted
chosen = instance->priv->fns->location(instance, &list, FALSE);
if (pcmk__list_of_multiple(list)) {
pcmk__config_err("Cannot promote non-colocated child %s",
instance->id);
}
g_list_free(list);
if (chosen == NULL) {
return;
}
next_role = instance->priv->fns->state(instance, FALSE);
switch (next_role) {
case pcmk_role_started:
case pcmk_role_unknown:
// Set instance priority to its promotion score (or -1 if none)
{
bool is_default = false;
instance->priv->priority = promotion_score(instance, chosen,
&is_default);
if (is_default) {
/* Default to -1 if no value is set. This allows instances
* eligible for promotion to be specified based solely on
* PCMK_XE_RSC_LOCATION constraints, but prevents any
* instance from being promoted if neither a constraint nor
* a promotion score is present.
*/
instance->priv->priority = -1;
}
}
break;
case pcmk_role_unpromoted:
case pcmk_role_stopped:
// Instance can't be promoted
instance->priv->priority = -PCMK_SCORE_INFINITY;
break;
case pcmk_role_promoted:
// Nothing needed (re-creating actions after scheduling fencing)
break;
default:
CRM_CHECK(FALSE, crm_err("Unknown resource role %d for %s",
next_role, instance->id));
}
// Add relevant location constraint scores for promoted role
apply_promoted_locations(instance, instance->priv->location_constraints,
chosen);
apply_promoted_locations(instance, clone->priv->location_constraints,
chosen);
// Consider instance's role-based colocations with other resources
list = pcmk__this_with_colocations(instance);
for (GList *iter = list; iter != NULL; iter = iter->next) {
pcmk__colocation_t *cons = (pcmk__colocation_t *) iter->data;
instance->priv->cmds->apply_coloc_score(instance, cons->primary, cons,
true);
}
g_list_free(list);
instance->priv->promotion_priority = instance->priv->priority;
if (next_role == pcmk_role_promoted) {
instance->priv->promotion_priority = PCMK_SCORE_INFINITY;
}
pcmk__rsc_trace(clone, "Assigning %s priority = %d",
instance->id, instance->priv->priority);
}
/*!
* \internal
* \brief Set a promotable clone instance's role
*
* \param[in,out] data Promotable clone instance to update
* \param[in,out] user_data Pointer to count of instances chosen for promotion
*/
static void
set_instance_role(gpointer data, gpointer user_data)
{
pcmk_resource_t *instance = (pcmk_resource_t *) data;
int *count = (int *) user_data;
const pcmk_resource_t *clone = pe__const_top_resource(instance, false);
const pcmk_scheduler_t *scheduler = instance->priv->scheduler;
pcmk_node_t *chosen = NULL;
show_promotion_score(instance);
if (instance->priv->promotion_priority < 0) {
pcmk__rsc_trace(clone, "Not supposed to promote instance %s",
instance->id);
} else if ((*count < pe__clone_promoted_max(instance))
|| !pcmk_is_set(clone->flags, pcmk__rsc_managed)) {
chosen = node_to_be_promoted_on(instance);
}
if (chosen == NULL) {
set_next_role_unpromoted(instance, NULL);
return;
}
if ((instance->priv->orig_role < pcmk_role_promoted)
&& !pcmk_is_set(scheduler->flags, pcmk__sched_quorate)
&& (scheduler->no_quorum_policy == pcmk_no_quorum_freeze)) {
crm_notice("Clone instance %s cannot be promoted without quorum",
instance->id);
set_next_role_unpromoted(instance, NULL);
return;
}
chosen->assign->count++;
pcmk__rsc_info(clone, "Choosing %s (%s) on %s for promotion",
instance->id, pcmk_role_text(instance->priv->orig_role),
pcmk__node_name(chosen));
set_next_role_promoted(instance, NULL);
(*count)++;
}
/*!
* \internal
* \brief Set roles for all instances of a promotable clone
*
* \param[in,out] rsc Promotable clone resource to update
*/
void
pcmk__set_instance_roles(pcmk_resource_t *rsc)
{
int promoted = 0;
GHashTableIter iter;
pcmk_node_t *node = NULL;
// Repurpose count to track the number of promoted instances assigned
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
node->assign->count = 0;
}
// Set instances' promotion priorities and sort by highest priority first
g_list_foreach(rsc->priv->children, set_instance_priority, rsc);
sort_promotable_instances(rsc);
// Choose the first N eligible instances to be promoted
g_list_foreach(rsc->priv->children, set_instance_role, &promoted);
pcmk__rsc_info(rsc, "%s: Promoted %d instances of a possible %d",
rsc->id, promoted, pe__clone_promoted_max(rsc));
}
/*!
*
* \internal
* \brief Create actions for promotable clone instances
*
* \param[in,out] clone Promotable clone to create actions for
* \param[out] any_promoting Will be set true if any instance is promoting
* \param[out] any_demoting Will be set true if any instance is demoting
*/
static void
create_promotable_instance_actions(pcmk_resource_t *clone,
bool *any_promoting, bool *any_demoting)
{
for (GList *iter = clone->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
instance->priv->cmds->create_actions(instance);
check_for_role_change(instance, any_demoting, any_promoting);
}
}
/*!
* \internal
* \brief Reset each promotable instance's resource priority
*
* Reset the priority of each instance of a promotable clone to the clone's
* priority (after promotion actions are scheduled, when instance priorities
* were repurposed as promotion scores).
*
* \param[in,out] clone Promotable clone to reset
*/
static void
reset_instance_priorities(pcmk_resource_t *clone)
{
for (GList *iter = clone->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
instance->priv->priority = clone->priv->priority;
}
}
/*!
* \internal
* \brief Create actions specific to promotable clones
*
* \param[in,out] clone Promotable clone to create actions for
*/
void
pcmk__create_promotable_actions(pcmk_resource_t *clone)
{
bool any_promoting = false;
bool any_demoting = false;
// Create actions for each clone instance individually
create_promotable_instance_actions(clone, &any_promoting, &any_demoting);
// Create pseudo-actions for clone as a whole
pe__create_promotable_pseudo_ops(clone, any_promoting, any_demoting);
// Undo our temporary repurposing of resource priority for instances
reset_instance_priorities(clone);
}
/*!
* \internal
* \brief Create internal orderings for a promotable clone's instances
*
* \param[in,out] clone Promotable clone instance to order
*/
void
pcmk__order_promotable_instances(pcmk_resource_t *clone)
{
pcmk_resource_t *previous = NULL; // Needed for ordered clones
pcmk__promotable_restart_ordering(clone);
for (GList *iter = clone->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
// Demote before promote
pcmk__order_resource_actions(instance, PCMK_ACTION_DEMOTE,
instance, PCMK_ACTION_PROMOTE,
pcmk__ar_ordered);
order_instance_promotion(clone, instance, previous);
order_instance_demotion(clone, instance, previous);
previous = instance;
}
}
/*!
* \internal
* \brief Update dependent's allowed nodes for colocation with promotable
*
* \param[in,out] dependent Dependent resource to update
* \param[in] primary Primary resource
* \param[in] primary_node Node where an instance of the primary will be
* \param[in] colocation Colocation constraint to apply
*/
static void
update_dependent_allowed_nodes(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk_node_t *primary_node,
const pcmk__colocation_t *colocation)
{
GHashTableIter iter;
pcmk_node_t *node = NULL;
const char *primary_value = NULL;
const char *attr = colocation->node_attribute;
if (colocation->score >= PCMK_SCORE_INFINITY) {
return; // Colocation is mandatory, so allowed node scores don't matter
}
primary_value = pcmk__colocation_node_attr(primary_node, attr, primary);
pcmk__rsc_trace(colocation->primary,
"Applying %s (%s with %s on %s by %s @%d) to %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, pcmk__node_name(primary_node),
attr, colocation->score, dependent->id);
g_hash_table_iter_init(&iter, dependent->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
const char *dependent_value = pcmk__colocation_node_attr(node, attr,
dependent);
if (pcmk__str_eq(primary_value, dependent_value, pcmk__str_casei)) {
node->assign->score = pcmk__add_scores(node->assign->score,
colocation->score);
pcmk__rsc_trace(colocation->primary,
"Added %s score (%s) to %s (now %s)",
colocation->id,
pcmk_readable_score(colocation->score),
pcmk__node_name(node),
pcmk_readable_score(node->assign->score));
}
}
}
/*!
* \brief Update dependent for a colocation with a promotable clone
*
* \param[in] primary Primary resource in the colocation
* \param[in,out] dependent Dependent resource in the colocation
* \param[in] colocation Colocation constraint to apply
*/
void
pcmk__update_dependent_with_promotable(const pcmk_resource_t *primary,
pcmk_resource_t *dependent,
const pcmk__colocation_t *colocation)
{
GList *affected_nodes = NULL;
/* Build a list of all nodes where an instance of the primary will be, and
* (for optional colocations) update the dependent's allowed node scores for
* each one.
*/
for (GList *iter = primary->priv->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
pcmk_node_t *node = instance->priv->fns->location(instance, NULL,
FALSE);
if (node == NULL) {
continue;
}
if (instance->priv->fns->state(instance,
FALSE) == colocation->primary_role) {
update_dependent_allowed_nodes(dependent, primary, node,
colocation);
affected_nodes = g_list_prepend(affected_nodes, node);
}
}
/* For mandatory colocations, add the primary's node score to the
* dependent's node score for each affected node, and ban the dependent
* from all other nodes.
*
* However, skip this for promoted-with-promoted colocations, otherwise
* inactive dependent instances can't start (in the unpromoted role).
*/
if ((colocation->score >= PCMK_SCORE_INFINITY)
&& ((colocation->dependent_role != pcmk_role_promoted)
|| (colocation->primary_role != pcmk_role_promoted))) {
pcmk__rsc_trace(colocation->primary,
"Applying %s (mandatory %s with %s) to %s",
colocation->id, colocation->dependent->id,
colocation->primary->id, dependent->id);
pcmk__colocation_intersect_nodes(dependent, primary, colocation,
affected_nodes, true);
}
g_list_free(affected_nodes);
}
/*!
* \internal
* \brief Update dependent priority for colocation with promotable
*
* \param[in] primary Primary resource in the colocation
* \param[in,out] dependent Dependent resource in the colocation
* \param[in] colocation Colocation constraint to apply
+ *
+ * \return The score added to the dependent's priority
*/
-void
+int
pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
pcmk_resource_t *dependent,
const pcmk__colocation_t *colocation)
{
pcmk_resource_t *primary_instance = NULL;
// Look for a primary instance where dependent will be
primary_instance = pcmk__find_compatible_instance(dependent, primary,
colocation->primary_role,
false);
if (primary_instance != NULL) {
// Add primary instance's priority to dependent's
int new_priority = pcmk__add_scores(dependent->priv->priority,
colocation->score);
pcmk__rsc_trace(colocation->primary,
"Applying %s (%s with %s) to %s priority "
"(%s + %s = %s)",
colocation->id, colocation->dependent->id,
colocation->primary->id, dependent->id,
pcmk_readable_score(dependent->priv->priority),
pcmk_readable_score(colocation->score),
pcmk_readable_score(new_priority));
dependent->priv->priority = new_priority;
+ return colocation->score;
+ }
- } else if (colocation->score >= PCMK_SCORE_INFINITY) {
+ if (colocation->score >= PCMK_SCORE_INFINITY) {
// Mandatory colocation, but primary won't be here
pcmk__rsc_trace(colocation->primary,
"Applying %s (%s with %s) to %s: can't be promoted",
colocation->id, colocation->dependent->id,
colocation->primary->id, dependent->id);
dependent->priv->priority = -PCMK_SCORE_INFINITY;
+ return -PCMK_SCORE_INFINITY;
}
+ return 0;
}