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; }