diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in index 1cf8e523f2..57ef3d43ad 100644 --- a/cts/cts-scheduler.in +++ b/cts/cts-scheduler.in @@ -1,1701 +1,1703 @@ #!@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"), ]), 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 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: rc = self.run_one(self.args.run, "Single shot", self.single_test_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/banned-group-inner-constraints.dot b/cts/scheduler/dot/banned-group-inner-constraints.dot new file mode 100644 index 0000000000..6dc32a7a7f --- /dev/null +++ b/cts/scheduler/dot/banned-group-inner-constraints.dot @@ -0,0 +1,36 @@ + digraph "g" { +"G-RSC1_monitor_30000 node3" [ style=bold color="green" fontcolor="black"] +"G-RSC1_start_0 node3" -> "G-RSC1_monitor_30000 node3" [ style = bold] +"G-RSC1_start_0 node3" -> "G-RSC2_start_0 node3" [ style = bold] +"G-RSC1_start_0 node3" -> "G_running_0" [ style = bold] +"G-RSC1_start_0 node3" [ style=bold color="green" fontcolor="black"] +"G-RSC1_stop_0 node2" -> "G-RSC1_start_0 node3" [ style = bold] +"G-RSC1_stop_0 node2" -> "G_stopped_0" [ style = bold] +"G-RSC1_stop_0 node2" [ style=bold color="green" fontcolor="black"] +"G-RSC2_monitor_30000 node3" [ style=bold color="green" fontcolor="black"] +"G-RSC2_start_0 node3" -> "G-RSC2_monitor_30000 node3" [ style = bold] +"G-RSC2_start_0 node3" -> "G_running_0" [ style = bold] +"G-RSC2_start_0 node3" [ style=bold color="green" fontcolor="black"] +"G-RSC2_stop_0 node2" -> "G-RSC1_stop_0 node2" [ style = bold] +"G-RSC2_stop_0 node2" -> "G-RSC2_start_0 node3" [ style = bold] +"G-RSC2_stop_0 node2" -> "G_stopped_0" [ style = bold] +"G-RSC2_stop_0 node2" -> "dummy_stop_0 node2" [ style = bold] +"G-RSC2_stop_0 node2" [ style=bold color="green" fontcolor="black"] +"G_running_0" [ style=bold color="green" fontcolor="orange"] +"G_start_0" -> "G-RSC1_start_0 node3" [ style = bold] +"G_start_0" -> "G-RSC2_start_0 node3" [ style = bold] +"G_start_0" -> "G_running_0" [ style = bold] +"G_start_0" [ style=bold color="green" fontcolor="orange"] +"G_stop_0" -> "G-RSC1_stop_0 node2" [ style = bold] +"G_stop_0" -> "G-RSC2_stop_0 node2" [ style = bold] +"G_stop_0" -> "G_stopped_0" [ style = bold] +"G_stop_0" [ style=bold color="green" fontcolor="orange"] +"G_stopped_0" -> "G_start_0" [ style = bold] +"G_stopped_0" [ style=bold color="green" fontcolor="orange"] +"dummy_monitor_20000 node3" [ style=bold color="green" fontcolor="black"] +"dummy_start_0 node3" -> "G-RSC2_start_0 node3" [ style = bold] +"dummy_start_0 node3" -> "dummy_monitor_20000 node3" [ style = bold] +"dummy_start_0 node3" [ style=bold color="green" fontcolor="black"] +"dummy_stop_0 node2" -> "dummy_start_0 node3" [ style = bold] +"dummy_stop_0 node2" [ style=bold color="green" fontcolor="black"] +} diff --git a/cts/scheduler/exp/banned-group-inner-constraints.exp b/cts/scheduler/exp/banned-group-inner-constraints.exp new file mode 100644 index 0000000000..84ccb51ed9 --- /dev/null +++ b/cts/scheduler/exp/banned-group-inner-constraints.exp @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cts/scheduler/scores/banned-group-inner-constraints.scores b/cts/scheduler/scores/banned-group-inner-constraints.scores new file mode 100644 index 0000000000..57d1f349b2 --- /dev/null +++ b/cts/scheduler/scores/banned-group-inner-constraints.scores @@ -0,0 +1,36 @@ + +pcmk__group_assign: G allocation score on node1: 0 +pcmk__group_assign: G allocation score on node2: -INFINITY +pcmk__group_assign: G allocation score on node3: 0 +pcmk__group_assign: G allocation score on node4: 0 +pcmk__group_assign: G allocation score on node5: 0 +pcmk__group_assign: G-RSC1 allocation score on node1: 0 +pcmk__group_assign: G-RSC1 allocation score on node2: -INFINITY +pcmk__group_assign: G-RSC1 allocation score on node3: 0 +pcmk__group_assign: G-RSC1 allocation score on node4: 0 +pcmk__group_assign: G-RSC1 allocation score on node5: 0 +pcmk__group_assign: G-RSC2 allocation score on node1: 0 +pcmk__group_assign: G-RSC2 allocation score on node2: 0 +pcmk__group_assign: G-RSC2 allocation score on node3: 0 +pcmk__group_assign: G-RSC2 allocation score on node4: 0 +pcmk__group_assign: G-RSC2 allocation score on node5: 0 +pcmk__primitive_assign: Fencing allocation score on node1: 0 +pcmk__primitive_assign: Fencing allocation score on node2: 0 +pcmk__primitive_assign: Fencing allocation score on node3: 0 +pcmk__primitive_assign: Fencing allocation score on node4: 0 +pcmk__primitive_assign: Fencing allocation score on node5: 0 +pcmk__primitive_assign: G-RSC1 allocation score on node1: -INFINITY +pcmk__primitive_assign: G-RSC1 allocation score on node2: -INFINITY +pcmk__primitive_assign: G-RSC1 allocation score on node3: 0 +pcmk__primitive_assign: G-RSC1 allocation score on node4: -INFINITY +pcmk__primitive_assign: G-RSC1 allocation score on node5: -INFINITY +pcmk__primitive_assign: G-RSC2 allocation score on node1: -INFINITY +pcmk__primitive_assign: G-RSC2 allocation score on node2: -INFINITY +pcmk__primitive_assign: G-RSC2 allocation score on node3: 0 +pcmk__primitive_assign: G-RSC2 allocation score on node4: -INFINITY +pcmk__primitive_assign: G-RSC2 allocation score on node5: -INFINITY +pcmk__primitive_assign: dummy allocation score on node1: 0 +pcmk__primitive_assign: dummy allocation score on node2: -INFINITY +pcmk__primitive_assign: dummy allocation score on node3: 0 +pcmk__primitive_assign: dummy allocation score on node4: 0 +pcmk__primitive_assign: dummy allocation score on node5: 0 diff --git a/cts/scheduler/summary/banned-group-inner-constraints.summary b/cts/scheduler/summary/banned-group-inner-constraints.summary new file mode 100644 index 0000000000..77dc3db901 --- /dev/null +++ b/cts/scheduler/summary/banned-group-inner-constraints.summary @@ -0,0 +1,41 @@ +Current cluster status: + * Node List: + * Online: [ node1 node2 node3 node4 node5 ] + + * Full List of Resources: + * Fencing (stonith:fence_xvm): Started node1 + * dummy (ocf:pacemaker:Dummy): Started node2 + * Resource Group: G: + * G-RSC1 (ocf:pacemaker:Dummy): Started node2 + * G-RSC2 (ocf:pacemaker:Dummy): Started node2 + +Transition Summary: + * Move dummy ( node2 -> node3 ) + * Move G-RSC1 ( node2 -> node3 ) + * Move G-RSC2 ( node2 -> node3 ) + +Executing Cluster Transition: + * Pseudo action: G_stop_0 + * Resource action: G-RSC2 stop on node2 + * Resource action: dummy stop on node2 + * Resource action: G-RSC1 stop on node2 + * Resource action: dummy start on node3 + * Pseudo action: G_stopped_0 + * Pseudo action: G_start_0 + * Resource action: G-RSC1 start on node3 + * Resource action: G-RSC2 start on node3 + * Resource action: dummy monitor=20000 on node3 + * Pseudo action: G_running_0 + * Resource action: G-RSC1 monitor=30000 on node3 + * Resource action: G-RSC2 monitor=30000 on node3 + +Revised Cluster Status: + * Node List: + * Online: [ node1 node2 node3 node4 node5 ] + + * Full List of Resources: + * Fencing (stonith:fence_xvm): Started node1 + * dummy (ocf:pacemaker:Dummy): Started node3 + * Resource Group: G: + * G-RSC1 (ocf:pacemaker:Dummy): Started node3 + * G-RSC2 (ocf:pacemaker:Dummy): Started node3 diff --git a/cts/scheduler/xml/banned-group-inner-constraints.xml b/cts/scheduler/xml/banned-group-inner-constraints.xml new file mode 100644 index 0000000000..cef377e4a7 --- /dev/null +++ b/cts/scheduler/xml/banned-group-inner-constraints.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c index 5db7b847ff..fd8624107b 100644 --- a/lib/pacemaker/pcmk_sched_colocation.c +++ b/lib/pacemaker/pcmk_sched_colocation.c @@ -1,1925 +1,1961 @@ /* * 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 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->private->priority > rsc2->private->priority) { return -1; } if (rsc1->private->priority < rsc2->private->priority) { return 1; } // Process clones before primitives and groups if (rsc1->private->variant > rsc2->private->variant) { return -1; } if (rsc1->private->variant < rsc2->private->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->private->this_with_colocations), new_con, dependent); pcmk__add_with_this(&(primary->private->with_this_colocations), new_con, primary); dependent->private->scheduler->colocation_constraints = g_list_prepend(dependent->private->scheduler->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->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->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->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->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->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->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->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->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->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->resources, primary_id); dependent = pcmk__find_constraint_resource(scheduler->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->private->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->private->scheduler); } } // If parent resource can't perform an action, neither can any children for (iter = rsc->private->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->private->parent != NULL) { rsc = rsc->private->parent; // Bundle } // Colocation fails only if entire primary can't reach desired role for (iter = rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *child = iter->data; pcmk_action_t *child_action = NULL; child_action = find_first_action(child->private->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->private->parent != NULL) && pcmk_is_set(dependent_role_rsc->private->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->private->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->private->next_role)); return pcmk__coloc_affects_nothing; } if ((colocation->primary_role != pcmk_role_unknown) && (colocation->primary_role != primary_role_rsc->private->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->private->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->private->assigned_node != NULL) { value = pcmk__colocation_node_attr(primary->private->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->private->allowed_nodes); g_hash_table_iter_init(&iter, work); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (primary->private->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->private->allowed_nodes); dependent->private->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 */ void 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; 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)); primary_node = primary->private->assigned_node; dependent_node = dependent->private->assigned_node; if ((primary_node == NULL) || (dependent_node == NULL)) { return; } 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->private->priority = -PCMK_SCORE_INFINITY; } return; } if ((colocation->primary_role != pcmk_role_unknown) && (colocation->primary_role != primary_role_rsc->private->next_role)) { return; } if (colocation->dependent_role == pcmk_role_unpromoted) { score_multiplier = -1; } dependent->private->priority = pcmk__add_scores(score_multiplier * colocation->score, dependent->private->priority); pcmk__rsc_trace(dependent, "Applied %s to %s promotion priority (now %s after %s %s)", colocation->id, dependent->id, pcmk_readable_score(dependent->private->priority), ((score_multiplier == 1)? "adding" : "subtracting"), pcmk_readable_score(colocation->score)); } /*! * \internal * \brief Find score of highest-scored node that matches colocation attribute * - * \param[in] 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 + * \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_resource_t *rsc, const char *attr, +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->private->parent) + && (rsc != rsc->private->parent->private->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->private->allowed_nodes; + rsc->private->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig); + for (GList *loc_iter = rsc->private->scheduler->placement_constraints; + loc_iter != NULL; loc_iter = loc_iter->next) { + + pcmk__location_t *location = loc_iter->data; + + if (location->rsc == rsc->private->parent) { + rsc->private->cmds->apply_location(rsc, location); + } + } + } + // Find best allowed node with matching attribute g_hash_table_iter_init(&iter, rsc->private->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->private->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->private->allowed_nodes); + rsc->private->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->private->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] source_rsc Resource whose node scores to add + * \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, - const pcmk_resource_t *source_rsc, + 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(source_rsc, attr, value); + 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->private->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->private->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->private->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->private->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->private->cmds->add_colocated_node_scores(dependent, primary, dependent->id, &(primary->private->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->private->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->private->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->private->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->private->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 0f23ba7e12..004d1c97d6 100644 --- a/lib/pacemaker/pcmk_sched_group.c +++ b/lib/pacemaker/pcmk_sched_group.c @@ -1,1001 +1,1013 @@ /* * 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" /*! * \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->private->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->private->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->private->children->data; rsc->private->orig_role = first_member->private->orig_role; pe__show_node_scores(!pcmk_is_set(rsc->private->scheduler->flags, pcmk__sched_output_scores), rsc, __func__, rsc->private->allowed_nodes, rsc->private->scheduler); for (GList *iter = rsc->private->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->private->cmds->assign(member, prefer, stop_if_fail); if (first_assigned_node == NULL) { first_assigned_node = node; } } pe__set_next_role(rsc, first_member->private->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->private->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->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; member->private->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->private->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->private->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->private->parent, PCMK_ACTION_DEMOTE, member, PCMK_ACTION_DEMOTE, down_flags); pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE, member->private->parent, PCMK_ACTION_DEMOTED, post_down_flags); // Promote group -> promote member -> group is promoted pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE, member->private->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->private->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->private->parent, member, down_flags); pcmk__order_resource_actions(member, PCMK_ACTION_STOP, member->private->parent, PCMK_ACTION_STOPPED, post_down_flags); // Start group -> start member -> group is started pcmk__order_starts(member->private->parent, member, pcmk__ar_then_implies_first_graphed); pcmk__order_resource_actions(member, PCMK_ACTION_START, member->private->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->private->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->private->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->private->parent, member, pcmk__ar_none); if (member_data->promotable) { pcmk__order_resource_actions(member->private->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->private->active_nodes != NULL) && (member_data->previous_member->private->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->private->active_nodes != NULL) { if (member_data->ordered && (member_data->previous_member != NULL) && (member_data->previous_member->private->active_nodes == NULL) && (member_data->last_active != NULL) && (member_data->last_active->private->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->private->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 */ static void colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { pcmk_resource_t *member = NULL; if (dependent->private->children == NULL) { return; } 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->private->children->data; member->private->cmds->apply_coloc_score(member, primary, colocation, true); return; } 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; } // Colocate each member individually for (GList *iter = dependent->private->children; iter != NULL; iter = iter->next) { member = (pcmk_resource_t *) iter->data; member->private->cmds->apply_coloc_score(member, primary, colocation, true); } } /*! * \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 */ static void colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { 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; } 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->private->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->private->children->data; } if (member == NULL) { return; // Nothing to colocate with } member->private->cmds->apply_coloc_score(dependent, member, colocation, false); return; } if (colocation->score >= PCMK_SCORE_INFINITY) { pcmk__config_err("%s: Cannot perform mandatory colocation with" " non-colocated group %s", dependent->id, primary->id); return; } // Colocate dependent with each member individually for (const GList *iter = primary->private->children; iter != NULL; iter = iter->next) { member = iter->data; member->private->cmds->apply_coloc_score(dependent, member, colocation, false); } } /*! * \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 */ void 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); } else { // Method should only be called for primitive dependents CRM_ASSERT(pcmk__is_primitive(dependent)); 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->private->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->private->actions, NULL, task_s, node); if (member_action != NULL) { uint32_t member_flags = 0U; member_flags = member->private->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->private->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->private->actions, NULL, then->task, node); if (member_action == NULL) { continue; } changed |= member->private->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; - bool reset_scores = true; CRM_ASSERT(pcmk__is_group(rsc) && (location != NULL)); + // Save the constraint's original node list (with the constraint score) node_list_orig = location->nodes; - node_list_copy = pcmk__copy_node_list(node_list_orig, true); - reset_scores = pe__group_flag_is_set(rsc, pcmk__group_colocated); - // Apply the constraint for the group itself (updates node scores) + // 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->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; - member->private->cmds->apply_location(member, location); - - if (reset_scores) { - /* The first member of colocated groups needs to use the original - * node scores, but subsequent members should work on a copy, since - * the first member's scores already incorporate theirs. + if (pe__group_flag_is_set(rsc, pcmk__group_colocated) + && (iter != rsc->private->children)) { + /* When apply_location() is called below for the first member (iter + * == rsc->private->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. */ - reset_scores = false; location->nodes = node_list_copy; } + + member->private->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->private->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->private->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *member = iter->data; colocated_rscs = member->private->cmds->colocated_resources(member, orig_rsc, colocated_rscs); } } else if (rsc->private->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->private->parent; // Ignore empty groups if (rsc->private->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->private->with_this_colocations, orig_rsc); // If cloned, add any relevant colocations with the clone if (parent != NULL) { parent->private->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->private->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *member = iter->data; if (member == orig_rsc) { continue; } member->private->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->private->parent; // Ignore empty groups if (rsc->private->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->private->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->private->this_with_colocations, orig_rsc); // If cloned, add any relevant colocations involving the clone if (parent != NULL) { parent->private->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->private->children; iter != NULL; iter = iter->next) { member = iter->data; if (member == orig_rsc) { continue; } member->private->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->private->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->private->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->private->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->private->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->private->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->private->parent)) { // Every group member will be on same node, so sum all members for (GList *iter = rsc->private->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->private->cmds->add_utilization(member, orig_rsc, all_rscs, utilization); } } } else if (rsc->private->children != NULL) { // Just add first member's utilization member = (pcmk_resource_t *) rsc->private->children->data; if ((member != NULL) && pcmk_is_set(member->flags, pcmk__rsc_unassigned) && (g_list_find(all_rscs, member) == NULL)) { member->private->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->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; member->private->cmds->shutdown_lock(member); } } diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c index f881cc4af7..2a7fbe383c 100644 --- a/lib/pacemaker/pcmk_sched_location.c +++ b/lib/pacemaker/pcmk_sched_location.c @@ -1,732 +1,732 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include "libpacemaker_private.h" static int get_node_score(const char *rule, const char *score, bool raw, pcmk_node_t *node, pcmk_resource_t *rsc) { int score_f = 0; if (score == NULL) { pcmk__config_warn("Rule %s: no score specified (assuming 0)", rule); } else if (raw) { score_f = char2score(score); } else { const char *target = NULL; const char *attr_score = NULL; target = g_hash_table_lookup(rsc->private->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET); attr_score = pcmk__node_attr(node, score, target, pcmk__rsc_node_current); if (attr_score == NULL) { crm_debug("Rule %s: %s did not have a value for %s", rule, pcmk__node_name(node), score); score_f = -PCMK_SCORE_INFINITY; } else { crm_debug("Rule %s: %s had value %s for %s", rule, pcmk__node_name(node), attr_score, score); score_f = char2score(attr_score); } } return score_f; } /*! * \internal * \brief Parse a role configuration for a location constraint * * \param[in] role_spec Role specification * \param[out] role Where to store parsed role * * \return true if role specification is valid, otherwise false */ static bool parse_location_role(const char *role_spec, enum rsc_role_e *role) { if (role_spec == NULL) { *role = pcmk_role_unknown; return true; } *role = pcmk_parse_role(role_spec); switch (*role) { case pcmk_role_unknown: return false; case pcmk_role_started: case pcmk_role_unpromoted: /* Any promotable clone instance cannot be promoted without being in * the unpromoted role first. Therefore, any constraint for the * started or unpromoted role applies to every role. */ *role = pcmk_role_unknown; break; default: break; } return true; } /*! * \internal * \brief Generate a location constraint from a rule * * \param[in,out] rsc Resource that constraint is for * \param[in] rule_xml Rule XML (sub-element of location constraint) * \param[in] discovery Value of \c PCMK_XA_RESOURCE_DISCOVERY for * constraint * \param[out] next_change Where to set when rule evaluation will change * \param[in,out] rule_input Values used to evaluate rule criteria * (node-specific values will be overwritten by * this function) * * \return true if rule is valid, otherwise false */ static bool generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml, const char *discovery, crm_time_t *next_change, pcmk_rule_input_t *rule_input) { const char *rule_id = NULL; const char *score = NULL; const char *boolean = NULL; const char *role_spec = NULL; GList *iter = NULL; bool raw_score = true; bool score_allocated = false; pcmk__location_t *location_rule = NULL; enum rsc_role_e role = pcmk_role_unknown; enum pcmk__combine combine = pcmk__combine_unknown; rule_xml = pcmk__xe_resolve_idref(rule_xml, rsc->private->scheduler->input); if (rule_xml == NULL) { return false; // Error already logged } rule_id = crm_element_value(rule_xml, PCMK_XA_ID); if (rule_id == NULL) { pcmk__config_err("Ignoring " PCMK_XE_RULE " without " PCMK_XA_ID " in location constraint"); return false; } boolean = crm_element_value(rule_xml, PCMK_XA_BOOLEAN_OP); role_spec = crm_element_value(rule_xml, PCMK_XA_ROLE); if (parse_location_role(role_spec, &role)) { crm_trace("Setting rule %s role filter to %s", rule_id, role_spec); } else { pcmk__config_err("Ignoring rule %s: Invalid " PCMK_XA_ROLE " '%s'", rule_id, role_spec); return false; } crm_trace("Processing location constraint rule %s", rule_id); score = crm_element_value(rule_xml, PCMK_XA_SCORE); if (score == NULL) { score = crm_element_value(rule_xml, PCMK_XA_SCORE_ATTRIBUTE); if (score != NULL) { raw_score = false; } } combine = pcmk__parse_combine(boolean); switch (combine) { case pcmk__combine_and: case pcmk__combine_or: break; default: /* @COMPAT When we can break behavioral backward compatibility, * return false */ pcmk__config_warn("Location constraint rule %s has invalid " PCMK_XA_BOOLEAN_OP " value '%s', using default " "'" PCMK_VALUE_AND "'", rule_id, boolean); combine = pcmk__combine_and; break; } location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL); CRM_CHECK(location_rule != NULL, return NULL); location_rule->role_filter = role; if ((rule_input->rsc_id != NULL) && (rule_input->rsc_id_nmatches > 0) && !raw_score) { char *result = pcmk__replace_submatches(score, rule_input->rsc_id, rule_input->rsc_id_submatches, rule_input->rsc_id_nmatches); if (result != NULL) { score = result; score_allocated = true; } } for (iter = rsc->private->scheduler->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = iter->data; rule_input->node_attrs = node->private->attrs; rule_input->rsc_params = pe_rsc_params(rsc, node, rsc->private->scheduler); if (pcmk_evaluate_rule(rule_xml, rule_input, next_change) == pcmk_rc_ok) { pcmk_node_t *local = pe__copy_node(node); location_rule->nodes = g_list_prepend(location_rule->nodes, local); local->assign->score = get_node_score(rule_id, score, raw_score, node, rsc); crm_trace("%s has score %s after %s", pcmk__node_name(node), pcmk_readable_score(local->assign->score), rule_id); } } if (score_allocated) { free((char *)score); } if (location_rule->nodes == NULL) { crm_trace("No matching nodes for location constraint rule %s", rule_id); } else { crm_trace("Location constraint rule %s matched %d nodes", rule_id, g_list_length(location_rule->nodes)); } return true; } static void unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc, const char *role_spec, const char *score, char *rsc_id_match, int rsc_id_nmatches, regmatch_t *rsc_id_submatches) { const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); const char *id = crm_element_value(xml_obj, PCMK_XA_ID); const char *node = crm_element_value(xml_obj, PCMK_XA_NODE); const char *discovery = crm_element_value(xml_obj, PCMK_XA_RESOURCE_DISCOVERY); if (rsc == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not exist", id, rsc_id); return; } if (score == NULL) { score = crm_element_value(xml_obj, PCMK_XA_SCORE); } if ((node != NULL) && (score != NULL)) { int score_i = char2score(score); pcmk_node_t *match = pcmk_find_node(rsc->private->scheduler, node); enum rsc_role_e role = pcmk_role_unknown; pcmk__location_t *location = NULL; if (match == NULL) { crm_info("Ignoring location constraint %s " "because '%s' is not a known node", pcmk__s(id, "without ID"), node); return; } if (role_spec == NULL) { role_spec = crm_element_value(xml_obj, PCMK_XA_ROLE); } if (parse_location_role(role_spec, &role)) { crm_trace("Setting location constraint %s role filter: %s", id, role_spec); } else { /* @COMPAT The previous behavior of creating the constraint ignoring * the role is retained for now, but we should ignore the entire * constraint when we can break backward compatibility. */ pcmk__config_err("Ignoring role in constraint %s: " "Invalid value '%s'", id, role_spec); } location = pcmk__new_location(id, rsc, score_i, discovery, match); if (location == NULL) { return; // Error already logged } location->role_filter = role; } else { bool empty = true; crm_time_t *next_change = crm_time_new_undefined(); pcmk_rule_input_t rule_input = { .now = rsc->private->scheduler->now, .rsc_meta = rsc->private->meta, .rsc_id = rsc_id_match, .rsc_id_submatches = rsc_id_submatches, .rsc_id_nmatches = rsc_id_nmatches, }; /* This loop is logically parallel to pcmk__evaluate_rules(), except * instead of checking whether any rule is active, we set up location * constraints for each active rule. * * @COMPAT When we can break backward compatibility, limit location * constraints to a single rule, for consistency with other contexts. * Since a rule may contain other rules, this does not prohibit any * existing use cases. */ for (xmlNode *rule_xml = pcmk__xe_first_child(xml_obj, PCMK_XE_RULE, NULL, NULL); rule_xml != NULL; rule_xml = pcmk__xe_next_same(rule_xml)) { if (generate_location_rule(rsc, rule_xml, discovery, next_change, &rule_input)) { if (empty) { empty = false; continue; } pcmk__warn_once(pcmk__wo_location_rules, "Support for multiple " PCMK_XE_RULE " elements in a location constraint is " "deprecated and will be removed in a future " "release (use a single new rule combining the " "previous rules with " PCMK_XA_BOOLEAN_OP " set to '" PCMK_VALUE_OR "' instead)"); } } if (empty) { pcmk__config_err("Ignoring constraint '%s' because it contains " "no valid rules", id); } /* If there is a point in the future when the evaluation of a rule will * change, make sure the scheduler is re-run by that time. */ if (crm_time_is_defined(next_change)) { time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(t, rsc->private->scheduler, "location rule evaluation"); } crm_time_free(next_change); } } static void unpack_simple_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { const char *id = crm_element_value(xml_obj, PCMK_XA_ID); const char *value = crm_element_value(xml_obj, PCMK_XA_RSC); if (value) { pcmk_resource_t *rsc; rsc = pcmk__find_constraint_resource(scheduler->resources, value); unpack_rsc_location(xml_obj, rsc, NULL, NULL, NULL, 0, NULL); } value = crm_element_value(xml_obj, PCMK_XA_RSC_PATTERN); if (value) { regex_t regex; bool invert = false; if (value[0] == '!') { value++; invert = true; } if (regcomp(®ex, value, REG_EXTENDED) != 0) { pcmk__config_err("Ignoring constraint '%s' because " PCMK_XA_RSC_PATTERN " has invalid value '%s'", id, value); return; } for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) { pcmk_resource_t *r = iter->data; int nregs = 0; regmatch_t *pmatch = NULL; int status; if (regex.re_nsub > 0) { nregs = regex.re_nsub + 1; } else { nregs = 1; } pmatch = pcmk__assert_alloc(nregs, sizeof(regmatch_t)); status = regexec(®ex, r->id, nregs, pmatch, 0); if (!invert && (status == 0)) { crm_debug("'%s' matched '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, r->id, nregs, pmatch); } else if (invert && (status != 0)) { crm_debug("'%s' is an inverted match of '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, NULL, 0, NULL); } else { crm_trace("'%s' does not match '%s' for %s", r->id, value, id); } free(pmatch); } regfree(®ex); } } // \return Standard Pacemaker return code static int unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pcmk_scheduler_t *scheduler) { const char *id = NULL; const char *rsc_id = NULL; const char *state = NULL; pcmk_resource_t *rsc = NULL; pcmk__idref_t *tag = NULL; xmlNode *rsc_set = NULL; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return pcmk_rc_unpack_error; } // Check whether there are any resource sets with template or tag references *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION); return pcmk_rc_ok; } rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); if (rsc_id == NULL) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, rsc_id); return pcmk_rc_unpack_error; } else if (rsc != NULL) { // No template is referenced return pcmk_rc_ok; } state = crm_element_value(xml_obj, PCMK_XA_ROLE); *expanded_xml = pcmk__xml_copy(NULL, xml_obj); /* Convert any template or tag reference into constraint * PCMK_XE_RESOURCE_SET */ if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false, scheduler)) { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (rsc_set != NULL) { if (state != NULL) { /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as * PCMK_XA_ROLE attribute */ crm_xml_add(rsc_set, PCMK_XA_ROLE, state); pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_ROLE); } crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION); } else { // No sets pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int unpack_location_set(xmlNode *location, xmlNode *set, pcmk_scheduler_t *scheduler) { xmlNode *xml_rsc = NULL; pcmk_resource_t *resource = NULL; const char *set_id; const char *role; const char *local_score; CRM_CHECK(set != NULL, return EINVAL); set_id = pcmk__xe_id(set); if (set_id == NULL) { pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET " without " PCMK_XA_ID " in constraint '%s'", pcmk__s(pcmk__xe_id(location), "(missing ID)")); return pcmk_rc_unpack_error; } role = crm_element_value(set, PCMK_XA_ROLE); local_score = crm_element_value(set, PCMK_XA_SCORE); for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { resource = pcmk__find_constraint_resource(scheduler->resources, pcmk__xe_id(xml_rsc)); if (resource == NULL) { pcmk__config_err("%s: No resource found for %s", set_id, pcmk__xe_id(xml_rsc)); return pcmk_rc_unpack_error; } unpack_rsc_location(location, resource, role, local_score, NULL, 0, NULL); } return pcmk_rc_ok; } void pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { xmlNode *set = NULL; bool any_sets = false; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; if (unpack_location_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) { return; } if (expanded_xml) { orig_xml = xml_obj; xml_obj = expanded_xml; } for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL); set != NULL; set = pcmk__xe_next_same(set)) { any_sets = true; set = pcmk__xe_resolve_idref(set, scheduler->input); if ((set == NULL) // Configuration error, message already logged || (unpack_location_set(xml_obj, set, scheduler) != pcmk_rc_ok)) { if (expanded_xml) { pcmk__xml_free(expanded_xml); } return; } } if (expanded_xml) { pcmk__xml_free(expanded_xml); xml_obj = orig_xml; } if (!any_sets) { unpack_simple_location(xml_obj, scheduler); } } /*! * \internal * \brief Add a new location constraint to scheduler data * * \param[in] id XML ID of location constraint * \param[in,out] rsc Resource in location constraint * \param[in] node_score Constraint score * \param[in] probe_mode When resource should be probed on node * \param[in] node Node in constraint (or NULL if rule-based) * * \return Newly allocated location constraint on success, otherwise NULL * \note The result will be added to the cluster (via \p rsc) and should not be * freed separately. */ pcmk__location_t * pcmk__new_location(const char *id, pcmk_resource_t *rsc, int node_score, const char *probe_mode, pcmk_node_t *node) { pcmk__location_t *new_con = NULL; CRM_CHECK((node != NULL) || (node_score == 0), return NULL); if (id == NULL) { pcmk__config_err("Invalid constraint: no ID specified"); return NULL; } if (rsc == NULL) { pcmk__config_err("Invalid constraint %s: no resource specified", id); return NULL; } new_con = pcmk__assert_alloc(1, sizeof(pcmk__location_t)); new_con->id = pcmk__str_copy(id); new_con->rsc = rsc; new_con->nodes = NULL; new_con->role_filter = pcmk_role_unknown; if (pcmk__str_eq(probe_mode, PCMK_VALUE_ALWAYS, pcmk__str_null_matches|pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_always; } else if (pcmk__str_eq(probe_mode, PCMK_VALUE_NEVER, pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_never; } else if (pcmk__str_eq(probe_mode, PCMK_VALUE_EXCLUSIVE, pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_exclusive; pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes); } else { pcmk__config_err("Invalid " PCMK_XA_RESOURCE_DISCOVERY " value %s " "in location constraint", probe_mode); } if (node != NULL) { pcmk_node_t *copy = pe__copy_node(node); copy->assign->score = node_score; new_con->nodes = g_list_prepend(NULL, copy); } rsc->private->scheduler->placement_constraints = g_list_prepend(rsc->private->scheduler->placement_constraints, new_con); rsc->private->location_constraints = g_list_prepend(rsc->private->location_constraints, new_con); return new_con; } /*! * \internal * \brief Apply all location constraints * * \param[in,out] scheduler Scheduler data */ void pcmk__apply_locations(pcmk_scheduler_t *scheduler) { for (GList *iter = scheduler->placement_constraints; iter != NULL; iter = iter->next) { pcmk__location_t *location = iter->data; location->rsc->private->cmds->apply_location(location->rsc, location); } } /*! * \internal * \brief Apply a location constraint to a resource's allowed node scores * * \param[in,out] rsc Resource to apply constraint to * \param[in,out] location Location constraint to apply * * \note This does not consider the resource's children, so the resource's * apply_location() method should be used instead in most cases. */ void pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *location) { bool need_role = false; CRM_ASSERT((rsc != NULL) && (location != NULL)); // If a role was specified, ensure constraint is applicable need_role = (location->role_filter > pcmk_role_unknown); if (need_role && (location->role_filter != rsc->private->next_role)) { pcmk__rsc_trace(rsc, "Not applying %s to %s because role will be %s not %s", location->id, rsc->id, pcmk_role_text(rsc->private->next_role), pcmk_role_text(location->role_filter)); return; } if (location->nodes == NULL) { pcmk__rsc_trace(rsc, "Not applying %s to %s because no nodes match", location->id, rsc->id); return; } - pcmk__rsc_trace(rsc, "Applying %s%s%s to %s", location->id, - (need_role? " for role " : ""), - (need_role? pcmk_role_text(location->role_filter) : ""), - rsc->id); - for (GList *iter = location->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = iter->data; pcmk_node_t *allowed_node = NULL; allowed_node = g_hash_table_lookup(rsc->private->allowed_nodes, node->private->id); + + pcmk__rsc_trace(rsc, "Applying %s%s%s to %s score on %s: %c %s", + location->id, + (need_role? " for role " : ""), + (need_role? pcmk_role_text(location->role_filter) : ""), + rsc->id, pcmk__node_name(node), + ((allowed_node == NULL)? '=' : '+'), + pcmk_readable_score(node->assign->score)); + if (allowed_node == NULL) { - pcmk__rsc_trace(rsc, "* = %d on %s", - node->assign->score, pcmk__node_name(node)); allowed_node = pe__copy_node(node); g_hash_table_insert(rsc->private->allowed_nodes, (gpointer) allowed_node->private->id, allowed_node); } else { - pcmk__rsc_trace(rsc, "* + %d on %s", - node->assign->score, pcmk__node_name(node)); allowed_node->assign->score = pcmk__add_scores(allowed_node->assign->score, node->assign->score); } if (allowed_node->assign->probe_mode < location->probe_mode) { if (location->probe_mode == pcmk__probe_exclusive) { pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes); } /* exclusive > never > always... always is default */ allowed_node->assign->probe_mode = location->probe_mode; } } }