diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in
index 268cc3dd3a..feb5dc852b 100644
--- a/cts/cts-scheduler.in
+++ b/cts/cts-scheduler.in
@@ -1,1601 +1,1602 @@
#!@PYTHON@
""" Regression tests for Pacemaker's scheduler
"""
__copyright__ = "Copyright 2004-2022 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
DESC = """Regression tests for Pacemaker's scheduler"""
# 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 = [
[
[ "simple1", "Offline" ],
[ "simple2", "Start" ],
[ "simple3", "Start 2" ],
[ "simple4", "Start Failed" ],
[ "simple6", "Stop Start" ],
[ "simple7", "Shutdown" ],
#[ "simple8", "Stonith" ],
#[ "simple9", "Lower version" ],
#[ "simple10", "Higher version" ],
[ "simple11", "Priority (ne)" ],
[ "simple12", "Priority (eq)" ],
[ "simple8", "Stickiness" ],
],
[
[ "group1", "Group" ],
[ "group2", "Group + Native" ],
[ "group3", "Group + Group" ],
[ "group4", "Group + Native (nothing)" ],
[ "group5", "Group + Native (move)" ],
[ "group6", "Group + Group (move)" ],
[ "group7", "Group colocation" ],
[ "group13", "Group colocation (cant run)" ],
[ "group8", "Group anti-colocation" ],
[ "group9", "Group recovery" ],
[ "group10", "Group partial recovery" ],
[ "group11", "Group target_role" ],
[ "group14", "Group stop (graph terminated)" ],
[ "group15", "Negative group colocation" ],
[ "bug-1573", "Partial stop of a group with two children" ],
[ "bug-1718", "Mandatory group ordering - Stop group_FUN" ],
[ "bug-lf-2613", "Move group on failure" ],
[ "bug-lf-2619", "Move group on clone failure" ],
[ "group-fail", "Ensure stop order is preserved for partially active groups" ],
[ "group-unmanaged", "No need to restart r115 because r114 is unmanaged" ],
[ "group-unmanaged-stopped", "Make sure r115 is stopped when r114 fails" ],
[ "group-dependents", "Account for the location preferences of things colocated with a group" ],
[ "group-stop-ordering", "Ensure blocked group member stop does not force other member stops" ],
[ "colocate-unmanaged-group", "Respect mandatory colocations even if earlier group member is unmanaged" ],
],
[
[ "rsc_dep1", "Must not" ],
[ "rsc_dep3", "Must" ],
[ "rsc_dep5", "Must not 3" ],
[ "rsc_dep7", "Must 3" ],
[ "rsc_dep10", "Must (but cant)" ],
[ "rsc_dep2", "Must (running)" ],
[ "rsc_dep8", "Must (running : alt)" ],
[ "rsc_dep4", "Must (running + move)" ],
[ "asymmetric", "Asymmetric - require explicit location constraints" ],
],
[
[ "orphan-0", "Orphan ignore" ],
[ "orphan-1", "Orphan stop" ],
[ "orphan-2", "Orphan stop, remove failcount" ],
],
[
[ "params-0", "Params: No change" ],
[ "params-1", "Params: Changed" ],
[ "params-2", "Params: Resource definition" ],
[ "params-3", "Params: Restart instead of reload if start pending" ],
[ "params-4", "Params: Reload" ],
[ "params-5", "Params: Restart based on probe digest" ],
[ "novell-251689", "Resource definition change + target_role=stopped" ],
[ "bug-lf-2106", "Restart all anonymous clone instances after config change" ],
[ "params-6", "Params: Detect reload in previously migrated resource" ],
[ "nvpair-id-ref", "Support id-ref in nvpair with optional name" ],
[ "not-reschedule-unneeded-monitor",
"Do not reschedule unneeded monitors while resource definitions have changed" ],
[ "reload-becomes-restart", "Cancel reload if restart becomes required" ],
[ "restart-with-extra-op-params", "Restart if with extra operation parameters upon changes of any" ],
],
[
[ "target-0", "Target Role : baseline" ],
[ "target-1", "Target Role : promoted" ],
[ "target-2", "Target Role : invalid" ],
],
[
[ "base-score", "Set a node's default score for all nodes" ],
],
[
[ "date-1", "Dates", [ "-t", "2005-020" ] ],
[ "date-2", "Date Spec - Pass", [ "-t", "2005-020T12:30" ] ],
[ "date-3", "Date Spec - Fail", [ "-t", "2005-020T11:30" ] ],
[ "origin", "Timing of recurring operations", [ "-t", "2014-05-07 00:28:00" ] ],
[ "probe-0", "Probe (anon clone)" ],
[ "probe-1", "Pending Probe" ],
[ "probe-2", "Correctly re-probe cloned groups" ],
[ "probe-3", "Probe (pending node)" ],
[ "probe-4", "Probe (pending node + stopped resource)" ],
[ "probe-pending-node", "Probe (pending node + unmanaged resource)" ],
[ "failed-probe-primitive", "Maskable vs. unmaskable probe failures on primitive resources" ],
[ "failed-probe-clone", "Maskable vs. unmaskable probe failures on cloned resources" ],
[ "expired-failed-probe-primitive", "Maskable, expired probe failure on primitive resources" ],
[ "standby", "Standby" ],
[ "comments", "Comments" ],
],
[
[ "one-or-more-0", "Everything starts" ],
[ "one-or-more-1", "Nothing starts because of A" ],
[ "one-or-more-2", "D can start because of C" ],
[ "one-or-more-3", "D cannot start because of B and C" ],
[ "one-or-more-4", "D cannot start because of target-role" ],
[ "one-or-more-5", "Start A and F even though C and D are stopped" ],
[ "one-or-more-6", "Leave A running even though B is stopped" ],
[ "one-or-more-7", "Leave A running even though C is stopped" ],
[ "bug-5140-require-all-false", "Allow basegrp:0 to stop" ],
[ "clone-require-all-1", "clone B starts node 3 and 4" ],
[ "clone-require-all-2", "clone B remains stopped everywhere" ],
[ "clone-require-all-3", "clone B stops everywhere because A stops everywhere" ],
[ "clone-require-all-4", "clone B remains on node 3 and 4 with only one instance of A remaining" ],
[ "clone-require-all-5", "clone B starts on node 1 3 and 4" ],
[ "clone-require-all-6", "clone B remains active after shutting down instances of A" ],
[ "clone-require-all-7",
"clone A and B both start at the same time. all instances of A start before B" ],
[ "clone-require-all-no-interleave-1", "C starts everywhere after A and B" ],
[ "clone-require-all-no-interleave-2",
"C starts on nodes 1, 2, and 4 with only one active instance of B" ],
[ "clone-require-all-no-interleave-3",
"C remains active when instance of B is stopped on one node and started on another" ],
[ "one-or-more-unrunnable-instances", "Avoid dependencies on instances that won't ever be started" ],
],
[
[ "location-date-rules-1", "Use location constraints with ineffective date-based rules" ],
[ "location-date-rules-2", "Use location constraints with effective date-based rules" ],
[ "nvpair-date-rules-1", "Use nvpair blocks with a variety of date-based rules" ],
[ "value-source", "Use location constraints with node attribute expressions using value-source" ],
[ "rule-dbl-as-auto-number-match",
"Floating-point rule values default to number comparison: match" ],
[ "rule-dbl-as-auto-number-no-match",
"Floating-point rule values default to number comparison: no "
"match" ],
[ "rule-dbl-as-integer-match",
"Floating-point rule values set to integer comparison: match" ],
[ "rule-dbl-as-integer-no-match",
"Floating-point rule values set to integer comparison: no match" ],
[ "rule-dbl-as-number-match",
"Floating-point rule values set to number comparison: match" ],
[ "rule-dbl-as-number-no-match",
"Floating-point rule values set to number comparison: no match" ],
[ "rule-dbl-parse-fail-default-str-match",
"Floating-point rule values fail to parse, default to string "
"comparison: match" ],
[ "rule-dbl-parse-fail-default-str-no-match",
"Floating-point rule values fail to parse, default to string "
"comparison: no match" ],
[ "rule-int-as-auto-integer-match",
"Integer rule values default to integer comparison: match" ],
[ "rule-int-as-auto-integer-no-match",
"Integer rule values default to integer comparison: no match" ],
[ "rule-int-as-integer-match",
"Integer rule values set to integer comparison: match" ],
[ "rule-int-as-integer-no-match",
"Integer rule values set to integer comparison: no match" ],
[ "rule-int-as-number-match",
"Integer rule values set to number comparison: match" ],
[ "rule-int-as-number-no-match",
"Integer rule values set to number comparison: no match" ],
[ "rule-int-parse-fail-default-str-match",
"Integer rule values fail to parse, default to string "
"comparison: match" ],
[ "rule-int-parse-fail-default-str-no-match",
"Integer rule values fail to parse, default to string "
"comparison: no match" ],
],
[
[ "order1", "Order start 1" ],
[ "order2", "Order start 2" ],
[ "order3", "Order stop" ],
[ "order4", "Order (multiple)" ],
[ "order5", "Order (move)" ],
[ "order6", "Order (move w/ restart)" ],
[ "order7", "Order (mandatory)" ],
[ "order-optional", "Order (score=0)" ],
[ "order-required", "Order (score=INFINITY)" ],
[ "bug-lf-2171", "Prevent group start when clone is stopped" ],
[ "order-clone", "Clone ordering should be able to prevent startup of dependent clones" ],
[ "order-sets", "Ordering for resource sets" ],
[ "order-serialize", "Serialize resources without inhibiting migration" ],
[ "order-serialize-set", "Serialize a set of resources without inhibiting migration" ],
[ "clone-order-primitive", "Order clone start after a primitive" ],
[ "clone-order-16instances", "Verify ordering of 16 cloned resources" ],
[ "order-optional-keyword", "Order (optional keyword)" ],
[ "order-mandatory", "Order (mandatory keyword)" ],
[ "bug-lf-2493",
"Don't imply colocation requirements when applying ordering constraints with clones" ],
[ "ordered-set-basic-startup", "Constraint set with default order settings" ],
[ "ordered-set-natural", "Allow natural set ordering" ],
[ "order-wrong-kind", "Order (error)" ],
],
[
[ "coloc-loop", "Colocation - loop" ],
[ "coloc-many-one", "Colocation - many-to-one" ],
[ "coloc-list", "Colocation - many-to-one with list" ],
[ "coloc-group", "Colocation - groups" ],
[ "coloc-unpromoted-anti", "Anti-colocation with unpromoted shouldn't prevent promoted colocation" ],
[ "coloc-attr", "Colocation based on node attributes" ],
[ "coloc-negative-group", "Negative colocation with a group" ],
[ "coloc-intra-set", "Intra-set colocation" ],
[ "bug-lf-2435", "Colocation sets with a negative score" ],
[ "coloc-clone-stays-active",
"Ensure clones don't get stopped/demoted because a dependent must stop" ],
[ "coloc_fp_logic", "Verify floating point calculations in colocation are working" ],
[ "colo_promoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating promoted with primitive" ],
[ "colo_unpromoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating unpromoted with primitive" ],
[ "anti-colocation-order",
"cl#5187 - Prevent resources in an anti-colocation from even temporarily running on a same node" ],
[ "anti-colocation-promoted", "Organize order of actions for promoted resources in anti-colocations" ],
[ "anti-colocation-unpromoted", "Organize order of actions for unpromoted resources in anti-colocations" ],
[ "group-anticolocation", "Group with failed last member anti-colocated with another group" ],
[ "group-colocation-failure",
"Group with sole member failed, colocated with another group"
],
[ "enforce-colo1", "Always enforce B with A INFINITY" ],
[ "complex_enforce_colo", "Always enforce B with A INFINITY. (make sure heat-engine stops)" ],
[ "coloc-dependee-should-stay", "Stickiness outweighs group colocation" ],
[ "coloc-dependee-should-move", "Group colocation outweighs stickiness" ],
[ "colocation-influence", "Respect colocation influence" ],
[ "colocation-priority-group", "Apply group colocations in order of primary priority" ],
[ "colocation-vs-stickiness", "Group stickiness outweighs anti-colocation score" ],
+ [ "promoted-with-blocked", "Promoted role colocated with a resource with blocked start" ],
],
[
[ "rsc-sets-seq-true", "Resource Sets - sequential=false" ],
[ "rsc-sets-seq-false", "Resource Sets - sequential=true" ],
[ "rsc-sets-clone", "Resource Sets - Clone" ],
[ "rsc-sets-promoted", "Resource Sets - Promoted" ],
[ "rsc-sets-clone-1", "Resource Sets - Clone (lf#2404)" ],
],
[
[ "attrs1", "string: eq (and)" ],
[ "attrs2", "string: lt / gt (and)" ],
[ "attrs3", "string: ne (or)" ],
[ "attrs4", "string: exists" ],
[ "attrs5", "string: not_exists" ],
[ "attrs6", "is_dc: true" ],
[ "attrs7", "is_dc: false" ],
[ "attrs8", "score_attribute" ],
[ "per-node-attrs", "Per node resource parameters" ],
],
[
[ "mon-rsc-1", "Schedule Monitor - start" ],
[ "mon-rsc-2", "Schedule Monitor - move" ],
[ "mon-rsc-3", "Schedule Monitor - pending start" ],
[ "mon-rsc-4", "Schedule Monitor - move/pending start" ],
],
[
[ "rec-rsc-0", "Resource Recover - no start" ],
[ "rec-rsc-1", "Resource Recover - start" ],
[ "rec-rsc-2", "Resource Recover - monitor" ],
[ "rec-rsc-3", "Resource Recover - stop - ignore" ],
[ "rec-rsc-4", "Resource Recover - stop - block" ],
[ "rec-rsc-5", "Resource Recover - stop - fence" ],
[ "rec-rsc-6", "Resource Recover - multiple - restart" ],
[ "rec-rsc-7", "Resource Recover - multiple - stop" ],
[ "rec-rsc-8", "Resource Recover - multiple - block" ],
[ "rec-rsc-9", "Resource Recover - group/group" ],
[ "stop-unexpected", "Recover multiply active group with stop_unexpected" ],
[ "stop-unexpected-2", "Resource multiply active primitve with stop_unexpected" ],
[ "monitor-recovery", "on-fail=block + resource recovery detected by recurring monitor" ],
[ "stop-failure-no-quorum", "Stop failure without quorum" ],
[ "stop-failure-no-fencing", "Stop failure without fencing available" ],
[ "stop-failure-with-fencing", "Stop failure with fencing available" ],
[ "multiple-active-block-group", "Support of multiple-active=block for resource groups" ],
[ "multiple-monitor-one-failed",
"Consider resource failed if any of the configured monitor operations failed" ],
],
[
[ "quorum-1", "No quorum - ignore" ],
[ "quorum-2", "No quorum - freeze" ],
[ "quorum-3", "No quorum - stop" ],
[ "quorum-4", "No quorum - start anyway" ],
[ "quorum-5", "No quorum - start anyway (group)" ],
[ "quorum-6", "No quorum - start anyway (clone)" ],
[ "bug-cl-5212", "No promotion with no-quorum-policy=freeze" ],
[ "suicide-needed-inquorate", "no-quorum-policy=suicide: suicide necessary" ],
[ "suicide-not-needed-initial-quorum",
"no-quorum-policy=suicide: suicide not necessary at initial quorum" ],
[ "suicide-not-needed-never-quorate",
"no-quorum-policy=suicide: suicide not necessary if never quorate" ],
[ "suicide-not-needed-quorate", "no-quorum-policy=suicide: suicide necessary if quorate" ],
],
[
[ "rec-node-1", "Node Recover - Startup - no fence" ],
[ "rec-node-2", "Node Recover - Startup - fence" ],
[ "rec-node-3", "Node Recover - HA down - no fence" ],
[ "rec-node-4", "Node Recover - HA down - fence" ],
[ "rec-node-5", "Node Recover - CRM down - no fence" ],
[ "rec-node-6", "Node Recover - CRM down - fence" ],
[ "rec-node-7", "Node Recover - no quorum - ignore" ],
[ "rec-node-8", "Node Recover - no quorum - freeze" ],
[ "rec-node-9", "Node Recover - no quorum - stop" ],
[ "rec-node-10", "Node Recover - no quorum - stop w/fence" ],
[ "rec-node-11", "Node Recover - CRM down w/ group - fence" ],
[ "rec-node-12", "Node Recover - nothing active - fence" ],
[ "rec-node-13", "Node Recover - failed resource + shutdown - fence" ],
[ "rec-node-15", "Node Recover - unknown lrm section" ],
[ "rec-node-14", "Serialize all stonith's" ],
],
[
[ "multi1", "Multiple Active (stop/start)" ],
],
[
[ "migrate-begin", "Normal migration" ],
[ "migrate-success", "Completed migration" ],
[ "migrate-partial-1", "Completed migration, missing stop on source" ],
[ "migrate-partial-2", "Successful migrate_to only" ],
[ "migrate-partial-3", "Successful migrate_to only, target down" ],
[ "migrate-partial-4", "Migrate from the correct host after migrate_to+migrate_from" ],
[ "bug-5186-partial-migrate", "Handle partial migration when src node loses membership" ],
[ "migrate-fail-2", "Failed migrate_from" ],
[ "migrate-fail-3", "Failed migrate_from + stop on source" ],
[ "migrate-fail-4",
"Failed migrate_from + stop on target - ideally we wouldn't need to re-stop on target" ],
[ "migrate-fail-5", "Failed migrate_from + stop on source and target" ],
[ "migrate-fail-6", "Failed migrate_to" ],
[ "migrate-fail-7", "Failed migrate_to + stop on source" ],
[ "migrate-fail-8",
"Failed migrate_to + stop on target - ideally we wouldn't need to re-stop on target" ],
[ "migrate-fail-9", "Failed migrate_to + stop on source and target" ],
[ "migration-ping-pong", "Old migrate_to failure + successful migrate_from on same node" ],
[ "migrate-stop", "Migration in a stopping stack" ],
[ "migrate-start", "Migration in a starting stack" ],
[ "migrate-stop_start", "Migration in a restarting stack" ],
[ "migrate-stop-complex", "Migration in a complex stopping stack" ],
[ "migrate-start-complex", "Migration in a complex starting stack" ],
[ "migrate-stop-start-complex", "Migration in a complex moving stack" ],
[ "migrate-shutdown", "Order the post-migration 'stop' before node shutdown" ],
[ "migrate-1", "Migrate (migrate)" ],
[ "migrate-2", "Migrate (stable)" ],
[ "migrate-3", "Migrate (failed migrate_to)" ],
[ "migrate-4", "Migrate (failed migrate_from)" ],
[ "novell-252693", "Migration in a stopping stack" ],
[ "novell-252693-2", "Migration in a starting stack" ],
[ "novell-252693-3", "Non-Migration in a starting and stopping stack" ],
[ "bug-1820", "Migration in a group" ],
[ "bug-1820-1", "Non-migration in a group" ],
[ "migrate-5", "Primitive migration with a clone" ],
[ "migrate-fencing", "Migration after Fencing" ],
[ "migrate-both-vms", "Migrate two VMs that have no colocation" ],
[ "migration-behind-migrating-remote", "Migrate resource behind migrating remote connection" ],
[ "1-a-then-bm-move-b", "Advanced migrate logic. A then B. migrate B" ],
[ "2-am-then-b-move-a", "Advanced migrate logic, A then B, migrate A without stopping B" ],
[ "3-am-then-bm-both-migrate", "Advanced migrate logic. A then B. migrate both" ],
[ "4-am-then-bm-b-not-migratable", "Advanced migrate logic, A then B, B not migratable" ],
[ "5-am-then-bm-a-not-migratable", "Advanced migrate logic. A then B. move both, a not migratable" ],
[ "6-migrate-group", "Advanced migrate logic, migrate a group" ],
[ "7-migrate-group-one-unmigratable",
"Advanced migrate logic, migrate group mixed with allow-migrate true/false" ],
[ "8-am-then-bm-a-migrating-b-stopping",
"Advanced migrate logic, A then B, A migrating, B stopping" ],
[ "9-am-then-bm-b-migrating-a-stopping",
"Advanced migrate logic, A then B, B migrate, A stopping" ],
[ "10-a-then-bm-b-move-a-clone",
"Advanced migrate logic, A clone then B, migrate B while stopping A" ],
[ "11-a-then-bm-b-move-a-clone-starting",
"Advanced migrate logic, A clone then B, B moving while A is start/stopping" ],
[ "a-promote-then-b-migrate", "A promote then B start. migrate B" ],
[ "a-demote-then-b-migrate", "A demote then B stop. migrate B" ],
[ "probe-target-of-failed-migrate_to-1", "Failed migrate_to, target rejoins" ],
[ "probe-target-of-failed-migrate_to-2", "Failed migrate_to, target rejoined and probed" ],
[ "partial-live-migration-multiple-active", "Prevent running on multiple nodes due to partial live migration" ],
[ "bug-lf-2422", "Dependency on partially active group - stop ocfs:*" ],
],
[
[ "clone-anon-probe-1", "Probe the correct (anonymous) clone instance for each node" ],
[ "clone-anon-probe-2", "Avoid needless re-probing of anonymous clones" ],
[ "clone-anon-failcount", "Merge failcounts for anonymous clones" ],
[ "force-anon-clone-max", "Update clone-max properly when forcing a clone to be anonymous" ],
[ "anon-instance-pending", "Assign anonymous clone instance numbers properly when action pending" ],
[ "inc0", "Incarnation start" ],
[ "inc1", "Incarnation start order" ],
[ "inc2", "Incarnation silent restart, stop, move" ],
[ "inc3", "Inter-incarnation ordering, silent restart, stop, move" ],
[ "inc4", "Inter-incarnation ordering, silent restart, stop, move (ordered)" ],
[ "inc5", "Inter-incarnation ordering, silent restart, stop, move (restart 1)" ],
[ "inc6", "Inter-incarnation ordering, silent restart, stop, move (restart 2)" ],
[ "inc7", "Clone colocation" ],
[ "inc8", "Clone anti-colocation" ],
[ "inc9", "Non-unique clone" ],
[ "inc10", "Non-unique clone (stop)" ],
[ "inc11", "Primitive colocation with clones" ],
[ "inc12", "Clone shutdown" ],
[ "cloned-group", "Make sure only the correct number of cloned groups are started" ],
[ "cloned-group-stop", "Ensure stopping qpidd also stops glance and cinder" ],
[ "clone-no-shuffle", "Don't prioritize allocation of instances that must be moved" ],
[ "clone-max-zero", "Orphan processing with clone-max=0" ],
[ "clone-anon-dup",
"Bug LF#2087 - Correctly parse the state of anonymous clones that are active more than once per node" ],
[ "bug-lf-2160", "Don't shuffle clones due to colocation" ],
[ "bug-lf-2213", "clone-node-max enforcement for cloned groups" ],
[ "bug-lf-2153", "Clone ordering constraints" ],
[ "bug-lf-2361", "Ensure clones observe mandatory ordering constraints if the LHS is unrunnable" ],
[ "bug-lf-2317", "Avoid needless restart of primitive depending on a clone" ],
[ "bug-lf-2453", "Enforce mandatory clone ordering without colocation" ],
[ "bug-lf-2508", "Correctly reconstruct the status of anonymous cloned groups" ],
[ "bug-lf-2544", "Balanced clone placement" ],
[ "bug-lf-2445", "Redistribute clones with node-max > 1 and stickiness = 0" ],
[ "bug-lf-2574", "Avoid clone shuffle" ],
[ "bug-lf-2581", "Avoid group restart due to unrelated clone (re)start" ],
[ "bug-cl-5168", "Don't shuffle clones" ],
[ "bug-cl-5170", "Prevent clone from starting with on-fail=block" ],
[ "clone-fail-block-colocation", "Move colocated group when failed clone has on-fail=block" ],
[ "clone-interleave-1",
"Clone-3 cannot start on pcmk-1 due to interleaved ordering (no colocation)" ],
[ "clone-interleave-2", "Clone-3 must stop on pcmk-1 due to interleaved ordering (no colocation)" ],
[ "clone-interleave-3",
"Clone-3 must be recovered on pcmk-1 due to interleaved ordering (no colocation)" ],
[ "rebalance-unique-clones", "Rebalance unique clone instances with no stickiness" ],
[ "clone-requires-quorum-recovery", "Clone with requires=quorum on failed node needing recovery" ],
[ "clone-requires-quorum",
"Clone with requires=quorum with presumed-inactive instance on failed node" ],
],
[
[ "cloned_start_one", "order first clone then clone... first clone_min=2" ],
[ "cloned_start_two", "order first clone then clone... first clone_min=2" ],
[ "cloned_stop_one", "order first clone then clone... first clone_min=2" ],
[ "cloned_stop_two", "order first clone then clone... first clone_min=2" ],
[ "clone_min_interleave_start_one",
"order first clone then clone... first clone_min=2 and then has interleave=true" ],
[ "clone_min_interleave_start_two",
"order first clone then clone... first clone_min=2 and then has interleave=true" ],
[ "clone_min_interleave_stop_one",
"order first clone then clone... first clone_min=2 and then has interleave=true" ],
[ "clone_min_interleave_stop_two",
"order first clone then clone... first clone_min=2 and then has interleave=true" ],
[ "clone_min_start_one", "order first clone then primitive... first clone_min=2" ],
[ "clone_min_start_two", "order first clone then primitive... first clone_min=2" ],
[ "clone_min_stop_all", "order first clone then primitive... first clone_min=2" ],
[ "clone_min_stop_one", "order first clone then primitive... first clone_min=2" ],
[ "clone_min_stop_two", "order first clone then primitive... first clone_min=2" ],
],
[
[ "unfence-startup", "Clean unfencing" ],
[ "unfence-definition", "Unfencing when the agent changes" ],
[ "unfence-parameters", "Unfencing when the agent parameters changes" ],
[ "unfence-device", "Unfencing when a cluster has only fence devices" ],
],
[
[ "promoted-0", "Stopped -> Unpromoted" ],
[ "promoted-1", "Stopped -> Promote" ],
[ "promoted-2", "Stopped -> Promote : notify" ],
[ "promoted-3", "Stopped -> Promote : promoted location" ],
[ "promoted-4", "Started -> Promote : promoted location" ],
[ "promoted-5", "Promoted -> Promoted" ],
[ "promoted-6", "Promoted -> Promoted (2)" ],
[ "promoted-7", "Promoted -> Fenced" ],
[ "promoted-8", "Promoted -> Fenced -> Moved" ],
[ "promoted-9", "Stopped + Promotable + No quorum" ],
[ "promoted-10", "Stopped -> Promotable : notify with monitor" ],
[ "promoted-11", "Stopped -> Promote : colocation" ],
[ "novell-239082", "Demote/Promote ordering" ],
[ "novell-239087", "Stable promoted placement" ],
[ "promoted-12", "Promotion based solely on rsc_location constraints" ],
[ "promoted-13", "Include preferences of colocated resources when placing promoted" ],
[ "promoted-demote", "Ordering when actions depends on demoting an unpromoted resource" ],
[ "promoted-ordering", "Prevent resources from starting that need a promoted" ],
[ "bug-1765", "Verify promoted-with-promoted colocation does not stop unpromoted instances" ],
[ "promoted-group", "Promotion of cloned groups" ],
[ "bug-lf-1852", "Don't shuffle promotable instances unnecessarily" ],
[ "promoted-failed-demote", "Don't retry failed demote actions" ],
[ "promoted-failed-demote-2", "Don't retry failed demote actions (notify=false)" ],
[ "promoted-depend",
"Ensure resources that depend on promoted instance don't get allocated until that does" ],
[ "promoted-reattach", "Re-attach to a running promoted" ],
[ "promoted-allow-start", "Don't include promoted score if it would prevent allocation" ],
[ "promoted-colocation",
"Allow promoted instances placemaker to be influenced by colocation constraints" ],
[ "promoted-pseudo", "Make sure promote/demote pseudo actions are created correctly" ],
[ "promoted-role", "Prevent target-role from promoting more than promoted-max instances" ],
[ "bug-lf-2358", "Anti-colocation of promoted instances" ],
[ "promoted-promotion-constraint", "Mandatory promoted colocation constraints" ],
[ "unmanaged-promoted", "Ensure role is preserved for unmanaged resources" ],
[ "promoted-unmanaged-monitor", "Start correct monitor for unmanaged promoted instances" ],
[ "promoted-demote-2", "Demote does not clear past failure" ],
[ "promoted-move", "Move promoted based on failure of colocated group" ],
[ "promoted-probed-score", "Observe the promotion score of probed resources" ],
[ "colocation_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by colocation constraint" ],
[ "colocation_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by colocation constraint" ],
[ "order_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by order constraint" ],
[ "order_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by order constraint" ],
[ "promoted_monitor_restart", "cl#5072 - Ensure promoted monitor operation will start after promotion" ],
[ "bug-rh-880249", "Handle replacement of an m/s resource with a primitive" ],
[ "bug-5143-ms-shuffle", "Prevent promoted instance shuffling due to promotion score" ],
[ "promoted-demote-block", "Block promotion if demote fails with on-fail=block" ],
[ "promoted-dependent-ban",
"Don't stop instances from being active because a dependent is banned from that host" ],
[ "promoted-stop", "Stop instances due to location constraint with role=Started" ],
[ "promoted-partially-demoted-group", "Allow partially demoted group to finish demoting" ],
[ "bug-cl-5213", "Ensure role colocation with -INFINITY is enforced" ],
[ "bug-cl-5219", "Allow unrelated resources with a common colocation target to remain promoted" ],
[ "promoted-asymmetrical-order",
"Fix the behaviors of multi-state resources with asymmetrical ordering" ],
[ "promoted-notify", "Promotion with notifications" ],
[ "promoted-score-startup", "Use permanent promoted scores without LRM history" ],
[ "failed-demote-recovery", "Recover resource in unpromoted role after demote fails" ],
[ "failed-demote-recovery-promoted", "Recover resource in promoted role after demote fails" ],
[ "on_fail_demote1", "Recovery with on-fail=\"demote\" on healthy cluster, remote, guest, and bundle nodes" ],
[ "on_fail_demote2", "Recovery with on-fail=\"demote\" with promotion on different node" ],
[ "on_fail_demote3", "Recovery with on-fail=\"demote\" with no promotion" ],
[ "on_fail_demote4", "Recovery with on-fail=\"demote\" on failed cluster, remote, guest, and bundle nodes" ],
[ "no_quorum_demote", "Promotable demotion and primitive stop with no-quorum-policy=\"demote\"" ],
[ "no-promote-on-unrunnable-guest", "Don't select bundle instance for promotion when container can't run" ],
],
[
[ "history-1", "Correctly parse stateful-1 resource state" ],
],
[
[ "managed-0", "Managed (reference)" ],
[ "managed-1", "Not managed - down" ],
[ "managed-2", "Not managed - up" ],
[ "bug-5028", "Shutdown should block if anything depends on an unmanaged resource" ],
[ "bug-5028-detach", "Ensure detach still works" ],
[ "bug-5028-bottom",
"Ensure shutdown still blocks if the blocked resource is at the bottom of the stack" ],
[ "unmanaged-stop-1",
"cl#5155 - Block the stop of resources if any depending resource is unmanaged" ],
[ "unmanaged-stop-2",
"cl#5155 - Block the stop of resources if the first resource in a mandatory stop order is unmanaged" ],
[ "unmanaged-stop-3",
"cl#5155 - Block the stop of resources if any depending resource in a group is unmanaged" ],
[ "unmanaged-stop-4",
"cl#5155 - Block the stop of resources if any depending resource in the middle of a group is unmanaged" ],
[ "unmanaged-block-restart",
"Block restart of resources if any dependent resource in a group is unmanaged" ],
],
[
[ "interleave-0", "Interleave (reference)" ],
[ "interleave-1", "coloc - not interleaved" ],
[ "interleave-2", "coloc - interleaved" ],
[ "interleave-3", "coloc - interleaved (2)" ],
[ "interleave-pseudo-stop", "Interleaved clone during stonith" ],
[ "interleave-stop", "Interleaved clone during stop" ],
[ "interleave-restart", "Interleaved clone during dependency restart" ],
],
[
[ "notify-0", "Notify reference" ],
[ "notify-1", "Notify simple" ],
[ "notify-2", "Notify simple, confirm" ],
[ "notify-3", "Notify move, confirm" ],
[ "novell-239079", "Notification priority" ],
#[ "notify-2", "Notify - 764" ],
[ "notifs-for-unrunnable", "Don't schedule notifications for an unrunnable action" ],
[ "route-remote-notify", "Route remote notify actions through correct cluster node" ],
[ "notify-behind-stopping-remote", "Don't schedule notifications behind stopped remote" ],
],
[
[ "594", "OSDL #594 - Unrunnable actions scheduled in transition" ],
[ "662", "OSDL #662 - Two resources start on one node when incarnation_node_max = 1" ],
[ "696", "OSDL #696 - CRM starts stonith RA without monitor" ],
[ "726", "OSDL #726 - Attempting to schedule rsc_posic041_monitor_5000 _after_ a stop" ],
[ "735", "OSDL #735 - Correctly detect that rsc_hadev1 is stopped on hadev3" ],
[ "764", "OSDL #764 - Missing monitor op for DoFencing:child_DoFencing:1" ],
[ "797", "OSDL #797 - Assert triggered: task_id_i > max_call_id" ],
[ "829", "OSDL #829" ],
[ "994",
"OSDL #994 - Stopping the last resource in a resource group causes the entire group to be restarted" ],
[ "994-2", "OSDL #994 - with a dependent resource" ],
[ "1360", "OSDL #1360 - Clone stickiness" ],
[ "1484", "OSDL #1484 - on_fail=stop" ],
[ "1494", "OSDL #1494 - Clone stability" ],
[ "unrunnable-1", "Unrunnable" ],
[ "unrunnable-2", "Unrunnable 2" ],
[ "stonith-0", "Stonith loop - 1" ],
[ "stonith-1", "Stonith loop - 2" ],
[ "stonith-2", "Stonith loop - 3" ],
[ "stonith-3", "Stonith startup" ],
[ "stonith-4", "Stonith node state" ],
[ "dc-fence-ordering", "DC needs fencing while other nodes are shutting down" ],
[ "bug-1572-1", "Recovery of groups depending on promotable role" ],
[ "bug-1572-2", "Recovery of groups depending on promotable role when promoted is not re-promoted" ],
[ "bug-1685", "Depends-on-promoted ordering" ],
[ "bug-1822", "Don't promote partially active groups" ],
[ "bug-pm-11", "New resource added to a m/s group" ],
[ "bug-pm-12", "Recover only the failed portion of a cloned group" ],
[ "bug-n-387749", "Don't shuffle clone instances" ],
[ "bug-n-385265",
"Don't ignore the failure stickiness of group children - resource_idvscommon should stay stopped" ],
[ "bug-n-385265-2",
"Ensure groups are migrated instead of remaining partially active on the current node" ],
[ "bug-lf-1920", "Correctly handle probes that find active resources" ],
[ "bnc-515172", "Location constraint with multiple expressions" ],
[ "colocate-primitive-with-clone", "Optional colocation with a clone" ],
[ "use-after-free-merge", "Use-after-free in native_merge_weights" ],
[ "bug-lf-2551", "STONITH ordering for stop" ],
[ "bug-lf-2606", "Stonith implies demote" ],
[ "bug-lf-2474", "Ensure resource op timeout takes precedence over op_defaults" ],
[ "bug-suse-707150", "Prevent vm-01 from starting due to colocation/ordering" ],
[ "bug-5014-A-start-B-start", "Verify when A starts B starts using symmetrical=false" ],
[ "bug-5014-A-stop-B-started",
"Verify when A stops B does not stop if it has already started using symmetric=false" ],
[ "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" ],
[ "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" ],
[ "bug-5014-CLONE-A-start-B-start",
"Verify when A starts B starts using clone resources with symmetric=false" ],
[ "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" ],
[ "bug-5014-GROUP-A-start-B-start",
"Verify when A starts B starts when using group resources with symmetric=false" ],
[ "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" ],
[ "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" ],
[ "bug-5014-ordered-set-symmetrical-false",
"Verify ordered sets work with symmetrical=false" ],
[ "bug-5014-ordered-set-symmetrical-true",
"Verify ordered sets work with symmetrical=true" ],
[ "clbz5007-promotable-colocation",
"Verify use of colocation scores other than INFINITY and -INFINITY work on multi-state resources" ],
[ "bug-5038", "Prevent restart of anonymous clones when clone-max decreases" ],
[ "bug-5025-1", "Automatically clean up failcount after resource config change with reload" ],
[ "bug-5025-2", "Make sure clear failcount action isn't set when config does not change" ],
[ "bug-5025-3", "Automatically clean up failcount after resource config change with restart" ],
[ "bug-5025-4", "Clear failcount when last failure is a start op and rsc attributes changed" ],
[ "failcount", "Ensure failcounts are correctly expired" ],
[ "failcount-block", "Ensure failcounts are not expired when on-fail=block is present" ],
[ "per-op-failcount", "Ensure per-operation failcount is handled and not passed to fence agent" ],
[ "on-fail-ignore", "Ensure on-fail=ignore works even beyond migration-threshold" ],
[ "monitor-onfail-restart", "bug-5058 - Monitor failure with on-fail set to restart" ],
[ "monitor-onfail-stop", "bug-5058 - Monitor failure wiht on-fail set to stop" ],
[ "bug-5059", "No need to restart p_stateful1:*" ],
[ "bug-5069-op-enabled", "Test on-fail=ignore with failure when monitor is enabled" ],
[ "bug-5069-op-disabled", "Test on-fail-ignore with failure when monitor is disabled" ],
[ "obsolete-lrm-resource", "cl#5115 - Do not use obsolete lrm_resource sections" ],
[ "expire-non-blocked-failure",
"Ignore failure-timeout only if the failed operation has on-fail=block" ],
[ "asymmetrical-order-move", "Respect asymmetrical ordering when trying to move resources" ],
[ "asymmetrical-order-restart", "Respect asymmetrical ordering when restarting dependent resource" ],
[ "start-then-stop-with-unfence", "Avoid graph loop with start-then-stop constraint plus unfencing" ],
[ "order-expired-failure", "Order failcount cleanup after remote fencing" ],
[ "ignore_stonith_rsc_order1",
"cl#5056- Ignore order constraint between stonith and non-stonith rsc" ],
[ "ignore_stonith_rsc_order2",
"cl#5056- Ignore order constraint with group rsc containing mixed stonith and non-stonith" ],
[ "ignore_stonith_rsc_order3", "cl#5056- Ignore order constraint, stonith clone and mixed group" ],
[ "ignore_stonith_rsc_order4",
"cl#5056- Ignore order constraint, stonith clone and clone with nested mixed group" ],
[ "honor_stonith_rsc_order1",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(single rsc)" ],
[ "honor_stonith_rsc_order2",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(multiple rsc)" ],
[ "honor_stonith_rsc_order3",
"cl#5056- Honor order constraint, stonith clones with nested pure stonith group" ],
[ "honor_stonith_rsc_order4",
"cl#5056- Honor order constraint, between two native stonith rscs" ],
[ "multiply-active-stonith", "Multiply active stonith" ],
[ "probe-timeout", "cl#5099 - Default probe timeout" ],
[ "order-first-probes",
"cl#5301 - respect order constraints when relevant resources are being probed" ],
[ "concurrent-fencing", "Allow performing fencing operations in parallel" ],
[ "priority-fencing-delay", "Delay fencing targeting the more significant node" ],
],
[
[ "systemhealth1", "System Health () #1" ],
[ "systemhealth2", "System Health () #2" ],
[ "systemhealth3", "System Health () #3" ],
[ "systemhealthn1", "System Health (None) #1" ],
[ "systemhealthn2", "System Health (None) #2" ],
[ "systemhealthn3", "System Health (None) #3" ],
[ "systemhealthm1", "System Health (Migrate On Red) #1" ],
[ "systemhealthm2", "System Health (Migrate On Red) #2" ],
[ "systemhealthm3", "System Health (Migrate On Red) #3" ],
[ "systemhealtho1", "System Health (Only Green) #1" ],
[ "systemhealtho2", "System Health (Only Green) #2" ],
[ "systemhealtho3", "System Health (Only Green) #3" ],
[ "systemhealthp1", "System Health (Progessive) #1" ],
[ "systemhealthp2", "System Health (Progessive) #2" ],
[ "systemhealthp3", "System Health (Progessive) #3" ],
[ "allow-unhealthy-nodes", "System Health (migrate-on-red + allow-unhealth-nodes)" ],
],
[
[ "utilization", "Placement Strategy - utilization" ],
[ "minimal", "Placement Strategy - minimal" ],
[ "balanced", "Placement Strategy - balanced" ],
],
[
[ "placement-stickiness", "Optimized Placement Strategy - stickiness" ],
[ "placement-priority", "Optimized Placement Strategy - priority" ],
[ "placement-location", "Optimized Placement Strategy - location" ],
[ "placement-capacity", "Optimized Placement Strategy - capacity" ],
],
[
[ "utilization-order1", "Utilization Order - Simple" ],
[ "utilization-order2", "Utilization Order - Complex" ],
[ "utilization-order3", "Utilization Order - Migrate" ],
[ "utilization-order4", "Utilization Order - Live Migration (bnc#695440)" ],
[ "utilization-complex", "Utilization with complex relationships" ],
[ "utilization-shuffle",
"Don't displace prmExPostgreSQLDB2 on act2, Start prmExPostgreSQLDB1 on act3" ],
[ "load-stopped-loop", "Avoid transition loop due to load_stopped (cl#5044)" ],
[ "load-stopped-loop-2",
"cl#5235 - Prevent graph loops that can be introduced by load_stopped -> migrate_to ordering" ],
],
[
[ "colocated-utilization-primitive-1", "Colocated Utilization - Primitive" ],
[ "colocated-utilization-primitive-2", "Colocated Utilization - Choose the most capable node" ],
[ "colocated-utilization-group", "Colocated Utilization - Group" ],
[ "colocated-utilization-clone", "Colocated Utilization - Clone" ],
[ "utilization-check-allowed-nodes",
"Only check the capacities of the nodes that can run the resource" ],
],
[
[ "reprobe-target_rc", "Ensure correct target_rc for reprobe of inactive resources" ],
[ "node-maintenance-1", "cl#5128 - Node maintenance" ],
[ "node-maintenance-2", "cl#5128 - Node maintenance (coming out of maintenance mode)" ],
[ "shutdown-maintenance-node", "Do not fence a maintenance node if it shuts down cleanly" ],
[ "rsc-maintenance", "Per-resource maintenance" ],
],
[
[ "not-installed-agent", "The resource agent is missing" ],
[ "not-installed-tools", "Something the resource agent needs is missing" ],
],
[
[ "stopped-monitor-00", "Stopped Monitor - initial start" ],
[ "stopped-monitor-01", "Stopped Monitor - failed started" ],
[ "stopped-monitor-02", "Stopped Monitor - started multi-up" ],
[ "stopped-monitor-03", "Stopped Monitor - stop started" ],
[ "stopped-monitor-04", "Stopped Monitor - failed stop" ],
[ "stopped-monitor-05", "Stopped Monitor - start unmanaged" ],
[ "stopped-monitor-06", "Stopped Monitor - unmanaged multi-up" ],
[ "stopped-monitor-07", "Stopped Monitor - start unmanaged multi-up" ],
[ "stopped-monitor-08", "Stopped Monitor - migrate" ],
[ "stopped-monitor-09", "Stopped Monitor - unmanage started" ],
[ "stopped-monitor-10", "Stopped Monitor - unmanaged started multi-up" ],
[ "stopped-monitor-11", "Stopped Monitor - stop unmanaged started" ],
[ "stopped-monitor-12", "Stopped Monitor - unmanaged started multi-up (target-role=Stopped)" ],
[ "stopped-monitor-20", "Stopped Monitor - initial stop" ],
[ "stopped-monitor-21", "Stopped Monitor - stopped single-up" ],
[ "stopped-monitor-22", "Stopped Monitor - stopped multi-up" ],
[ "stopped-monitor-23", "Stopped Monitor - start stopped" ],
[ "stopped-monitor-24", "Stopped Monitor - unmanage stopped" ],
[ "stopped-monitor-25", "Stopped Monitor - unmanaged stopped multi-up" ],
[ "stopped-monitor-26", "Stopped Monitor - start unmanaged stopped" ],
[ "stopped-monitor-27", "Stopped Monitor - unmanaged stopped multi-up (target-role=Started)" ],
[ "stopped-monitor-30", "Stopped Monitor - new node started" ],
[ "stopped-monitor-31", "Stopped Monitor - new node stopped" ],
],
[
# 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
[ "intervals", "Recurring monitor interval handling" ],
],
[
[ "ticket-primitive-1", "Ticket - Primitive (loss-policy=stop, initial)" ],
[ "ticket-primitive-2", "Ticket - Primitive (loss-policy=stop, granted)" ],
[ "ticket-primitive-3", "Ticket - Primitive (loss-policy-stop, revoked)" ],
[ "ticket-primitive-4", "Ticket - Primitive (loss-policy=demote, initial)" ],
[ "ticket-primitive-5", "Ticket - Primitive (loss-policy=demote, granted)" ],
[ "ticket-primitive-6", "Ticket - Primitive (loss-policy=demote, revoked)" ],
[ "ticket-primitive-7", "Ticket - Primitive (loss-policy=fence, initial)" ],
[ "ticket-primitive-8", "Ticket - Primitive (loss-policy=fence, granted)" ],
[ "ticket-primitive-9", "Ticket - Primitive (loss-policy=fence, revoked)" ],
[ "ticket-primitive-10", "Ticket - Primitive (loss-policy=freeze, initial)" ],
[ "ticket-primitive-11", "Ticket - Primitive (loss-policy=freeze, granted)" ],
[ "ticket-primitive-12", "Ticket - Primitive (loss-policy=freeze, revoked)" ],
[ "ticket-primitive-13", "Ticket - Primitive (loss-policy=stop, standby, granted)" ],
[ "ticket-primitive-14", "Ticket - Primitive (loss-policy=stop, granted, standby)" ],
[ "ticket-primitive-15", "Ticket - Primitive (loss-policy=stop, standby, revoked)" ],
[ "ticket-primitive-16", "Ticket - Primitive (loss-policy=demote, standby, granted)" ],
[ "ticket-primitive-17", "Ticket - Primitive (loss-policy=demote, granted, standby)" ],
[ "ticket-primitive-18", "Ticket - Primitive (loss-policy=demote, standby, revoked)" ],
[ "ticket-primitive-19", "Ticket - Primitive (loss-policy=fence, standby, granted)" ],
[ "ticket-primitive-20", "Ticket - Primitive (loss-policy=fence, granted, standby)" ],
[ "ticket-primitive-21", "Ticket - Primitive (loss-policy=fence, standby, revoked)" ],
[ "ticket-primitive-22", "Ticket - Primitive (loss-policy=freeze, standby, granted)" ],
[ "ticket-primitive-23", "Ticket - Primitive (loss-policy=freeze, granted, standby)" ],
[ "ticket-primitive-24", "Ticket - Primitive (loss-policy=freeze, standby, revoked)" ],
],
[
[ "ticket-group-1", "Ticket - Group (loss-policy=stop, initial)" ],
[ "ticket-group-2", "Ticket - Group (loss-policy=stop, granted)" ],
[ "ticket-group-3", "Ticket - Group (loss-policy-stop, revoked)" ],
[ "ticket-group-4", "Ticket - Group (loss-policy=demote, initial)" ],
[ "ticket-group-5", "Ticket - Group (loss-policy=demote, granted)" ],
[ "ticket-group-6", "Ticket - Group (loss-policy=demote, revoked)" ],
[ "ticket-group-7", "Ticket - Group (loss-policy=fence, initial)" ],
[ "ticket-group-8", "Ticket - Group (loss-policy=fence, granted)" ],
[ "ticket-group-9", "Ticket - Group (loss-policy=fence, revoked)" ],
[ "ticket-group-10", "Ticket - Group (loss-policy=freeze, initial)" ],
[ "ticket-group-11", "Ticket - Group (loss-policy=freeze, granted)" ],
[ "ticket-group-12", "Ticket - Group (loss-policy=freeze, revoked)" ],
[ "ticket-group-13", "Ticket - Group (loss-policy=stop, standby, granted)" ],
[ "ticket-group-14", "Ticket - Group (loss-policy=stop, granted, standby)" ],
[ "ticket-group-15", "Ticket - Group (loss-policy=stop, standby, revoked)" ],
[ "ticket-group-16", "Ticket - Group (loss-policy=demote, standby, granted)" ],
[ "ticket-group-17", "Ticket - Group (loss-policy=demote, granted, standby)" ],
[ "ticket-group-18", "Ticket - Group (loss-policy=demote, standby, revoked)" ],
[ "ticket-group-19", "Ticket - Group (loss-policy=fence, standby, granted)" ],
[ "ticket-group-20", "Ticket - Group (loss-policy=fence, granted, standby)" ],
[ "ticket-group-21", "Ticket - Group (loss-policy=fence, standby, revoked)" ],
[ "ticket-group-22", "Ticket - Group (loss-policy=freeze, standby, granted)" ],
[ "ticket-group-23", "Ticket - Group (loss-policy=freeze, granted, standby)" ],
[ "ticket-group-24", "Ticket - Group (loss-policy=freeze, standby, revoked)" ],
],
[
[ "ticket-clone-1", "Ticket - Clone (loss-policy=stop, initial)" ],
[ "ticket-clone-2", "Ticket - Clone (loss-policy=stop, granted)" ],
[ "ticket-clone-3", "Ticket - Clone (loss-policy-stop, revoked)" ],
[ "ticket-clone-4", "Ticket - Clone (loss-policy=demote, initial)" ],
[ "ticket-clone-5", "Ticket - Clone (loss-policy=demote, granted)" ],
[ "ticket-clone-6", "Ticket - Clone (loss-policy=demote, revoked)" ],
[ "ticket-clone-7", "Ticket - Clone (loss-policy=fence, initial)" ],
[ "ticket-clone-8", "Ticket - Clone (loss-policy=fence, granted)" ],
[ "ticket-clone-9", "Ticket - Clone (loss-policy=fence, revoked)" ],
[ "ticket-clone-10", "Ticket - Clone (loss-policy=freeze, initial)" ],
[ "ticket-clone-11", "Ticket - Clone (loss-policy=freeze, granted)" ],
[ "ticket-clone-12", "Ticket - Clone (loss-policy=freeze, revoked)" ],
[ "ticket-clone-13", "Ticket - Clone (loss-policy=stop, standby, granted)" ],
[ "ticket-clone-14", "Ticket - Clone (loss-policy=stop, granted, standby)" ],
[ "ticket-clone-15", "Ticket - Clone (loss-policy=stop, standby, revoked)" ],
[ "ticket-clone-16", "Ticket - Clone (loss-policy=demote, standby, granted)" ],
[ "ticket-clone-17", "Ticket - Clone (loss-policy=demote, granted, standby)" ],
[ "ticket-clone-18", "Ticket - Clone (loss-policy=demote, standby, revoked)" ],
[ "ticket-clone-19", "Ticket - Clone (loss-policy=fence, standby, granted)" ],
[ "ticket-clone-20", "Ticket - Clone (loss-policy=fence, granted, standby)" ],
[ "ticket-clone-21", "Ticket - Clone (loss-policy=fence, standby, revoked)" ],
[ "ticket-clone-22", "Ticket - Clone (loss-policy=freeze, standby, granted)" ],
[ "ticket-clone-23", "Ticket - Clone (loss-policy=freeze, granted, standby)" ],
[ "ticket-clone-24", "Ticket - Clone (loss-policy=freeze, standby, revoked)" ],
],
[
[ "ticket-promoted-1", "Ticket - Promoted (loss-policy=stop, initial)" ],
[ "ticket-promoted-2", "Ticket - Promoted (loss-policy=stop, granted)" ],
[ "ticket-promoted-3", "Ticket - Promoted (loss-policy-stop, revoked)" ],
[ "ticket-promoted-4", "Ticket - Promoted (loss-policy=demote, initial)" ],
[ "ticket-promoted-5", "Ticket - Promoted (loss-policy=demote, granted)" ],
[ "ticket-promoted-6", "Ticket - Promoted (loss-policy=demote, revoked)" ],
[ "ticket-promoted-7", "Ticket - Promoted (loss-policy=fence, initial)" ],
[ "ticket-promoted-8", "Ticket - Promoted (loss-policy=fence, granted)" ],
[ "ticket-promoted-9", "Ticket - Promoted (loss-policy=fence, revoked)" ],
[ "ticket-promoted-10", "Ticket - Promoted (loss-policy=freeze, initial)" ],
[ "ticket-promoted-11", "Ticket - Promoted (loss-policy=freeze, granted)" ],
[ "ticket-promoted-12", "Ticket - Promoted (loss-policy=freeze, revoked)" ],
[ "ticket-promoted-13", "Ticket - Promoted (loss-policy=stop, standby, granted)" ],
[ "ticket-promoted-14", "Ticket - Promoted (loss-policy=stop, granted, standby)" ],
[ "ticket-promoted-15", "Ticket - Promoted (loss-policy=stop, standby, revoked)" ],
[ "ticket-promoted-16", "Ticket - Promoted (loss-policy=demote, standby, granted)" ],
[ "ticket-promoted-17", "Ticket - Promoted (loss-policy=demote, granted, standby)" ],
[ "ticket-promoted-18", "Ticket - Promoted (loss-policy=demote, standby, revoked)" ],
[ "ticket-promoted-19", "Ticket - Promoted (loss-policy=fence, standby, granted)" ],
[ "ticket-promoted-20", "Ticket - Promoted (loss-policy=fence, granted, standby)" ],
[ "ticket-promoted-21", "Ticket - Promoted (loss-policy=fence, standby, revoked)" ],
[ "ticket-promoted-22", "Ticket - Promoted (loss-policy=freeze, standby, granted)" ],
[ "ticket-promoted-23", "Ticket - Promoted (loss-policy=freeze, granted, standby)" ],
[ "ticket-promoted-24", "Ticket - Promoted (loss-policy=freeze, standby, revoked)" ],
],
[
[ "ticket-rsc-sets-1", "Ticket - Resource sets (1 ticket, initial)" ],
[ "ticket-rsc-sets-2", "Ticket - Resource sets (1 ticket, granted)" ],
[ "ticket-rsc-sets-3", "Ticket - Resource sets (1 ticket, revoked)" ],
[ "ticket-rsc-sets-4", "Ticket - Resource sets (2 tickets, initial)" ],
[ "ticket-rsc-sets-5", "Ticket - Resource sets (2 tickets, granted)" ],
[ "ticket-rsc-sets-6", "Ticket - Resource sets (2 tickets, granted)" ],
[ "ticket-rsc-sets-7", "Ticket - Resource sets (2 tickets, revoked)" ],
[ "ticket-rsc-sets-8", "Ticket - Resource sets (1 ticket, standby, granted)" ],
[ "ticket-rsc-sets-9", "Ticket - Resource sets (1 ticket, granted, standby)" ],
[ "ticket-rsc-sets-10", "Ticket - Resource sets (1 ticket, standby, revoked)" ],
[ "ticket-rsc-sets-11", "Ticket - Resource sets (2 tickets, standby, granted)" ],
[ "ticket-rsc-sets-12", "Ticket - Resource sets (2 tickets, standby, granted)" ],
[ "ticket-rsc-sets-13", "Ticket - Resource sets (2 tickets, granted, standby)" ],
[ "ticket-rsc-sets-14", "Ticket - Resource sets (2 tickets, standby, revoked)" ],
[ "cluster-specific-params", "Cluster-specific instance attributes based on rules" ],
[ "site-specific-params", "Site-specific instance attributes based on rules" ],
],
[
[ "template-1", "Template - 1" ],
[ "template-2", "Template - 2" ],
[ "template-3", "Template - 3 (merge operations)" ],
[ "template-coloc-1", "Template - Colocation 1" ],
[ "template-coloc-2", "Template - Colocation 2" ],
[ "template-coloc-3", "Template - Colocation 3" ],
[ "template-order-1", "Template - Order 1" ],
[ "template-order-2", "Template - Order 2" ],
[ "template-order-3", "Template - Order 3" ],
[ "template-ticket", "Template - Ticket" ],
[ "template-rsc-sets-1", "Template - Resource Sets 1" ],
[ "template-rsc-sets-2", "Template - Resource Sets 2" ],
[ "template-rsc-sets-3", "Template - Resource Sets 3" ],
[ "template-rsc-sets-4", "Template - Resource Sets 4" ],
[ "template-clone-primitive", "Cloned primitive from template" ],
[ "template-clone-group", "Cloned group from template" ],
[ "location-sets-templates", "Resource sets and templates - Location" ],
[ "tags-coloc-order-1", "Tags - Colocation and Order (Simple)" ],
[ "tags-coloc-order-2", "Tags - Colocation and Order (Resource Sets with Templates)" ],
[ "tags-location", "Tags - Location" ],
[ "tags-ticket", "Tags - Ticket" ],
],
[
[ "container-1", "Container - initial" ],
[ "container-2", "Container - monitor failed" ],
[ "container-3", "Container - stop failed" ],
[ "container-4", "Container - reached migration-threshold" ],
[ "container-group-1", "Container in group - initial" ],
[ "container-group-2", "Container in group - monitor failed" ],
[ "container-group-3", "Container in group - stop failed" ],
[ "container-group-4", "Container in group - reached migration-threshold" ],
[ "container-is-remote-node", "Place resource within container when container is remote-node" ],
[ "bug-rh-1097457", "Kill user defined container/contents ordering" ],
[ "bug-cl-5247", "Graph loop when recovering m/s resource in a container" ],
[ "bundle-order-startup", "Bundle startup ordering" ],
[ "bundle-order-partial-start",
"Bundle startup ordering when some dependencies are already running" ],
[ "bundle-order-partial-start-2",
"Bundle startup ordering when some dependencies and the container are already running" ],
[ "bundle-order-stop", "Bundle stop ordering" ],
[ "bundle-order-partial-stop", "Bundle startup ordering when some dependencies are already stopped" ],
[ "bundle-order-stop-on-remote", "Stop nested resource after bringing up the connection" ],
[ "bundle-order-startup-clone", "Prevent startup because bundle isn't promoted" ],
[ "bundle-order-startup-clone-2", "Bundle startup with clones" ],
[ "bundle-order-stop-clone", "Stop bundle because clone is stopping" ],
[ "bundle-nested-colocation", "Colocation of nested connection resources" ],
[ "bundle-order-fencing",
"Order pseudo bundle fencing after parent node fencing if both are happening" ],
[ "bundle-probe-order-1", "order 1" ],
[ "bundle-probe-order-2", "order 2" ],
[ "bundle-probe-order-3", "order 3" ],
[ "bundle-probe-remotes", "Ensure remotes get probed too" ],
[ "bundle-replicas-change", "Change bundle from 1 replica to multiple" ],
[ "bundle-connection-with-container", "Don't move a container due to connection preferences" ],
[ "nested-remote-recovery", "Recover bundle's container hosted on remote node" ],
],
[
[ "whitebox-fail1", "Fail whitebox container rsc" ],
[ "whitebox-fail2", "Fail cluster connection to guest node" ],
[ "whitebox-fail3", "Failed containers should not run nested on remote nodes" ],
[ "whitebox-start", "Start whitebox container with resources assigned to it" ],
[ "whitebox-stop", "Stop whitebox container with resources assigned to it" ],
[ "whitebox-move", "Move whitebox container with resources assigned to it" ],
[ "whitebox-asymmetric", "Verify connection rsc opts-in based on container resource" ],
[ "whitebox-ms-ordering", "Verify promote/demote can not occur before connection is established" ],
[ "whitebox-ms-ordering-move", "Stop/Start cycle within a moving container" ],
[ "whitebox-orphaned", "Properly shutdown orphaned whitebox container" ],
[ "whitebox-orphan-ms", "Properly tear down orphan ms resources on remote-nodes" ],
[ "whitebox-unexpectedly-running", "Recover container nodes the cluster did not start" ],
[ "whitebox-migrate1", "Migrate both container and connection resource" ],
[ "whitebox-imply-stop-on-fence",
"imply stop action on container node rsc when host node is fenced" ],
[ "whitebox-nested-group", "Verify guest remote-node works nested in a group" ],
[ "guest-node-host-dies", "Verify guest node is recovered if host goes away" ],
[ "guest-node-cleanup", "Order guest node connection recovery after container probe" ],
[ "guest-host-not-fenceable", "Actions on guest node are unrunnable if host is unclean and cannot be fenced" ],
],
[
[ "remote-startup-probes", "Baremetal remote-node startup probes" ],
[ "remote-startup", "Startup a newly discovered remote-nodes with no status" ],
[ "remote-fence-unclean", "Fence unclean baremetal remote-node" ],
[ "remote-fence-unclean2",
"Fence baremetal remote-node after cluster node fails and connection can not be recovered" ],
[ "remote-fence-unclean-3", "Probe failed remote nodes (triggers fencing)" ],
[ "remote-move", "Move remote-node connection resource" ],
[ "remote-disable", "Disable a baremetal remote-node" ],
[ "remote-probe-disable", "Probe then stop a baremetal remote-node" ],
[ "remote-orphaned", "Properly shutdown orphaned connection resource" ],
[ "remote-orphaned2",
"verify we can handle orphaned remote connections with active resources on the remote" ],
[ "remote-recover", "Recover connection resource after cluster-node fails" ],
[ "remote-stale-node-entry",
"Make sure we properly handle leftover remote-node entries in the node section" ],
[ "remote-partial-migrate",
"Make sure partial migrations are handled before ops on the remote node" ],
[ "remote-partial-migrate2",
"Make sure partial migration target is prefered for remote connection" ],
[ "remote-recover-fail", "Make sure start failure causes fencing if rsc are active on remote" ],
[ "remote-start-fail",
"Make sure a start failure does not result in fencing if no active resources are on remote" ],
[ "remote-unclean2",
"Make monitor failure always results in fencing, even if no rsc are active on remote" ],
[ "remote-fence-before-reconnect", "Fence before clearing recurring monitor failure" ],
[ "remote-recovery", "Recover remote connections before attempting demotion" ],
[ "remote-recover-connection", "Optimistically recovery of only the connection" ],
[ "remote-recover-all", "Fencing when the connection has no home" ],
[ "remote-recover-no-resources", "Fencing when the connection has no home and no active resources" ],
[ "remote-recover-unknown",
"Fencing when the connection has no home and the remote has no operation history" ],
[ "remote-reconnect-delay", "Waiting for remote reconnect interval to expire" ],
[ "remote-connection-unrecoverable",
"Remote connection host must be fenced, with connection unrecoverable" ],
[ "remote-connection-shutdown", "Remote connection shutdown" ],
[ "cancel-behind-moving-remote",
"Route recurring monitor cancellations through original node of a moving remote connection" ],
],
[
[ "resource-discovery", "Exercises resource-discovery location constraint option" ],
[ "rsc-discovery-per-node", "Disable resource discovery per node" ],
[ "shutdown-lock", "Ensure shutdown lock works properly" ],
[ "shutdown-lock-expiration", "Ensure shutdown lock expiration works properly" ],
],
[
[ "op-defaults", "Test op_defaults conditional expressions" ],
[ "op-defaults-2", "Test op_defaults AND'ed conditional expressions" ],
[ "op-defaults-3", "Test op_defaults precedence" ],
[ "rsc-defaults", "Test rsc_defaults conditional expressions" ],
[ "rsc-defaults-2", "Test rsc_defaults conditional expressions without type" ],
],
[ [ "stop-all-resources", "Test stop-all-resources=true "],
],
[ [ "ocf_degraded-remap-ocf_ok", "Test degraded remapped to OK" ],
[ "ocf_degraded_promoted-remap-ocf_ok", "Test degraded promoted remapped to OK"],
],
]
TESTS_64BIT = [
[
[ "year-2038", "Check handling of timestamps beyond 2038-01-19 03:14:08 UTC" ],
],
]
# Constants substituted in the build process
class BuildVars(object):
SBINDIR = "@sbindir@"
BUILDDIR = "@abs_top_builddir@"
CRM_SCHEMA_DIRECTORY = "@CRM_SCHEMA_DIRECTORY@"
# These values must be kept in sync with crm_exit_t
class CrmExit(object):
OK = 0
ERROR = 1
NOT_INSTALLED = 5
NOINPUT = 66
CANTCREAT = 73
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 = BuildVars.BUILDDIR + "/tools/crm_simulate"
if not is_executable(self.args.binary):
self.args.binary = BuildVars.SBINDIR + "/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(CrmExit.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(BuildVars.BUILDDIR, "xml"),
BuildVars.CRM_SCHEMA_DIRECTORY ]:
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(CrmExit.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(CrmExit.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 %-25s %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 CrmExit.NOINPUT
if not self.args.update and not os.path.isfile(expected_filename):
self._error("no stored output")
return CrmExit.NOINPUT
# Run simulation to generate summary output
if self.args.run: # Single test mode
test_cmd_full = test_cmd + [ '-x', input_filename, '-S' ] + test_args
print(" ".join(test_cmd_full))
else:
# @TODO Why isn't test_args added here?
test_cmd_full = test_cmd + [ '-x', input_filename, '-S' ]
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 != CrmExit.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 CrmExit.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 CrmExit.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 CrmExit.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 CrmExit.ERROR
return CrmExit.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:
try:
args = test[2]
except IndexError:
args = []
self.run_one(test[0], test[1], 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 CrmExit.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 CrmExit.ERROR
def run(self):
""" Run test(s) as specified """
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/promoted-with-blocked.dot b/cts/scheduler/dot/promoted-with-blocked.dot
new file mode 100644
index 0000000000..427b2c0740
--- /dev/null
+++ b/cts/scheduler/dot/promoted-with-blocked.dot
@@ -0,0 +1,63 @@
+ digraph "g" {
+"rsc1_monitor_0 node1" -> "rsc1_start_0 node2" [ style = dashed]
+"rsc1_monitor_0 node1" [ style=bold color="green" fontcolor="black"]
+"rsc1_monitor_0 node2" -> "rsc1_start_0 node2" [ style = dashed]
+"rsc1_monitor_0 node2" [ style=bold color="green" fontcolor="black"]
+"rsc1_monitor_0 node3" -> "rsc1_start_0 node2" [ style = dashed]
+"rsc1_monitor_0 node3" [ style=bold color="green" fontcolor="black"]
+"rsc1_monitor_0 node4" -> "rsc1_start_0 node2" [ style = dashed]
+"rsc1_monitor_0 node4" [ style=bold color="green" fontcolor="black"]
+"rsc1_monitor_0 node5" -> "rsc1_start_0 node2" [ style = dashed]
+"rsc1_monitor_0 node5" [ style=bold color="green" fontcolor="black"]
+"rsc1_monitor_10000 node2" [ style=dashed color="red" fontcolor="black"]
+"rsc1_start_0 node2" -> "rsc1_monitor_10000 node2" [ style = dashed]
+"rsc1_start_0 node2" [ style=dashed color="red" fontcolor="black"]
+"rsc2-clone_promote_0" -> "rsc2:4_promote_0 node2" [ style = dashed]
+"rsc2-clone_promote_0" [ style=dashed color="red" fontcolor="orange"]
+"rsc2-clone_promoted_0" [ style=dashed color="red" fontcolor="orange"]
+"rsc2-clone_running_0" -> "rsc2-clone_promote_0" [ style = dashed]
+"rsc2-clone_running_0" [ style=bold color="green" fontcolor="orange"]
+"rsc2-clone_start_0" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2-clone_start_0" -> "rsc2:0_start_0 node3" [ style = bold]
+"rsc2-clone_start_0" -> "rsc2:1_start_0 node4" [ style = bold]
+"rsc2-clone_start_0" -> "rsc2:2_start_0 node5" [ style = bold]
+"rsc2-clone_start_0" -> "rsc2:3_start_0 node1" [ style = bold]
+"rsc2-clone_start_0" -> "rsc2:4_start_0 node2" [ style = bold]
+"rsc2-clone_start_0" [ style=bold color="green" fontcolor="orange"]
+"rsc2:0_monitor_0 node3" -> "rsc2-clone_start_0" [ style = bold]
+"rsc2:0_monitor_0 node3" [ style=bold color="green" fontcolor="black"]
+"rsc2:0_monitor_10000 node3" [ style=bold color="green" fontcolor="black"]
+"rsc2:0_start_0 node3" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2:0_start_0 node3" -> "rsc2:0_monitor_10000 node3" [ style = bold]
+"rsc2:0_start_0 node3" [ style=bold color="green" fontcolor="black"]
+"rsc2:1_monitor_0 node4" -> "rsc2-clone_start_0" [ style = bold]
+"rsc2:1_monitor_0 node4" [ style=bold color="green" fontcolor="black"]
+"rsc2:1_monitor_10000 node4" [ style=bold color="green" fontcolor="black"]
+"rsc2:1_start_0 node4" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2:1_start_0 node4" -> "rsc2:1_monitor_10000 node4" [ style = bold]
+"rsc2:1_start_0 node4" [ style=bold color="green" fontcolor="black"]
+"rsc2:2_monitor_0 node5" -> "rsc2-clone_start_0" [ style = bold]
+"rsc2:2_monitor_0 node5" [ style=bold color="green" fontcolor="black"]
+"rsc2:2_monitor_10000 node5" [ style=bold color="green" fontcolor="black"]
+"rsc2:2_start_0 node5" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2:2_start_0 node5" -> "rsc2:2_monitor_10000 node5" [ style = bold]
+"rsc2:2_start_0 node5" [ style=bold color="green" fontcolor="black"]
+"rsc2:3_monitor_0 node1" -> "rsc2-clone_start_0" [ style = bold]
+"rsc2:3_monitor_0 node1" [ style=bold color="green" fontcolor="black"]
+"rsc2:3_monitor_10000 node1" [ style=bold color="green" fontcolor="black"]
+"rsc2:3_start_0 node1" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2:3_start_0 node1" -> "rsc2:3_monitor_10000 node1" [ style = bold]
+"rsc2:3_start_0 node1" [ style=bold color="green" fontcolor="black"]
+"rsc2:4_monitor_0 node2" -> "rsc2-clone_start_0" [ style = bold]
+"rsc2:4_monitor_0 node2" [ style=bold color="green" fontcolor="black"]
+"rsc2:4_promote_0 node2" -> "rsc2-clone_promoted_0" [ style = dashed]
+"rsc2:4_promote_0 node2" [ style=dashed color="red" fontcolor="black"]
+"rsc2:4_start_0 node2" -> "rsc2-clone_running_0" [ style = bold]
+"rsc2:4_start_0 node2" -> "rsc2:4_promote_0 node2" [ style = dashed]
+"rsc2:4_start_0 node2" [ style=bold color="green" fontcolor="black"]
+"rsc3_monitor_0 node1" [ style=bold color="green" fontcolor="black"]
+"rsc3_monitor_0 node2" [ style=bold color="green" fontcolor="black"]
+"rsc3_monitor_0 node3" [ style=bold color="green" fontcolor="black"]
+"rsc3_monitor_0 node4" [ style=bold color="green" fontcolor="black"]
+"rsc3_monitor_0 node5" [ style=bold color="green" fontcolor="black"]
+}
diff --git a/cts/scheduler/exp/promoted-with-blocked.exp b/cts/scheduler/exp/promoted-with-blocked.exp
new file mode 100644
index 0000000000..540963bcd7
--- /dev/null
+++ b/cts/scheduler/exp/promoted-with-blocked.exp
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cts/scheduler/scores/promoted-with-blocked.scores b/cts/scheduler/scores/promoted-with-blocked.scores
new file mode 100644
index 0000000000..d279b01a8c
--- /dev/null
+++ b/cts/scheduler/scores/promoted-with-blocked.scores
@@ -0,0 +1,76 @@
+
+pcmk__clone_allocate: rsc2-clone allocation score on node1: 0
+pcmk__clone_allocate: rsc2-clone allocation score on node2: 0
+pcmk__clone_allocate: rsc2-clone allocation score on node3: 0
+pcmk__clone_allocate: rsc2-clone allocation score on node4: 0
+pcmk__clone_allocate: rsc2-clone allocation score on node5: 0
+pcmk__clone_allocate: rsc2:0 allocation score on node1: 0
+pcmk__clone_allocate: rsc2:0 allocation score on node2: 0
+pcmk__clone_allocate: rsc2:0 allocation score on node3: 0
+pcmk__clone_allocate: rsc2:0 allocation score on node4: 0
+pcmk__clone_allocate: rsc2:0 allocation score on node5: 0
+pcmk__clone_allocate: rsc2:1 allocation score on node1: 0
+pcmk__clone_allocate: rsc2:1 allocation score on node2: 0
+pcmk__clone_allocate: rsc2:1 allocation score on node3: 0
+pcmk__clone_allocate: rsc2:1 allocation score on node4: 0
+pcmk__clone_allocate: rsc2:1 allocation score on node5: 0
+pcmk__clone_allocate: rsc2:2 allocation score on node1: 0
+pcmk__clone_allocate: rsc2:2 allocation score on node2: 0
+pcmk__clone_allocate: rsc2:2 allocation score on node3: 0
+pcmk__clone_allocate: rsc2:2 allocation score on node4: 0
+pcmk__clone_allocate: rsc2:2 allocation score on node5: 0
+pcmk__clone_allocate: rsc2:3 allocation score on node1: 0
+pcmk__clone_allocate: rsc2:3 allocation score on node2: 0
+pcmk__clone_allocate: rsc2:3 allocation score on node3: 0
+pcmk__clone_allocate: rsc2:3 allocation score on node4: 0
+pcmk__clone_allocate: rsc2:3 allocation score on node5: 0
+pcmk__clone_allocate: rsc2:4 allocation score on node1: 0
+pcmk__clone_allocate: rsc2:4 allocation score on node2: 0
+pcmk__clone_allocate: rsc2:4 allocation score on node3: 0
+pcmk__clone_allocate: rsc2:4 allocation score on node4: 0
+pcmk__clone_allocate: rsc2:4 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: rsc1 allocation score on node1: 0
+pcmk__primitive_assign: rsc1 allocation score on node2: 0
+pcmk__primitive_assign: rsc1 allocation score on node3: 0
+pcmk__primitive_assign: rsc1 allocation score on node4: 0
+pcmk__primitive_assign: rsc1 allocation score on node5: 0
+pcmk__primitive_assign: rsc2:0 allocation score on node1: 0
+pcmk__primitive_assign: rsc2:0 allocation score on node2: 0
+pcmk__primitive_assign: rsc2:0 allocation score on node3: 0
+pcmk__primitive_assign: rsc2:0 allocation score on node4: 0
+pcmk__primitive_assign: rsc2:0 allocation score on node5: 0
+pcmk__primitive_assign: rsc2:1 allocation score on node1: 0
+pcmk__primitive_assign: rsc2:1 allocation score on node2: 0
+pcmk__primitive_assign: rsc2:1 allocation score on node3: -INFINITY
+pcmk__primitive_assign: rsc2:1 allocation score on node4: 0
+pcmk__primitive_assign: rsc2:1 allocation score on node5: 0
+pcmk__primitive_assign: rsc2:2 allocation score on node1: 0
+pcmk__primitive_assign: rsc2:2 allocation score on node2: 0
+pcmk__primitive_assign: rsc2:2 allocation score on node3: -INFINITY
+pcmk__primitive_assign: rsc2:2 allocation score on node4: -INFINITY
+pcmk__primitive_assign: rsc2:2 allocation score on node5: 0
+pcmk__primitive_assign: rsc2:3 allocation score on node1: 0
+pcmk__primitive_assign: rsc2:3 allocation score on node2: 0
+pcmk__primitive_assign: rsc2:3 allocation score on node3: -INFINITY
+pcmk__primitive_assign: rsc2:3 allocation score on node4: -INFINITY
+pcmk__primitive_assign: rsc2:3 allocation score on node5: -INFINITY
+pcmk__primitive_assign: rsc2:4 allocation score on node1: -INFINITY
+pcmk__primitive_assign: rsc2:4 allocation score on node2: 0
+pcmk__primitive_assign: rsc2:4 allocation score on node3: -INFINITY
+pcmk__primitive_assign: rsc2:4 allocation score on node4: -INFINITY
+pcmk__primitive_assign: rsc2:4 allocation score on node5: -INFINITY
+pcmk__primitive_assign: rsc3 allocation score on node1: -INFINITY
+pcmk__primitive_assign: rsc3 allocation score on node2: -INFINITY
+pcmk__primitive_assign: rsc3 allocation score on node3: -INFINITY
+pcmk__primitive_assign: rsc3 allocation score on node4: -INFINITY
+pcmk__primitive_assign: rsc3 allocation score on node5: -INFINITY
+rsc2:0 promotion score on node3: -INFINITY
+rsc2:1 promotion score on node4: -INFINITY
+rsc2:2 promotion score on node5: -INFINITY
+rsc2:3 promotion score on node1: -INFINITY
+rsc2:4 promotion score on node2: INFINITY
diff --git a/cts/scheduler/summary/bundle-order-partial-stop.summary b/cts/scheduler/summary/bundle-order-partial-stop.summary
index 39eab8f93e..faf5e5d51f 100644
--- a/cts/scheduler/summary/bundle-order-partial-stop.summary
+++ b/cts/scheduler/summary/bundle-order-partial-stop.summary
@@ -1,127 +1,127 @@
Current cluster status:
* Node List:
* Online: [ undercloud ]
* GuestOnline: [ galera-bundle-0 rabbitmq-bundle-0 redis-bundle-0 ]
* Full List of Resources:
* Container bundle: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]:
* rabbitmq-bundle-0 (ocf:heartbeat:rabbitmq-cluster): Started undercloud
* Container bundle: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]:
* galera-bundle-0 (ocf:heartbeat:galera): Promoted undercloud
* Container bundle: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]:
* redis-bundle-0 (ocf:heartbeat:redis): Promoted undercloud
* ip-192.168.122.254 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.250 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.249 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.253 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.247 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.248 (ocf:heartbeat:IPaddr2): Started undercloud
* Container bundle: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]:
* haproxy-bundle-docker-0 (ocf:heartbeat:docker): Started undercloud
* Container bundle: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]:
* openstack-cinder-volume-docker-0 (ocf:heartbeat:docker): Started undercloud
Transition Summary:
* Stop rabbitmq-bundle-docker-0 ( undercloud ) due to node availability
* Stop rabbitmq-bundle-0 ( undercloud ) due to node availability
- * Stop rabbitmq:0 ( rabbitmq-bundle-0 ) due to unrunnable rabbitmq-bundle-0 start
+ * Stop rabbitmq:0 ( rabbitmq-bundle-0 ) due to colocation with haproxy-bundle-docker-0
* Stop galera-bundle-docker-0 ( undercloud ) due to node availability
* Stop galera-bundle-0 ( undercloud ) due to node availability
* Stop galera:0 ( Promoted galera-bundle-0 ) due to unrunnable galera-bundle-0 start
* Stop redis-bundle-docker-0 ( undercloud ) due to node availability
* Stop redis-bundle-0 ( undercloud ) due to node availability
* Stop redis:0 ( Promoted redis-bundle-0 ) due to unrunnable redis-bundle-0 start
* Stop ip-192.168.122.254 ( undercloud ) due to node availability
* Stop ip-192.168.122.250 ( undercloud ) due to node availability
* Stop ip-192.168.122.249 ( undercloud ) due to node availability
* Stop ip-192.168.122.253 ( undercloud ) due to node availability
* Stop ip-192.168.122.247 ( undercloud ) due to node availability
* Stop ip-192.168.122.248 ( undercloud ) due to node availability
* Stop haproxy-bundle-docker-0 ( undercloud ) due to node availability
* Stop openstack-cinder-volume-docker-0 ( undercloud ) due to node availability
Executing Cluster Transition:
* Pseudo action: rabbitmq-bundle-clone_pre_notify_stop_0
* Resource action: galera cancel=10000 on galera-bundle-0
* Resource action: redis cancel=20000 on redis-bundle-0
* Pseudo action: redis-bundle-master_pre_notify_demote_0
* Pseudo action: openstack-cinder-volume_stop_0
* Pseudo action: redis-bundle_demote_0
* Pseudo action: galera-bundle_demote_0
* Pseudo action: rabbitmq-bundle_stop_0
* Resource action: rabbitmq notify on rabbitmq-bundle-0
* Pseudo action: rabbitmq-bundle-clone_confirmed-pre_notify_stop_0
* Pseudo action: rabbitmq-bundle-clone_stop_0
* Pseudo action: galera-bundle-master_demote_0
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-pre_notify_demote_0
* Pseudo action: redis-bundle-master_demote_0
* Resource action: openstack-cinder-volume-docker-0 stop on undercloud
* Pseudo action: openstack-cinder-volume_stopped_0
* Resource action: rabbitmq stop on rabbitmq-bundle-0
* Pseudo action: rabbitmq-bundle-clone_stopped_0
* Resource action: rabbitmq-bundle-0 stop on undercloud
* Resource action: galera demote on galera-bundle-0
* Pseudo action: galera-bundle-master_demoted_0
* Resource action: redis demote on redis-bundle-0
* Pseudo action: redis-bundle-master_demoted_0
* Pseudo action: galera-bundle_demoted_0
* Pseudo action: galera-bundle_stop_0
* Pseudo action: rabbitmq-bundle-clone_post_notify_stopped_0
* Resource action: rabbitmq-bundle-docker-0 stop on undercloud
* Pseudo action: galera-bundle-master_stop_0
* Pseudo action: redis-bundle-master_post_notify_demoted_0
* Pseudo action: rabbitmq-bundle-clone_confirmed-post_notify_stopped_0
* Resource action: galera stop on galera-bundle-0
* Pseudo action: galera-bundle-master_stopped_0
* Resource action: galera-bundle-0 stop on undercloud
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-post_notify_demoted_0
* Pseudo action: redis-bundle-master_pre_notify_stop_0
* Pseudo action: redis-bundle_demoted_0
* Pseudo action: rabbitmq-bundle_stopped_0
* Resource action: galera-bundle-docker-0 stop on undercloud
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-pre_notify_stop_0
* Pseudo action: galera-bundle_stopped_0
* Pseudo action: redis-bundle_stop_0
* Pseudo action: redis-bundle-master_stop_0
* Resource action: redis stop on redis-bundle-0
* Pseudo action: redis-bundle-master_stopped_0
* Resource action: redis-bundle-0 stop on undercloud
* Pseudo action: redis-bundle-master_post_notify_stopped_0
* Resource action: redis-bundle-docker-0 stop on undercloud
* Pseudo action: redis-bundle-master_confirmed-post_notify_stopped_0
* Pseudo action: redis-bundle_stopped_0
* Pseudo action: haproxy-bundle_stop_0
* Resource action: haproxy-bundle-docker-0 stop on undercloud
* Pseudo action: haproxy-bundle_stopped_0
* Resource action: ip-192.168.122.254 stop on undercloud
* Resource action: ip-192.168.122.250 stop on undercloud
* Resource action: ip-192.168.122.249 stop on undercloud
* Resource action: ip-192.168.122.253 stop on undercloud
* Resource action: ip-192.168.122.247 stop on undercloud
* Resource action: ip-192.168.122.248 stop on undercloud
* Cluster action: do_shutdown on undercloud
Revised Cluster Status:
* Node List:
* Online: [ undercloud ]
* Full List of Resources:
* Container bundle: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]:
* rabbitmq-bundle-0 (ocf:heartbeat:rabbitmq-cluster): Stopped
* Container bundle: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]:
* galera-bundle-0 (ocf:heartbeat:galera): Stopped
* Container bundle: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]:
* redis-bundle-0 (ocf:heartbeat:redis): Stopped
* ip-192.168.122.254 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.250 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.249 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.253 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.247 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.248 (ocf:heartbeat:IPaddr2): Stopped
* Container bundle: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]:
* haproxy-bundle-docker-0 (ocf:heartbeat:docker): Stopped
* Container bundle: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]:
* openstack-cinder-volume-docker-0 (ocf:heartbeat:docker): Stopped
diff --git a/cts/scheduler/summary/bundle-order-stop.summary b/cts/scheduler/summary/bundle-order-stop.summary
index 39eab8f93e..faf5e5d51f 100644
--- a/cts/scheduler/summary/bundle-order-stop.summary
+++ b/cts/scheduler/summary/bundle-order-stop.summary
@@ -1,127 +1,127 @@
Current cluster status:
* Node List:
* Online: [ undercloud ]
* GuestOnline: [ galera-bundle-0 rabbitmq-bundle-0 redis-bundle-0 ]
* Full List of Resources:
* Container bundle: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]:
* rabbitmq-bundle-0 (ocf:heartbeat:rabbitmq-cluster): Started undercloud
* Container bundle: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]:
* galera-bundle-0 (ocf:heartbeat:galera): Promoted undercloud
* Container bundle: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]:
* redis-bundle-0 (ocf:heartbeat:redis): Promoted undercloud
* ip-192.168.122.254 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.250 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.249 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.253 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.247 (ocf:heartbeat:IPaddr2): Started undercloud
* ip-192.168.122.248 (ocf:heartbeat:IPaddr2): Started undercloud
* Container bundle: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]:
* haproxy-bundle-docker-0 (ocf:heartbeat:docker): Started undercloud
* Container bundle: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]:
* openstack-cinder-volume-docker-0 (ocf:heartbeat:docker): Started undercloud
Transition Summary:
* Stop rabbitmq-bundle-docker-0 ( undercloud ) due to node availability
* Stop rabbitmq-bundle-0 ( undercloud ) due to node availability
- * Stop rabbitmq:0 ( rabbitmq-bundle-0 ) due to unrunnable rabbitmq-bundle-0 start
+ * Stop rabbitmq:0 ( rabbitmq-bundle-0 ) due to colocation with haproxy-bundle-docker-0
* Stop galera-bundle-docker-0 ( undercloud ) due to node availability
* Stop galera-bundle-0 ( undercloud ) due to node availability
* Stop galera:0 ( Promoted galera-bundle-0 ) due to unrunnable galera-bundle-0 start
* Stop redis-bundle-docker-0 ( undercloud ) due to node availability
* Stop redis-bundle-0 ( undercloud ) due to node availability
* Stop redis:0 ( Promoted redis-bundle-0 ) due to unrunnable redis-bundle-0 start
* Stop ip-192.168.122.254 ( undercloud ) due to node availability
* Stop ip-192.168.122.250 ( undercloud ) due to node availability
* Stop ip-192.168.122.249 ( undercloud ) due to node availability
* Stop ip-192.168.122.253 ( undercloud ) due to node availability
* Stop ip-192.168.122.247 ( undercloud ) due to node availability
* Stop ip-192.168.122.248 ( undercloud ) due to node availability
* Stop haproxy-bundle-docker-0 ( undercloud ) due to node availability
* Stop openstack-cinder-volume-docker-0 ( undercloud ) due to node availability
Executing Cluster Transition:
* Pseudo action: rabbitmq-bundle-clone_pre_notify_stop_0
* Resource action: galera cancel=10000 on galera-bundle-0
* Resource action: redis cancel=20000 on redis-bundle-0
* Pseudo action: redis-bundle-master_pre_notify_demote_0
* Pseudo action: openstack-cinder-volume_stop_0
* Pseudo action: redis-bundle_demote_0
* Pseudo action: galera-bundle_demote_0
* Pseudo action: rabbitmq-bundle_stop_0
* Resource action: rabbitmq notify on rabbitmq-bundle-0
* Pseudo action: rabbitmq-bundle-clone_confirmed-pre_notify_stop_0
* Pseudo action: rabbitmq-bundle-clone_stop_0
* Pseudo action: galera-bundle-master_demote_0
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-pre_notify_demote_0
* Pseudo action: redis-bundle-master_demote_0
* Resource action: openstack-cinder-volume-docker-0 stop on undercloud
* Pseudo action: openstack-cinder-volume_stopped_0
* Resource action: rabbitmq stop on rabbitmq-bundle-0
* Pseudo action: rabbitmq-bundle-clone_stopped_0
* Resource action: rabbitmq-bundle-0 stop on undercloud
* Resource action: galera demote on galera-bundle-0
* Pseudo action: galera-bundle-master_demoted_0
* Resource action: redis demote on redis-bundle-0
* Pseudo action: redis-bundle-master_demoted_0
* Pseudo action: galera-bundle_demoted_0
* Pseudo action: galera-bundle_stop_0
* Pseudo action: rabbitmq-bundle-clone_post_notify_stopped_0
* Resource action: rabbitmq-bundle-docker-0 stop on undercloud
* Pseudo action: galera-bundle-master_stop_0
* Pseudo action: redis-bundle-master_post_notify_demoted_0
* Pseudo action: rabbitmq-bundle-clone_confirmed-post_notify_stopped_0
* Resource action: galera stop on galera-bundle-0
* Pseudo action: galera-bundle-master_stopped_0
* Resource action: galera-bundle-0 stop on undercloud
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-post_notify_demoted_0
* Pseudo action: redis-bundle-master_pre_notify_stop_0
* Pseudo action: redis-bundle_demoted_0
* Pseudo action: rabbitmq-bundle_stopped_0
* Resource action: galera-bundle-docker-0 stop on undercloud
* Resource action: redis notify on redis-bundle-0
* Pseudo action: redis-bundle-master_confirmed-pre_notify_stop_0
* Pseudo action: galera-bundle_stopped_0
* Pseudo action: redis-bundle_stop_0
* Pseudo action: redis-bundle-master_stop_0
* Resource action: redis stop on redis-bundle-0
* Pseudo action: redis-bundle-master_stopped_0
* Resource action: redis-bundle-0 stop on undercloud
* Pseudo action: redis-bundle-master_post_notify_stopped_0
* Resource action: redis-bundle-docker-0 stop on undercloud
* Pseudo action: redis-bundle-master_confirmed-post_notify_stopped_0
* Pseudo action: redis-bundle_stopped_0
* Pseudo action: haproxy-bundle_stop_0
* Resource action: haproxy-bundle-docker-0 stop on undercloud
* Pseudo action: haproxy-bundle_stopped_0
* Resource action: ip-192.168.122.254 stop on undercloud
* Resource action: ip-192.168.122.250 stop on undercloud
* Resource action: ip-192.168.122.249 stop on undercloud
* Resource action: ip-192.168.122.253 stop on undercloud
* Resource action: ip-192.168.122.247 stop on undercloud
* Resource action: ip-192.168.122.248 stop on undercloud
* Cluster action: do_shutdown on undercloud
Revised Cluster Status:
* Node List:
* Online: [ undercloud ]
* Full List of Resources:
* Container bundle: rabbitmq-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-rabbitmq:latest]:
* rabbitmq-bundle-0 (ocf:heartbeat:rabbitmq-cluster): Stopped
* Container bundle: galera-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-mariadb:latest]:
* galera-bundle-0 (ocf:heartbeat:galera): Stopped
* Container bundle: redis-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-redis:latest]:
* redis-bundle-0 (ocf:heartbeat:redis): Stopped
* ip-192.168.122.254 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.250 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.249 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.253 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.247 (ocf:heartbeat:IPaddr2): Stopped
* ip-192.168.122.248 (ocf:heartbeat:IPaddr2): Stopped
* Container bundle: haproxy-bundle [192.168.24.1:8787/tripleoupstream/centos-binary-haproxy:latest]:
* haproxy-bundle-docker-0 (ocf:heartbeat:docker): Stopped
* Container bundle: openstack-cinder-volume [192.168.24.1:8787/tripleoupstream/centos-binary-cinder-volume:latest]:
* openstack-cinder-volume-docker-0 (ocf:heartbeat:docker): Stopped
diff --git a/cts/scheduler/summary/promoted-with-blocked.summary b/cts/scheduler/summary/promoted-with-blocked.summary
new file mode 100644
index 0000000000..f045b61dfe
--- /dev/null
+++ b/cts/scheduler/summary/promoted-with-blocked.summary
@@ -0,0 +1,59 @@
+1 of 8 resource instances DISABLED and 0 BLOCKED from further action due to failure
+
+Current cluster status:
+ * Node List:
+ * Online: [ node1 node2 node3 node4 node5 ]
+
+ * Full List of Resources:
+ * Fencing (stonith:fence_xvm): Started node1
+ * rsc1 (ocf:pacemaker:Dummy): Stopped
+ * Clone Set: rsc2-clone [rsc2] (promotable):
+ * Stopped: [ node1 node2 node3 node4 node5 ]
+ * rsc3 (ocf:pacemaker:Dummy): Stopped (disabled)
+
+Transition Summary:
+ * Start rsc1 ( node2 ) due to unrunnable rsc3 start (blocked)
+ * Start rsc2:0 ( node3 )
+ * Start rsc2:1 ( node4 )
+ * Start rsc2:2 ( node5 )
+ * Start rsc2:3 ( node1 )
+ * Promote rsc2:4 ( Stopped -> Promoted node2 ) due to colocation with rsc1 (blocked)
+
+Executing Cluster Transition:
+ * Resource action: rsc1 monitor on node5
+ * Resource action: rsc1 monitor on node4
+ * Resource action: rsc1 monitor on node3
+ * Resource action: rsc1 monitor on node2
+ * Resource action: rsc1 monitor on node1
+ * Resource action: rsc2:0 monitor on node3
+ * Resource action: rsc2:1 monitor on node4
+ * Resource action: rsc2:2 monitor on node5
+ * Resource action: rsc2:3 monitor on node1
+ * Resource action: rsc2:4 monitor on node2
+ * Pseudo action: rsc2-clone_start_0
+ * Resource action: rsc3 monitor on node5
+ * Resource action: rsc3 monitor on node4
+ * Resource action: rsc3 monitor on node3
+ * Resource action: rsc3 monitor on node2
+ * Resource action: rsc3 monitor on node1
+ * Resource action: rsc2:0 start on node3
+ * Resource action: rsc2:1 start on node4
+ * Resource action: rsc2:2 start on node5
+ * Resource action: rsc2:3 start on node1
+ * Resource action: rsc2:4 start on node2
+ * Pseudo action: rsc2-clone_running_0
+ * Resource action: rsc2:0 monitor=10000 on node3
+ * Resource action: rsc2:1 monitor=10000 on node4
+ * Resource action: rsc2:2 monitor=10000 on node5
+ * Resource action: rsc2:3 monitor=10000 on node1
+
+Revised Cluster Status:
+ * Node List:
+ * Online: [ node1 node2 node3 node4 node5 ]
+
+ * Full List of Resources:
+ * Fencing (stonith:fence_xvm): Started node1
+ * rsc1 (ocf:pacemaker:Dummy): Stopped
+ * Clone Set: rsc2-clone [rsc2] (promotable):
+ * Unpromoted: [ node1 node2 node3 node4 node5 ]
+ * rsc3 (ocf:pacemaker:Dummy): Stopped (disabled)
diff --git a/cts/scheduler/xml/promoted-with-blocked.xml b/cts/scheduler/xml/promoted-with-blocked.xml
new file mode 100644
index 0000000000..c9a31b2ad2
--- /dev/null
+++ b/cts/scheduler/xml/promoted-with-blocked.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index 38ce24513b..6079fdb3a5 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,823 +1,823 @@
/*
* Copyright 2021-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__LIBPACEMAKER_PRIVATE__H
# define PCMK__LIBPACEMAKER_PRIVATE__H
/* This header is for the sole use of libpacemaker, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include // pe_action_t, pe_node_t, pe_working_set_t
// Flags to modify the behavior of pcmk__add_colocated_node_scores()
enum pcmk__coloc_select {
// With no other flags, apply all "with this" colocations
pcmk__coloc_select_default = 0,
// Apply "this with" colocations instead of "with this" colocations
pcmk__coloc_select_this_with = (1 << 0),
// Apply only colocations with non-negative scores
pcmk__coloc_select_nonnegative = (1 << 1),
// Apply only colocations with at least one matching node
pcmk__coloc_select_active = (1 << 2),
};
// Flags the update_ordered_actions() method can return
enum pcmk__updated {
pcmk__updated_none = 0, // Nothing changed
pcmk__updated_first = (1 << 0), // First action was updated
pcmk__updated_then = (1 << 1), // Then action was updated
};
#define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \
au_flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_set), #flags_to_set); \
} while (0)
#define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \
au_flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
// Resource allocation methods
struct resource_alloc_functions_s {
/*!
* \internal
* \brief Assign a resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*/
pe_node_t *(*assign)(pe_resource_t *rsc, const pe_node_t *prefer);
/*!
* \internal
* \brief Create all actions needed for a given resource
*
* \param[in,out] rsc Resource to create actions for
*/
void (*create_actions)(pe_resource_t *rsc);
/*!
* \internal
* \brief Schedule any probes needed for a resource on a node
*
* \param[in] rsc Resource to create probe for
* \param[in] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool (*create_probe)(pe_resource_t *rsc, pe_node_t *node);
/*!
* \internal
* \brief Create implicit constraints needed for a resource
*
* \param[in,out] rsc Resource to create implicit constraints for
*/
void (*internal_constraints)(pe_resource_t *rsc);
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node weights (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 (*apply_coloc_score) (pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
/*!
* \internal
* \brief Create list of all resources in colocations with a given resource
*
* Given a resource, create a list of all resources involved in mandatory
* colocations with it, whether directly or indirectly via chained colocations.
*
* \param[in] rsc Resource to add to colocated list
* \param[in] orig_rsc Resource originally requested
* \param[in] colocated_rscs Existing list
*
* \return List of given resource and all resources involved in colocations
*
* \note This function is recursive; top-level callers should pass NULL as
* \p colocated_rscs and \p orig_rsc, and the desired resource as
* \p rsc. The recursive calls will use other values.
*/
GList *(*colocated_resources)(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs);
/*!
* \internal
* \brief Apply a location constraint to a resource's allowed node scores
*
* \param[in,out] rsc Resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void (*apply_location)(pe_resource_t *rsc, pe__location_t *location);
/*!
* \internal
* \brief Return action flags for a given resource action
*
* \param[in,out] action Action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
* \note For primitives, this will be the same as action->flags regardless
* of node. For collective resources, the flags can differ due to
* multiple instances possibly being involved.
*/
enum pe_action_flags (*action_flags)(pe_action_t *action,
const pe_node_t *node);
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions'
* flags (and runnable_before members if appropriate) as appropriate for the
* ordering. In some cases, the ordering could be disabled 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 pe_action_optional to affect only
* mandatory actions, and pe_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pe_ordering flags to apply
* \param[in,out] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t (*update_ordered_actions)(pe_action_t *first, pe_action_t *then,
const pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set);
void (*output_actions)(pe_resource_t *rsc);
/*!
* \internal
* \brief Add a resource's actions to the transition graph
*
* \param[in] rsc Resource whose actions should be added
*/
void (*add_actions_to_graph)(pe_resource_t *rsc);
/*!
* \internal
* \brief Add meta-attributes relevant to transition graph actions to XML
*
* If a given resource supports variant-specific meta-attributes that are
* needed for transition graph actions, add them to a given XML element.
*
* \param[in] rsc Resource whose meta-attributes should be added
* \param[in,out] xml Transition graph action attributes XML to add to
*/
void (*add_graph_meta)(pe_resource_t *rsc, xmlNode *xml);
/*!
* \internal
* \brief Add a resource's utilization to a table of utilization values
*
* This function is used when summing the utilization of a resource and all
* resources colocated with it, to determine whether a node has sufficient
* capacity. Given a resource and a table of utilization values, it will add
* the resource's utilization to the existing values, if the resource has
* not yet been allocated to a node.
*
* \param[in] rsc Resource with utilization to add
* \param[in] orig_rsc Resource being allocated (for logging only)
* \param[in] all_rscs List of all resources that will be summed
* \param[in,out] utilization Table of utilization values to add to
*/
void (*add_utilization)(const pe_resource_t *rsc,
const pe_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization);
/*!
* \internal
* \brief Apply a shutdown lock for a resource, if appropriate
*
* \param[in] rsc Resource to check for shutdown lock
*/
void (*shutdown_lock)(pe_resource_t *rsc);
};
// Actions (pcmk_sched_actions.c)
G_GNUC_INTERNAL
void pcmk__update_action_for_orderings(pe_action_t *action,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
uint32_t pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then,
const pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__log_action(const char *pre_text, pe_action_t *action, bool details);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_cancel_action(pe_resource_t *rsc, const char *name,
guint interval_ms, const pe_node_t *node);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_shutdown_action(pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_locks_rsc_to_node(const pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__deduplicate_action_inputs(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__output_actions(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node,
xmlNode *xml_op);
G_GNUC_INTERNAL
void pcmk__handle_rsc_config_changes(pe_working_set_t *data_set);
// Recurring actions (pcmk_sched_recurring.c)
G_GNUC_INTERNAL
void pcmk__create_recurring_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__schedule_cancel(pe_resource_t *rsc, const char *call_id,
const char *task, guint interval_ms,
const pe_node_t *node, const char *reason);
G_GNUC_INTERNAL
void pcmk__reschedule_recurring(pe_resource_t *rsc, const char *task,
guint interval_ms, pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_is_recurring(const pe_action_t *action);
// Producing transition graphs (pcmk_graph_producer.c)
G_GNUC_INTERNAL
bool pcmk__graph_has_loop(pe_action_t *init_action, pe_action_t *action,
pe_action_wrapper_t *input);
G_GNUC_INTERNAL
void pcmk__add_rsc_actions_to_graph(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_graph(pe_working_set_t *data_set);
// Fencing (pcmk_sched_fencing.c)
G_GNUC_INTERNAL
void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_vs_unfence(pe_resource_t *rsc, pe_node_t *node,
pe_action_t *action, enum pe_ordering order);
G_GNUC_INTERNAL
void pcmk__fence_guest(pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__node_unfenced(pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_restart_vs_unfence(gpointer data, gpointer user_data);
// Injected scheduler inputs (pcmk_sched_injections.c)
void pcmk__inject_scheduler_input(pe_working_set_t *data_set, cib_t *cib,
pcmk_injections_t *injections);
// Constraints of any type (pcmk_sched_constraints.c)
G_GNUC_INTERNAL
pe_resource_t *pcmk__find_constraint_resource(GList *rsc_list, const char *id);
G_GNUC_INTERNAL
xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__valid_resource_or_tag(pe_working_set_t *data_set, const char *id,
pe_resource_t **rsc, pe_tag_t **tag);
G_GNUC_INTERNAL
bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
bool convert_rsc, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__create_internal_constraints(pe_working_set_t *data_set);
// Location constraints
G_GNUC_INTERNAL
void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe__location_t *pcmk__new_location(const char *id, pe_resource_t *rsc,
int node_weight, const char *discover_mode,
pe_node_t *foo_node,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_locations(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_location(pe_resource_t *rsc, pe__location_t *constraint);
// Colocation constraints (pcmk_sched_colocation.c)
enum pcmk__coloc_affects {
pcmk__coloc_affects_nothing = 0,
pcmk__coloc_affects_location,
pcmk__coloc_affects_role,
};
G_GNUC_INTERNAL
enum pcmk__coloc_affects pcmk__colocation_affects(const pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool preview);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_weights(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_priority(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr,
float factor, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__add_this_with(pe_resource_t *rsc, pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__add_with_this(pe_resource_t *rsc, pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__new_colocation(const char *id, const char *node_attr, int score,
pe_resource_t *dependent, pe_resource_t *primary,
const char *dependent_role, const char *primary_role,
bool influence, pe_working_set_t *data_set);
G_GNUC_INTERNAL
-void pcmk__block_colocated_starts(pe_action_t *action,
- pe_working_set_t *data_set);
+void pcmk__block_colocation_dependents(pe_action_t *action,
+ pe_working_set_t *data_set);
/*!
* \internal
* \brief Check whether colocation's dependent preferences should be considered
*
* \param[in] colocation Colocation constraint
* \param[in] rsc Primary instance (normally this will be
* colocation->primary, which NULL will be treated as,
* but for clones or bundles with multiple instances
* this can be a particular instance)
*
* \return true if colocation influence should be effective, otherwise false
*/
static inline bool
pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
const pe_resource_t *rsc)
{
if (rsc == NULL) {
rsc = colocation->primary;
}
/* A bundle replica colocates its remote connection with its container,
* using a finite score so that the container can run on Pacemaker Remote
* nodes.
*
* Moving a connection is lightweight and does not interrupt the service,
* while moving a container is heavyweight and does interrupt the service,
* so don't move a clean, active container based solely on the preferences
* of its connection.
*
* This also avoids problematic scenarios where two containers want to
* perpetually swap places.
*/
if (pcmk_is_set(colocation->dependent->flags, pe_rsc_allow_remote_remotes)
&& !pcmk_is_set(rsc->flags, pe_rsc_failed)
&& pcmk__list_of_1(rsc->running_on)) {
return false;
}
/* The dependent in a colocation influences the primary's location
* if the influence option is true or the primary is not yet active.
*/
return colocation->influence || (rsc->running_on == NULL);
}
// Ordering constraints (pcmk_sched_ordering.c)
G_GNUC_INTERNAL
void pcmk__new_ordering(pe_resource_t *first_rsc, char *first_task,
pe_action_t *first_action, pe_resource_t *then_rsc,
char *then_task, pe_action_t *then_action,
uint32_t flags, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__disable_invalid_orderings(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_stops_before_shutdown(pe_node_t *node,
pe_action_t *shutdown_op);
G_GNUC_INTERNAL
void pcmk__apply_orderings(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_after_each(pe_action_t *after, GList *list);
/*!
* \internal
* \brief Create a new ordering between two resource actions
*
* \param[in] first_rsc Resource for 'first' action
* \param[in] then_rsc Resource for 'then' action
* \param[in] first_task Action key for 'first' action
* \param[in] then_task Action key for 'then' action
* \param[in] flags Bitmask of enum pe_ordering flags
* \param[in] data_set Cluster working set to add ordering to
*/
#define pcmk__order_resource_actions(first_rsc, first_task, \
then_rsc, then_task, flags) \
pcmk__new_ordering((first_rsc), \
pcmk__op_key((first_rsc)->id, (first_task), 0), \
NULL, \
(then_rsc), \
pcmk__op_key((then_rsc)->id, (then_task), 0), \
NULL, (flags), (first_rsc)->cluster)
#define pcmk__order_starts(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \
(rsc2), CRMD_ACTION_START, (flags))
#define pcmk__order_stops(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \
(rsc2), CRMD_ACTION_STOP, (flags))
// Ticket constraints (pcmk_sched_tickets.c)
G_GNUC_INTERNAL
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set);
// Promotable clone resources (pcmk_sched_promotable.c)
G_GNUC_INTERNAL
void pcmk__add_promotion_scores(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__require_promotion_tickets(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__set_instance_roles(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_promotable_actions(pe_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__promotable_restart_ordering(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__order_promotable_instances(pe_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__update_dependent_with_promotable(const pe_resource_t *primary,
pe_resource_t *dependent,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__update_promotable_dependent_priority(const pe_resource_t *primary,
pe_resource_t *dependent,
const pcmk__colocation_t *colocation);
// Pacemaker Remote nodes (pcmk_sched_remote.c)
G_GNUC_INTERNAL
bool pcmk__is_failed_remote_node(pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_remote_connection_actions(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__rsc_corresponds_to_guest(pe_resource_t *rsc, pe_node_t *node);
G_GNUC_INTERNAL
pe_node_t *pcmk__connection_host_for_action(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params);
G_GNUC_INTERNAL
void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action);
// Primitives (pcmk_sched_primitive.c)
G_GNUC_INTERNAL
pe_node_t *pcmk__primitive_assign(pe_resource_t *rsc, const pe_node_t *prefer);
G_GNUC_INTERNAL
void pcmk__primitive_create_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__primitive_internal_constraints(pe_resource_t *rsc);
G_GNUC_INTERNAL
enum pe_action_flags pcmk__primitive_action_flags(pe_action_t *action,
const pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__primitive_apply_coloc_score(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__schedule_cleanup(pe_resource_t *rsc, const pe_node_t *node,
bool optional);
G_GNUC_INTERNAL
void pcmk__primitive_add_graph_meta(pe_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__primitive_add_utilization(const pe_resource_t *rsc,
const pe_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__primitive_shutdown_lock(pe_resource_t *rsc);
// Groups (pcmk_sched_group.c)
G_GNUC_INTERNAL
pe_node_t *pcmk__group_assign(pe_resource_t *rsc, const pe_node_t *prefer);
G_GNUC_INTERNAL
void pcmk__group_create_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__group_internal_constraints(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__group_apply_coloc_score(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__group_apply_location(pe_resource_t *rsc, pe__location_t *location);
G_GNUC_INTERNAL
enum pe_action_flags pcmk__group_action_flags(pe_action_t *action,
const pe_node_t *node);
G_GNUC_INTERNAL
uint32_t pcmk__group_update_ordered_actions(pe_action_t *first,
pe_action_t *then,
const pe_node_t *node,
uint32_t flags, uint32_t filter,
uint32_t type,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
GList *pcmk__group_colocated_resources(pe_resource_t *rsc,
pe_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__group_add_utilization(const pe_resource_t *rsc,
const pe_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__group_shutdown_lock(pe_resource_t *rsc);
// Clones (pcmk_sched_clone.c)
G_GNUC_INTERNAL
void pcmk__clone_apply_coloc_score(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
// Bundles (pcmk_sched_bundle.c)
G_GNUC_INTERNAL
void pcmk__bundle_apply_coloc_score(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__output_bundle_actions(pe_resource_t *rsc);
// Injections (pcmk_injections.c)
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
bool up);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
const char *resource,
const char *lrm_name,
const char *rclass,
const char *rtype,
const char *rprovider);
G_GNUC_INTERNAL
void pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node,
const char *resource, const char *task,
guint interval_ms, int rc);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
lrmd_event_data_t *op, int target_rc);
// Nodes (pcmk_sched_nodes.c)
G_GNUC_INTERNAL
bool pcmk__node_available(const pe_node_t *node, bool consider_score,
bool consider_guest);
G_GNUC_INTERNAL
bool pcmk__any_node_available(GHashTable *nodes);
G_GNUC_INTERNAL
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
G_GNUC_INTERNAL
GList *pcmk__sort_nodes(GList *nodes, pe_node_t *active_node);
G_GNUC_INTERNAL
void pcmk__apply_node_health(pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe_node_t *pcmk__top_allowed_node(const pe_resource_t *rsc,
const pe_node_t *node);
// Functions applying to more than one variant (pcmk_sched_resource.c)
G_GNUC_INTERNAL
void pcmk__set_allocation_methods(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__rsc_agent_changed(pe_resource_t *rsc, pe_node_t *node,
const xmlNode *rsc_entry, bool active_on_node);
G_GNUC_INTERNAL
GList *pcmk__rscs_matching_id(const char *id, pe_working_set_t *data_set);
G_GNUC_INTERNAL
GList *pcmk__colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__noop_add_graph_meta(pe_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__output_resource_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__finalize_assignment(pe_resource_t *rsc, pe_node_t *chosen,
bool force);
G_GNUC_INTERNAL
bool pcmk__assign_resource(pe_resource_t *rsc, pe_node_t *node, bool force);
G_GNUC_INTERNAL
void pcmk__unassign_resource(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__threshold_reached(pe_resource_t *rsc, pe_node_t *node,
pe_resource_t **failed);
G_GNUC_INTERNAL
void pcmk__sort_resources(pe_working_set_t *data_set);
G_GNUC_INTERNAL
gint pcmk__cmp_instance(gconstpointer a, gconstpointer b);
G_GNUC_INTERNAL
gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b);
// Functions related to probes (pcmk_sched_probes.c)
G_GNUC_INTERNAL
bool pcmk__probe_rsc_on_node(pe_resource_t *rsc, pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_probes(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__probe_resource_list(GList *rscs, pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__schedule_probes(pe_working_set_t *data_set);
// Functions related to live migration (pcmk_sched_migration.c)
void pcmk__create_migration_actions(pe_resource_t *rsc,
const pe_node_t *current);
void pcmk__abort_dangling_migration(void *data, void *user_data);
bool pcmk__rsc_can_migrate(const pe_resource_t *rsc, const pe_node_t *current);
void pcmk__order_migration_equivalents(pe__ordering_t *order);
// Functions related to node utilization (pcmk_sched_utilization.c)
G_GNUC_INTERNAL
int pcmk__compare_node_capacities(const pe_node_t *node1,
const pe_node_t *node2);
G_GNUC_INTERNAL
void pcmk__consume_node_capacity(GHashTable *current_utilization,
pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__release_node_capacity(GHashTable *current_utilization,
const pe_resource_t *rsc);
G_GNUC_INTERNAL
const pe_node_t *pcmk__ban_insufficient_capacity(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_utilization_constraints(pe_resource_t *rsc,
GList *allowed_nodes);
G_GNUC_INTERNAL
void pcmk__show_node_capacities(const char *desc, pe_working_set_t *data_set);
#endif // PCMK__LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_sched_actions.c b/lib/pacemaker/pcmk_sched_actions.c
index 0bcc72bff9..8c61264c28 100644
--- a/lib/pacemaker/pcmk_sched_actions.c
+++ b/lib/pacemaker/pcmk_sched_actions.c
@@ -1,1924 +1,1924 @@
/*
* Copyright 2004-2022 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 "libpacemaker_private.h"
/*!
* \internal
* \brief Get the action flags relevant to ordering constraints
*
* \param[in] action Action to check
* \param[in] node Node that *other* action in the ordering is on
* (used only for clone resource actions)
*
* \return Action flags that should be used for orderings
*/
static enum pe_action_flags
action_flags_for_ordering(pe_action_t *action, pe_node_t *node)
{
bool runnable = false;
enum pe_action_flags flags;
// For non-resource actions, return the action flags
if (action->rsc == NULL) {
return action->flags;
}
/* For non-clone resources, or a clone action not assigned to a node,
* return the flags as determined by the resource method without a node
* specified.
*/
flags = action->rsc->cmds->action_flags(action, NULL);
if ((node == NULL) || !pe_rsc_is_clone(action->rsc)) {
return flags;
}
/* Otherwise (i.e., for clone resource actions on a specific node), first
* remember whether the non-node-specific action is runnable.
*/
runnable = pcmk_is_set(flags, pe_action_runnable);
// Then recheck the resource method with the node
flags = action->rsc->cmds->action_flags(action, node);
/* For clones in ordering constraints, the node-specific "runnable" doesn't
* matter, just the non-node-specific setting (i.e., is the action runnable
* anywhere).
*
* This applies only to runnable, and only for ordering constraints. This
* function shouldn't be used for other types of constraints without
* changes. Not very satisfying, but it's logical and appears to work well.
*/
if (runnable && !pcmk_is_set(flags, pe_action_runnable)) {
pe__set_raw_action_flags(flags, action->rsc->id,
pe_action_runnable);
}
return flags;
}
/*!
* \internal
* \brief Get action UUID that should be used with a resource ordering
*
* When an action is ordered relative to an action for a collective resource
* (clone, group, or bundle), it actually needs to be ordered after all
* instances of the collective have completed the relevant action (for example,
* given "start CLONE then start RSC", RSC must wait until all instances of
* CLONE have started). Given the UUID and resource of the first action in an
* ordering, this returns the UUID of the action that should actually be used
* for ordering (for example, "CLONE_started_0" instead of "CLONE_start_0").
*
* \param[in] first_uuid UUID of first action in ordering
* \param[in] first_rsc Resource of first action in ordering
*
* \return Newly allocated copy of UUID to use with ordering
* \note It is the caller's responsibility to free the return value.
*/
static char *
action_uuid_for_ordering(const char *first_uuid, pe_resource_t *first_rsc)
{
guint interval_ms = 0;
char *uuid = NULL;
char *rid = NULL;
char *first_task_str = NULL;
enum action_tasks first_task = no_action;
enum action_tasks remapped_task = no_action;
// Only non-notify actions for collective resources need remapping
if ((strstr(first_uuid, "notify") != NULL)
|| (first_rsc->variant < pe_group)) {
goto done;
}
// Only non-recurring actions need remapping
CRM_ASSERT(parse_op_key(first_uuid, &rid, &first_task_str, &interval_ms));
if (interval_ms > 0) {
goto done;
}
first_task = text2task(first_task_str);
switch (first_task) {
case stop_rsc:
case start_rsc:
case action_notify:
case action_promote:
case action_demote:
remapped_task = first_task + 1;
break;
case stopped_rsc:
case started_rsc:
case action_notified:
case action_promoted:
case action_demoted:
remapped_task = first_task;
break;
case monitor_rsc:
case shutdown_crm:
case stonith_node:
break;
default:
crm_err("Unknown action '%s' in ordering", first_task_str);
break;
}
if (remapped_task != no_action) {
/* If a (clone) resource has notifications enabled, we want to order
* relative to when all notifications have been sent for the remapped
* task. Only outermost resources or those in bundles have
* notifications.
*/
if (pcmk_is_set(first_rsc->flags, pe_rsc_notify)
&& ((first_rsc->parent == NULL)
|| (pe_rsc_is_clone(first_rsc)
&& (first_rsc->parent->variant == pe_container)))) {
uuid = pcmk__notify_key(rid, "confirmed-post",
task2text(remapped_task));
} else {
uuid = pcmk__op_key(rid, task2text(remapped_task), 0);
}
pe_rsc_trace(first_rsc,
"Remapped action UUID %s to %s for ordering purposes",
first_uuid, uuid);
}
done:
if (uuid == NULL) {
uuid = strdup(first_uuid);
CRM_ASSERT(uuid != NULL);
}
free(first_task_str);
free(rid);
return uuid;
}
/*!
* \internal
* \brief Get actual action that should be used with an ordering
*
* When an action is ordered relative to an action for a collective resource
* (clone, group, or bundle), it actually needs to be ordered after all
* instances of the collective have completed the relevant action (for example,
* given "start CLONE then start RSC", RSC must wait until all instances of
* CLONE have started). Given the first action in an ordering, this returns the
* the action that should actually be used for ordering (for example, the
* started action instead of the start action).
*
* \param[in] action First action in an ordering
*
* \return Actual action that should be used for the ordering
*/
static pe_action_t *
action_for_ordering(pe_action_t *action)
{
pe_action_t *result = action;
pe_resource_t *rsc = action->rsc;
if ((rsc != NULL) && (rsc->variant >= pe_group) && (action->uuid != NULL)) {
char *uuid = action_uuid_for_ordering(action->uuid, rsc);
result = find_first_action(rsc->actions, uuid, NULL, NULL);
if (result == NULL) {
crm_warn("Not remapping %s to %s because %s does not have "
"remapped action", action->uuid, uuid, rsc->id);
result = action;
}
free(uuid);
}
return result;
}
/*!
* \internal
* \brief Update flags for ordering's actions appropriately for ordering's flags
*
* \param[in] first First action in an ordering
* \param[in] then Then action in an ordering
* \param[in] first_flags Action flags for \p first for ordering purposes
* \param[in] then_flags Action flags for \p then for ordering purposes
* \param[in] order Action wrapper for \p first in ordering
* \param[in] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags
*/
static uint32_t
update_action_for_ordering_flags(pe_action_t *first, pe_action_t *then,
enum pe_action_flags first_flags,
enum pe_action_flags then_flags,
pe_action_wrapper_t *order,
pe_working_set_t *data_set)
{
uint32_t changed = pcmk__updated_none;
/* The node will only be used for clones. If interleaved, node will be NULL,
* otherwise the ordering scope will be limited to the node. Normally, the
* whole 'then' clone should restart if 'first' is restarted, so then->node
* is needed.
*/
pe_node_t *node = then->node;
if (pcmk_is_set(order->type, pe_order_implies_then_on_node)) {
/* For unfencing, only instances of 'then' on the same node as 'first'
* (the unfencing operation) should restart, so reset node to
* first->node, at which point this case is handled like a normal
* pe_order_implies_then.
*/
pe__clear_order_flags(order->type, pe_order_implies_then_on_node);
pe__set_order_flags(order->type, pe_order_implies_then);
node = first->node;
pe_rsc_trace(then->rsc,
"%s then %s: mapped pe_order_implies_then_on_node to "
"pe_order_implies_then on %s",
first->uuid, then->uuid, pe__node_name(node));
}
if (pcmk_is_set(order->type, pe_order_implies_then)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags & pe_action_optional,
pe_action_optional,
pe_order_implies_then,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_optional)
&& pcmk_is_set(then->flags, pe_action_optional)) {
pe__clear_action_flags(then, pe_action_optional);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_then",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_restart) && (then->rsc != NULL)) {
enum pe_action_flags restart = pe_action_optional|pe_action_runnable;
changed |= then->rsc->cmds->update_ordered_actions(first, then, node,
first_flags, restart,
pe_order_restart,
data_set);
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_restart",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_implies_first)) {
if (first->rsc != NULL) {
changed |= first->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_implies_first,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_optional)
&& pcmk_is_set(first->flags, pe_action_runnable)) {
pe__clear_action_flags(first, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_promoted_implies_first)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags & pe_action_optional,
pe_action_optional,
pe_order_promoted_implies_first,
data_set);
}
pe_rsc_trace(then->rsc,
"%s then %s: %s after pe_order_promoted_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_one_or_more)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_one_or_more,
data_set);
} else if (pcmk_is_set(first_flags, pe_action_runnable)) {
// We have another runnable instance of "first"
then->runnable_before++;
/* Mark "then" as runnable if it requires a certain number of
* "before" instances to be runnable, and they now are.
*/
if ((then->runnable_before >= then->required_runnable_before)
&& !pcmk_is_set(then->flags, pe_action_runnable)) {
pe__set_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_one_or_more",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_probe) && (then->rsc != NULL)) {
if (!pcmk_is_set(first_flags, pe_action_runnable)
&& (first->rsc->running_on != NULL)) {
pe_rsc_trace(then->rsc,
"%s then %s: ignoring because first is stopping",
first->uuid, then->uuid);
order->type = pe_order_none;
} else {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_runnable_left,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_probe",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_runnable_left)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_runnable_left,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_runnable)
&& pcmk_is_set(then->flags, pe_action_runnable)) {
pe__clear_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_runnable_left",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_implies_first_migratable)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_implies_first_migratable,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after "
"pe_order_implies_first_migratable",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_pseudo_left)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_pseudo_left,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_pseudo_left",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_optional)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_optional,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_optional",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_asymmetrical)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_asymmetrical,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_asymmetrical",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(first->flags, pe_action_runnable)
&& pcmk_is_set(order->type, pe_order_implies_then_printed)
&& !pcmk_is_set(first_flags, pe_action_optional)) {
pe_rsc_trace(then->rsc, "%s will be in graph because %s is required",
then->uuid, first->uuid);
pe__set_action_flags(then, pe_action_print_always);
// Don't bother marking 'then' as changed just for this
}
if (pcmk_is_set(order->type, pe_order_implies_first_printed)
&& !pcmk_is_set(then_flags, pe_action_optional)) {
pe_rsc_trace(then->rsc, "%s will be in graph because %s is required",
first->uuid, then->uuid);
pe__set_action_flags(first, pe_action_print_always);
// Don't bother marking 'first' as changed just for this
}
if (pcmk_any_flags_set(order->type, pe_order_implies_then
|pe_order_implies_first
|pe_order_restart)
&& (first->rsc != NULL)
&& !pcmk_is_set(first->rsc->flags, pe_rsc_managed)
&& pcmk_is_set(first->rsc->flags, pe_rsc_block)
&& !pcmk_is_set(first->flags, pe_action_runnable)
&& pcmk__str_eq(first->task, RSC_STOP, pcmk__str_casei)) {
if (pcmk_is_set(then->flags, pe_action_runnable)) {
pe__clear_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after checking whether first "
"is blocked, unmanaged, unrunnable stop",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
return changed;
}
// Convenience macros for logging action properties
#define action_type_str(flags) \
(pcmk_is_set((flags), pe_action_pseudo)? "pseudo-action" : "action")
#define action_optional_str(flags) \
(pcmk_is_set((flags), pe_action_optional)? "optional" : "required")
#define action_runnable_str(flags) \
(pcmk_is_set((flags), pe_action_runnable)? "runnable" : "unrunnable")
#define action_node_str(a) \
(((a)->node == NULL)? "no node" : (a)->node->details->uname)
/*!
* \internal
* \brief Update an action's flags for all orderings where it is "then"
*
* \param[in] then Action to update
* \param[in] data_set Cluster working set
*/
void
pcmk__update_action_for_orderings(pe_action_t *then, pe_working_set_t *data_set)
{
GList *lpc = NULL;
uint32_t changed = pcmk__updated_none;
int last_flags = then->flags;
pe_rsc_trace(then->rsc, "Updating %s %s (%s %s) on %s",
action_type_str(then->flags), then->uuid,
action_optional_str(then->flags),
action_runnable_str(then->flags), action_node_str(then));
if (pcmk_is_set(then->flags, pe_action_requires_any)) {
/* Initialize current known "runnable before" actions. As
* update_action_for_ordering_flags() is called for each of then's
* before actions, this number will increment as runnable 'first'
* actions are encountered.
*/
then->runnable_before = 0;
if (then->required_runnable_before == 0) {
/* @COMPAT This ordering constraint uses the deprecated
* "require-all=false" attribute. Treat it like "clone-min=1".
*/
then->required_runnable_before = 1;
}
/* The pe_order_one_or_more clause of update_action_for_ordering_flags()
* (called below) will reset runnable if appropriate.
*/
pe__clear_action_flags(then, pe_action_runnable);
}
for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data;
pe_action_t *first = other->action;
pe_node_t *then_node = then->node;
pe_node_t *first_node = first->node;
if ((first->rsc != NULL)
&& (first->rsc->variant == pe_group)
&& pcmk__str_eq(first->task, RSC_START, pcmk__str_casei)) {
first_node = first->rsc->fns->location(first->rsc, NULL, FALSE);
if (first_node != NULL) {
pe_rsc_trace(first->rsc, "Found %s for 'first' %s",
pe__node_name(first_node), first->uuid);
}
}
if ((then->rsc != NULL)
&& (then->rsc->variant == pe_group)
&& pcmk__str_eq(then->task, RSC_START, pcmk__str_casei)) {
then_node = then->rsc->fns->location(then->rsc, NULL, FALSE);
if (then_node != NULL) {
pe_rsc_trace(then->rsc, "Found %s for 'then' %s",
pe__node_name(then_node), then->uuid);
}
}
// Disable constraint if it only applies when on same node, but isn't
if (pcmk_is_set(other->type, pe_order_same_node)
&& (first_node != NULL) && (then_node != NULL)
&& (first_node->details != then_node->details)) {
pe_rsc_trace(then->rsc,
"Disabled ordering %s on %s then %s on %s: not same node",
other->action->uuid, pe__node_name(first_node),
then->uuid, pe__node_name(then_node));
other->type = pe_order_none;
continue;
}
pcmk__clear_updated_flags(changed, then, pcmk__updated_first);
if ((first->rsc != NULL)
&& pcmk_is_set(other->type, pe_order_then_cancels_first)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
/* 'then' is required, so we must abandon 'first'
* (e.g. a required stop cancels any agent reload).
*/
pe__set_action_flags(other->action, pe_action_optional);
if (!strcmp(first->task, CRMD_ACTION_RELOAD_AGENT)) {
pe__clear_resource_flags(first->rsc, pe_rsc_reload);
}
}
if ((first->rsc != NULL) && (then->rsc != NULL)
&& (first->rsc != then->rsc) && !is_parent(then->rsc, first->rsc)) {
first = action_for_ordering(first);
}
if (first != other->action) {
pe_rsc_trace(then->rsc, "Ordering %s after %s instead of %s",
then->uuid, first->uuid, other->action->uuid);
}
pe_rsc_trace(then->rsc,
"%s (%#.6x) then %s (%#.6x): type=%#.6x node=%s",
first->uuid, first->flags, then->uuid, then->flags,
other->type, action_node_str(first));
if (first == other->action) {
/* 'first' was not remapped (e.g. from 'start' to 'running'), which
* could mean it is a non-resource action, a primitive resource
* action, or already expanded.
*/
enum pe_action_flags first_flags, then_flags;
first_flags = action_flags_for_ordering(first, then_node);
then_flags = action_flags_for_ordering(then, first_node);
changed |= update_action_for_ordering_flags(first, then,
first_flags, then_flags,
other, data_set);
/* 'first' was for a complex resource (clone, group, etc),
* create a new dependency if necessary
*/
} else if (order_actions(first, then, other->type)) {
/* This was the first time 'first' and 'then' were associated,
* start again to get the new actions_before list
*/
pcmk__set_updated_flags(changed, then, pcmk__updated_then);
pe_rsc_trace(then->rsc,
"Disabled ordering %s then %s in favor of %s then %s",
other->action->uuid, then->uuid, first->uuid,
then->uuid);
other->type = pe_order_none;
}
if (pcmk_is_set(changed, pcmk__updated_first)) {
crm_trace("Re-processing %s and its 'after' actions "
"because it changed", first->uuid);
for (GList *lpc2 = first->actions_after; lpc2 != NULL;
lpc2 = lpc2->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc2->data;
pcmk__update_action_for_orderings(other->action, data_set);
}
pcmk__update_action_for_orderings(first, data_set);
}
}
if (pcmk_is_set(then->flags, pe_action_requires_any)) {
if (last_flags == then->flags) {
pcmk__clear_updated_flags(changed, then, pcmk__updated_then);
} else {
pcmk__set_updated_flags(changed, then, pcmk__updated_then);
}
}
if (pcmk_is_set(changed, pcmk__updated_then)) {
crm_trace("Re-processing %s and its 'after' actions because it changed",
then->uuid);
if (pcmk_is_set(last_flags, pe_action_runnable)
&& !pcmk_is_set(then->flags, pe_action_runnable)) {
- pcmk__block_colocated_starts(then, data_set);
+ pcmk__block_colocation_dependents(then, data_set);
}
pcmk__update_action_for_orderings(then, data_set);
for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data;
pcmk__update_action_for_orderings(other->action, data_set);
}
}
}
static inline bool
is_primitive_action(pe_action_t *action)
{
return action && action->rsc && (action->rsc->variant == pe_native);
}
/*!
* \internal
* \brief Clear a single action flag and set reason text
*
* \param[in] action Action whose flag should be cleared
* \param[in] flag Action flag that should be cleared
* \param[in] reason Action that is the reason why flag is being cleared
*/
#define clear_action_flag_because(action, flag, reason) do { \
if (pcmk_is_set((action)->flags, (flag))) { \
pe__clear_action_flags(action, flag); \
if ((action)->rsc != (reason)->rsc) { \
char *reason_text = pe__action2reason((reason), (flag)); \
pe_action_set_reason((action), reason_text, \
((flag) == pe_action_migrate_runnable)); \
free(reason_text); \
} \
} \
} while (0)
/*!
* \internal
* \brief Update actions in an asymmetric ordering
*
* \param[in] first 'First' action in an asymmetric ordering
* \param[in] then 'Then' action in an asymmetric ordering
*/
static void
handle_asymmetric_ordering(pe_action_t *first, pe_action_t *then)
{
enum rsc_role_e then_rsc_role = RSC_ROLE_UNKNOWN;
GList *then_on = NULL;
if (then->rsc == NULL) {
// Asymmetric orderings only matter if there's a resource involved
return;
}
then_rsc_role = then->rsc->fns->state(then->rsc, TRUE);
then_on = then->rsc->running_on;
if ((then_rsc_role == RSC_ROLE_STOPPED)
&& pcmk__str_eq(then->task, RSC_STOP, pcmk__str_none)) {
/* Nothing needs to be done for asymmetric ordering if 'then' is
* supposed to be stopped after 'first' but is already stopped.
*/
return;
}
if ((then_rsc_role >= RSC_ROLE_STARTED)
&& pcmk_is_set(then->flags, pe_action_optional)
&& (then->node != NULL)
&& pcmk__list_of_1(then_on)
&& (then->node->details == ((pe_node_t *) then_on->data)->details)
&& pcmk__str_eq(then->task, RSC_START, pcmk__str_none)) {
/* Nothing needs to be done for asymmetric ordering if 'then' is
* supposed to be started after 'first' but is already started --
* unless the start is mandatory, which indicates the resource is
* restarting and the ordering is still needed.
*/
return;
}
if (!pcmk_is_set(first->flags, pe_action_runnable)) {
// 'First' can't run, so 'then' can't either
clear_action_flag_because(then, pe_action_optional, first);
clear_action_flag_because(then, pe_action_runnable, first);
}
}
/*!
* \internal
* \brief Set action bits appropriately when pe_restart_order is used
*
* \param[in] first 'First' action in an ordering with pe_restart_order
* \param[in] then 'Then' action in an ordering with pe_restart_order
* \param[in] filter What action flags to care about
*
* \note pe_restart_order is set for "stop resource before starting it" and
* "stop later group member before stopping earlier group member"
*/
static void
handle_restart_ordering(pe_action_t *first, pe_action_t *then, uint32_t filter)
{
const char *reason = NULL;
CRM_ASSERT(is_primitive_action(first));
CRM_ASSERT(is_primitive_action(then));
// We need to update the action in two cases:
// ... if 'then' is required
if (pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
reason = "restart";
}
/* ... if 'then' is unrunnable action on same resource (if a resource
* should restart but can't start, we still want to stop)
*/
if (pcmk_is_set(filter, pe_action_runnable)
&& !pcmk_is_set(then->flags, pe_action_runnable)
&& pcmk_is_set(then->rsc->flags, pe_rsc_managed)
&& (first->rsc == then->rsc)) {
reason = "stop";
}
if (reason == NULL) {
return;
}
pe_rsc_trace(first->rsc, "Handling %s -> %s for %s",
first->uuid, then->uuid, reason);
// Make 'first' required if it is runnable
if (pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(first, pe_action_optional, then);
}
// Make 'first' required if 'then' is required
if (!pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
// Make 'first' unmigratable if 'then' is unmigratable
if (!pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable, then);
}
// Make 'then' unrunnable if 'first' is required but unrunnable
if (!pcmk_is_set(first->flags, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_runnable, first);
}
}
/*!
* \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. In some cases, the ordering could be disabled 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
* (ignored)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pe_action_optional to affect only mandatory
* actions, and pe_action_runnable to affect only
* runnable actions)
* \param[in] type Group of enum pe_ordering flags to apply
* \param[in,out] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then,
const pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set)
{
uint32_t changed = pcmk__updated_none;
uint32_t then_flags = then->flags;
uint32_t first_flags = first->flags;
if (pcmk_is_set(type, pe_order_asymmetrical)) {
handle_asymmetric_ordering(first, then);
}
if (pcmk_is_set(type, pe_order_implies_first)
&& !pcmk_is_set(then_flags, pe_action_optional)) {
// Then is required, and implies first should be, too
if (pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(flags, pe_action_optional)
&& pcmk_is_set(first_flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
if (pcmk_is_set(flags, pe_action_migrate_runnable)
&& !pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable, then);
}
}
if (pcmk_is_set(type, pe_order_promoted_implies_first)
&& (then->rsc != NULL) && (then->rsc->role == RSC_ROLE_PROMOTED)
&& pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
if (pcmk_is_set(first->flags, pe_action_migrate_runnable)
&& !pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable,
then);
}
}
if (pcmk_is_set(type, pe_order_implies_first_migratable)
&& pcmk_is_set(filter, pe_action_optional)) {
if (!pcmk_all_flags_set(then->flags,
pe_action_migrate_runnable|pe_action_runnable)) {
clear_action_flag_because(first, pe_action_runnable, then);
}
if (!pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
}
if (pcmk_is_set(type, pe_order_pseudo_left)
&& pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_migrate_runnable, first);
pe__clear_action_flags(then, pe_action_pseudo);
}
if (pcmk_is_set(type, pe_order_runnable_left)
&& pcmk_is_set(filter, pe_action_runnable)
&& pcmk_is_set(then->flags, pe_action_runnable)
&& !pcmk_is_set(flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_runnable, first);
clear_action_flag_because(then, pe_action_migrate_runnable, first);
}
if (pcmk_is_set(type, pe_order_implies_then)
&& pcmk_is_set(filter, pe_action_optional)
&& pcmk_is_set(then->flags, pe_action_optional)
&& !pcmk_is_set(flags, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(then, pe_action_optional, first);
}
if (pcmk_is_set(type, pe_order_restart)) {
handle_restart_ordering(first, then, filter);
}
if (then_flags != then->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
pe_rsc_trace(then->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'first' %s (%#.6x)",
then->uuid, pe__node_name(then->node),
then->flags, then_flags, first->uuid, first->flags);
if ((then->rsc != NULL) && (then->rsc->parent != NULL)) {
// Required to handle "X_stop then X_start" for cloned groups
pcmk__update_action_for_orderings(then, data_set);
}
}
if (first_flags != first->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
pe_rsc_trace(first->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'then' %s (%#.6x)",
first->uuid, pe__node_name(first->node),
first->flags, first_flags, then->uuid, then->flags);
}
return changed;
}
/*!
* \internal
* \brief Trace-log an action (optionally with its dependent actions)
*
* \param[in] pre_text If not NULL, prefix the log with this plus ": "
* \param[in] action Action to log
* \param[in] details If true, recursively log dependent actions
*/
void
pcmk__log_action(const char *pre_text, pe_action_t *action, bool details)
{
const char *node_uname = NULL;
const char *node_uuid = NULL;
const char *desc = NULL;
CRM_CHECK(action != NULL, return);
if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
if (action->node != NULL) {
node_uname = action->node->details->uname;
node_uuid = action->node->details->id;
} else {
node_uname = "";
}
}
switch (text2task(action->task)) {
case stonith_node:
case shutdown_crm:
if (pcmk_is_set(action->flags, pe_action_pseudo)) {
desc = "Pseudo ";
} else if (pcmk_is_set(action->flags, pe_action_optional)) {
desc = "Optional ";
} else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
desc = "!!Non-Startable!! ";
} else if (pcmk_is_set(action->flags, pe_action_processed)) {
desc = "";
} else {
desc = "(Provisional) ";
}
crm_trace("%s%s%sAction %d: %s%s%s%s%s%s",
((pre_text == NULL)? "" : pre_text),
((pre_text == NULL)? "" : ": "),
desc, action->id, action->uuid,
(node_uname? "\ton " : ""), (node_uname? node_uname : ""),
(node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""),
(node_uuid? ")" : ""));
break;
default:
if (pcmk_is_set(action->flags, pe_action_optional)) {
desc = "Optional ";
} else if (pcmk_is_set(action->flags, pe_action_pseudo)) {
desc = "Pseudo ";
} else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
desc = "!!Non-Startable!! ";
} else if (pcmk_is_set(action->flags, pe_action_processed)) {
desc = "";
} else {
desc = "(Provisional) ";
}
crm_trace("%s%s%sAction %d: %s %s%s%s%s%s%s",
((pre_text == NULL)? "" : pre_text),
((pre_text == NULL)? "" : ": "),
desc, action->id, action->uuid,
(action->rsc? action->rsc->id : ""),
(node_uname? "\ton " : ""), (node_uname? node_uname : ""),
(node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""),
(node_uuid? ")" : ""));
break;
}
if (details) {
GList *iter = NULL;
crm_trace("\t\t====== Preceding Actions");
for (iter = action->actions_before; iter != NULL; iter = iter->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) iter->data;
pcmk__log_action("\t\t", other->action, false);
}
crm_trace("\t\t====== Subsequent Actions");
for (iter = action->actions_after; iter != NULL; iter = iter->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) iter->data;
pcmk__log_action("\t\t", other->action, false);
}
crm_trace("\t\t====== End");
} else {
crm_trace("\t\t(before=%d, after=%d)",
g_list_length(action->actions_before),
g_list_length(action->actions_after));
}
}
/*!
* \internal
* \brief Create a new shutdown action for a node
*
* \param[in] node Node being shut down
*
* \return Newly created shutdown action for \p node
*/
pe_action_t *
pcmk__new_shutdown_action(pe_node_t *node)
{
char *shutdown_id = NULL;
pe_action_t *shutdown_op = NULL;
CRM_ASSERT(node != NULL);
shutdown_id = crm_strdup_printf("%s-%s", CRM_OP_SHUTDOWN,
node->details->uname);
shutdown_op = custom_action(NULL, shutdown_id, CRM_OP_SHUTDOWN, node, FALSE,
TRUE, node->details->data_set);
pcmk__order_stops_before_shutdown(node, shutdown_op);
add_hash_param(shutdown_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
return shutdown_op;
}
/*!
* \internal
* \brief Calculate and add an operation digest to XML
*
* Calculate an operation digest, which enables us to later determine when a
* restart is needed due to the resource's parameters being changed, and add it
* to given XML.
*
* \param[in] op Operation result from executor
* \param[in] update XML to add digest to
*/
static void
add_op_digest_to_xml(lrmd_event_data_t *op, xmlNode *update)
{
char *digest = NULL;
xmlNode *args_xml = NULL;
if (op->params == NULL) {
return;
}
args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
g_hash_table_foreach(op->params, hash2field, args_xml);
pcmk__filter_op_for_digest(args_xml);
digest = calculate_operation_digest(args_xml, NULL);
crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
free_xml(args_xml);
free(digest);
}
#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
/*!
* \internal
* \brief Create XML for resource operation history update
*
* \param[in,out] parent Parent XML node to add to
* \param[in,out] op Operation event data
* \param[in] caller_version DC feature set
* \param[in] target_rc Expected result of operation
* \param[in] node Name of node on which operation was performed
* \param[in] origin Arbitrary description of update source
*
* \return Newly created XML node for history update
*/
xmlNode *
pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *op,
const char *caller_version, int target_rc,
const char *node, const char *origin)
{
char *key = NULL;
char *magic = NULL;
char *op_id = NULL;
char *op_id_additional = NULL;
char *local_user_data = NULL;
const char *exit_reason = NULL;
xmlNode *xml_op = NULL;
const char *task = NULL;
CRM_CHECK(op != NULL, return NULL);
crm_trace("Creating history XML for %s-interval %s action for %s on %s "
"(DC version: %s, origin: %s)",
pcmk__readable_interval(op->interval_ms), op->op_type, op->rsc_id,
((node == NULL)? "no node" : node), caller_version, origin);
task = op->op_type;
/* Record a successful agent reload as a start, and a failed one as a
* monitor, to make life easier for the scheduler when determining the
* current state.
*
* @COMPAT We should check "reload" here only if the operation was for a
* pre-OCF-1.1 resource agent, but we don't know that here, and we should
* only ever get results for actions scheduled by us, so we can reasonably
* assume any "reload" is actually a pre-1.1 agent reload.
*/
if (pcmk__str_any_of(task, CRMD_ACTION_RELOAD, CRMD_ACTION_RELOAD_AGENT,
NULL)) {
if (op->op_status == PCMK_EXEC_DONE) {
task = CRMD_ACTION_START;
} else {
task = CRMD_ACTION_STATUS;
}
}
key = pcmk__op_key(op->rsc_id, task, op->interval_ms);
if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_none)) {
const char *n_type = crm_meta_value(op->params, "notify_type");
const char *n_task = crm_meta_value(op->params, "notify_operation");
CRM_LOG_ASSERT(n_type != NULL);
CRM_LOG_ASSERT(n_task != NULL);
op_id = pcmk__notify_key(op->rsc_id, n_type, n_task);
if (op->op_status != PCMK_EXEC_PENDING) {
/* Ignore notify errors.
*
* @TODO It might be better to keep the correct result here, and
* ignore it in process_graph_event().
*/
lrmd__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
}
/* Migration history is preserved separately, which usually matters for
* multiple nodes and is important for future cluster transitions.
*/
} else if (pcmk__str_any_of(op->op_type, CRMD_ACTION_MIGRATE,
CRMD_ACTION_MIGRATED, NULL)) {
op_id = strdup(key);
} else if (did_rsc_op_fail(op, target_rc)) {
op_id = pcmk__op_key(op->rsc_id, "last_failure", 0);
if (op->interval_ms == 0) {
// Ensure 'last' gets updated, in case record-pending is true
op_id_additional = pcmk__op_key(op->rsc_id, "last", 0);
}
exit_reason = op->exit_reason;
} else if (op->interval_ms > 0) {
op_id = strdup(key);
} else {
op_id = pcmk__op_key(op->rsc_id, "last", 0);
}
again:
xml_op = pcmk__xe_match(parent, XML_LRM_TAG_RSC_OP, XML_ATTR_ID, op_id);
if (xml_op == NULL) {
xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
}
if (op->user_data == NULL) {
crm_debug("Generating fake transition key for: " PCMK__OP_FMT
" %d from %s", op->rsc_id, op->op_type, op->interval_ms,
op->call_id, origin);
local_user_data = pcmk__transition_key(-1, op->call_id, target_rc,
FAKE_TE_ID);
op->user_data = local_user_data;
}
if (magic == NULL) {
magic = crm_strdup_printf("%d:%d;%s", op->op_status, op->rc,
(const char *) op->user_data);
}
crm_xml_add(xml_op, XML_ATTR_ID, op_id);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
crm_xml_add(xml_op, XML_LRM_ATTR_EXIT_REASON, exit_reason == NULL ? "" : exit_reason);
crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, node); /* For context during triage */
crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
crm_xml_add_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, op->interval_ms);
if (compare_version("2.1", caller_version) <= 0) {
if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
crm_trace("Timing data (" PCMK__OP_FMT
"): last=%u change=%u exec=%u queue=%u",
op->rsc_id, op->op_type, op->interval_ms,
op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
if ((op->interval_ms != 0) && (op->t_rcchange != 0)) {
// Recurring ops may have changed rc after initial run
crm_xml_add_ll(xml_op, XML_RSC_OP_LAST_CHANGE,
(long long) op->t_rcchange);
} else {
crm_xml_add_ll(xml_op, XML_RSC_OP_LAST_CHANGE,
(long long) op->t_run);
}
crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
}
}
if (pcmk__str_any_of(op->op_type, CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, NULL)) {
/*
* Record migrate_source and migrate_target always for migrate ops.
*/
const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
name = XML_LRM_ATTR_MIGRATE_TARGET;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
}
add_op_digest_to_xml(op, xml_op);
if (op_id_additional) {
free(op_id);
op_id = op_id_additional;
op_id_additional = NULL;
goto again;
}
if (local_user_data) {
free(local_user_data);
op->user_data = NULL;
}
free(magic);
free(op_id);
free(key);
return xml_op;
}
/*!
* \internal
* \brief Check whether an action shutdown-locks a resource to a node
*
* If the shutdown-lock cluster property is set, resources will not be recovered
* on a different node if cleanly stopped, and may start only on that same node.
* This function checks whether that applies to a given action, so that the
* transition graph can be marked appropriately.
*
* \param[in] action Action to check
*
* \return true if \p action locks its resource to the action's node,
* otherwise false
*/
bool
pcmk__action_locks_rsc_to_node(const pe_action_t *action)
{
// Only resource actions taking place on resource's lock node are locked
if ((action == NULL) || (action->rsc == NULL)
|| (action->rsc->lock_node == NULL) || (action->node == NULL)
|| (action->node->details != action->rsc->lock_node->details)) {
return false;
}
/* During shutdown, only stops are locked (otherwise, another action such as
* a demote would cause the controller to clear the lock)
*/
if (action->node->details->shutdown && (action->task != NULL)
&& (strcmp(action->task, RSC_STOP) != 0)) {
return false;
}
return true;
}
/* lowest to highest */
static gint
sort_action_id(gconstpointer a, gconstpointer b)
{
const pe_action_wrapper_t *action_wrapper2 = (const pe_action_wrapper_t *)a;
const pe_action_wrapper_t *action_wrapper1 = (const pe_action_wrapper_t *)b;
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (action_wrapper1->action->id < action_wrapper2->action->id) {
return 1;
}
if (action_wrapper1->action->id > action_wrapper2->action->id) {
return -1;
}
return 0;
}
/*!
* \internal
* \brief Remove any duplicate action inputs, merging action flags
*
* \param[in] action Action whose inputs should be checked
*/
void
pcmk__deduplicate_action_inputs(pe_action_t *action)
{
GList *item = NULL;
GList *next = NULL;
pe_action_wrapper_t *last_input = NULL;
action->actions_before = g_list_sort(action->actions_before,
sort_action_id);
for (item = action->actions_before; item != NULL; item = next) {
pe_action_wrapper_t *input = (pe_action_wrapper_t *) item->data;
next = item->next;
if ((last_input != NULL)
&& (input->action->id == last_input->action->id)) {
crm_trace("Input %s (%d) duplicate skipped for action %s (%d)",
input->action->uuid, input->action->id,
action->uuid, action->id);
/* For the purposes of scheduling, the ordering flags no longer
* matter, but crm_simulate looks at certain ones when creating a
* dot graph. Combining the flags is sufficient for that purpose.
*/
last_input->type |= input->type;
if (input->state == pe_link_dumped) {
last_input->state = pe_link_dumped;
}
free(item->data);
action->actions_before = g_list_delete_link(action->actions_before,
item);
} else {
last_input = input;
input->state = pe_link_not_dumped;
}
}
}
/*!
* \internal
* \brief Output all scheduled actions
*
* \param[in] data_set Cluster working set
*/
void
pcmk__output_actions(pe_working_set_t *data_set)
{
pcmk__output_t *out = data_set->priv;
// Output node (non-resource) actions
for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
char *node_name = NULL;
char *task = NULL;
pe_action_t *action = (pe_action_t *) iter->data;
if (action->rsc != NULL) {
continue; // Resource actions will be output later
} else if (pcmk_is_set(action->flags, pe_action_optional)) {
continue; // This action was not scheduled
}
if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
task = strdup("Shutdown");
} else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
const char *op = g_hash_table_lookup(action->meta, "stonith_action");
task = crm_strdup_printf("Fence (%s)", op);
} else {
continue; // Don't display other node action types
}
if (pe__is_guest_node(action->node)) {
node_name = crm_strdup_printf("%s (resource: %s)",
pe__node_name(action->node),
action->node->details->remote_rsc->container->id);
} else if (action->node != NULL) {
node_name = crm_strdup_printf("%s", pe__node_name(action->node));
}
out->message(out, "node-action", task, node_name, action->reason);
free(node_name);
free(task);
}
// Output resource actions
for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
rsc->cmds->output_actions(rsc);
}
}
/*!
* \internal
* \brief Check whether action from resource history is still in configuration
*
* \param[in] rsc Resource that action is for
* \param[in] task Action's name
* \param[in] interval_ms Action's interval (in milliseconds)
*
* \return true if action is still in resource configuration, otherwise false
*/
static bool
action_in_config(pe_resource_t *rsc, const char *task, guint interval_ms)
{
char *key = pcmk__op_key(rsc->id, task, interval_ms);
bool config = (find_rsc_op_entry(rsc, key) != NULL);
free(key);
return config;
}
/*!
* \internal
* \brief Get action name needed to compare digest for configuration changes
*
* \param[in] task Action name from history
* \param[in] interval_ms Action interval (in milliseconds)
*
* \return Action name whose digest should be compared
*/
static const char *
task_for_digest(const char *task, guint interval_ms)
{
/* Certain actions need to be compared against the parameters used to start
* the resource.
*/
if ((interval_ms == 0)
&& pcmk__str_any_of(task, RSC_STATUS, RSC_MIGRATED, RSC_PROMOTE, NULL)) {
task = RSC_START;
}
return task;
}
/*!
* \internal
* \brief Check whether only sanitized parameters to an action changed
*
* When collecting CIB files for troubleshooting, crm_report will mask
* sensitive resource parameters. If simulations were run using that, affected
* resources would appear to need a restart, which would complicate
* troubleshooting. To avoid that, we save a "secure digest" of non-sensitive
* parameters. This function used that digest to check whether only masked
* parameters are different.
*
* \param[in] xml_op Resource history entry with secure digest
* \param[in] digest_data Operation digest information being compared
* \param[in] data_set Cluster working set
*
* \return true if only sanitized parameters changed, otherwise false
*/
static bool
only_sanitized_changed(xmlNode *xml_op, const op_digest_cache_t *digest_data,
pe_working_set_t *data_set)
{
const char *digest_secure = NULL;
if (!pcmk_is_set(data_set->flags, pe_flag_sanitized)) {
// The scheduler is not being run as a simulation
return false;
}
digest_secure = crm_element_value(xml_op, XML_LRM_ATTR_SECURE_DIGEST);
return (digest_data->rc != RSC_DIGEST_MATCH) && (digest_secure != NULL)
&& (digest_data->digest_secure_calc != NULL)
&& (strcmp(digest_data->digest_secure_calc, digest_secure) == 0);
}
/*!
* \internal
* \brief Force a restart due to a configuration change
*
* \param[in] rsc Resource that action is for
* \param[in] task Name of action whose configuration changed
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] node Node where resource should be restarted
*/
static void
force_restart(pe_resource_t *rsc, const char *task, guint interval_ms,
pe_node_t *node)
{
char *key = pcmk__op_key(rsc->id, task, interval_ms);
pe_action_t *required = custom_action(rsc, key, task, NULL, FALSE, TRUE,
rsc->cluster);
pe_action_set_reason(required, "resource definition change", true);
trigger_unfencing(rsc, node, "Device parameters changed", NULL,
rsc->cluster);
}
/*!
* \internal
* \brief Schedule a reload of a resource on a node
*
* \param[in] rsc Resource to reload
* \param[in] node Where resource should be reloaded
*/
static void
schedule_reload(pe_resource_t *rsc, pe_node_t *node)
{
pe_action_t *reload = NULL;
// For collective resources, just call recursively for children
if (rsc->variant > pe_native) {
g_list_foreach(rsc->children, (GFunc) schedule_reload, node);
return;
}
// Skip the reload in certain situations
if ((node == NULL)
|| !pcmk_is_set(rsc->flags, pe_rsc_managed)
|| pcmk_is_set(rsc->flags, pe_rsc_failed)) {
pe_rsc_trace(rsc, "Skip reload of %s:%s%s %s",
rsc->id,
pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : " unmanaged",
pcmk_is_set(rsc->flags, pe_rsc_failed)? " failed" : "",
(node == NULL)? "inactive" : node->details->uname);
return;
}
/* If a resource's configuration changed while a start was pending,
* force a full restart instead of a reload.
*/
if (pcmk_is_set(rsc->flags, pe_rsc_start_pending)) {
pe_rsc_trace(rsc, "%s: preventing agent reload because start pending",
rsc->id);
custom_action(rsc, stop_key(rsc), CRMD_ACTION_STOP, node, FALSE, TRUE,
rsc->cluster);
return;
}
// Schedule the reload
pe__set_resource_flags(rsc, pe_rsc_reload);
reload = custom_action(rsc, reload_key(rsc), CRMD_ACTION_RELOAD_AGENT, node,
FALSE, TRUE, rsc->cluster);
pe_action_set_reason(reload, "resource definition change", FALSE);
// Set orderings so that a required stop or demote cancels the reload
pcmk__new_ordering(NULL, NULL, reload, rsc, stop_key(rsc), NULL,
pe_order_optional|pe_order_then_cancels_first,
rsc->cluster);
pcmk__new_ordering(NULL, NULL, reload, rsc, demote_key(rsc), NULL,
pe_order_optional|pe_order_then_cancels_first,
rsc->cluster);
}
/*!
* \internal
* \brief Handle any configuration change for an action
*
* Given an action from resource history, if the resource's configuration
* changed since the action was done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, etc.).
*
* \param[in] rsc Resource that action is for
* \param[in] node Node that action was on
* \param[in] xml_op Action XML from resource history
*
* \return true if action configuration changed, otherwise false
*/
bool
pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op)
{
guint interval_ms = 0;
const char *task = NULL;
const op_digest_cache_t *digest_data = NULL;
CRM_CHECK((rsc != NULL) && (node != NULL) && (xml_op != NULL),
return false);
task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
CRM_CHECK(task != NULL, return false);
crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
// If this is a recurring action, check whether it has been orphaned
if (interval_ms > 0) {
if (action_in_config(rsc, task, interval_ms)) {
pe_rsc_trace(rsc, "%s-interval %s for %s on %s is in configuration",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
} else if (pcmk_is_set(rsc->cluster->flags,
pe_flag_stop_action_orphans)) {
pcmk__schedule_cancel(rsc,
crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
task, interval_ms, node, "orphan");
return true;
} else {
pe_rsc_debug(rsc, "%s-interval %s for %s on %s is orphaned",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
return true;
}
}
crm_trace("Checking %s-interval %s for %s on %s for configuration changes",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
task = task_for_digest(task, interval_ms);
digest_data = rsc_action_digest_cmp(rsc, xml_op, node, rsc->cluster);
if (only_sanitized_changed(xml_op, digest_data, rsc->cluster)) {
if (!pcmk__is_daemon && (rsc->cluster->priv != NULL)) {
pcmk__output_t *out = rsc->cluster->priv;
out->info(out,
"Only 'private' parameters to %s-interval %s for %s "
"on %s changed: %s",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node),
crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
}
return false;
}
switch (digest_data->rc) {
case RSC_DIGEST_RESTART:
crm_log_xml_debug(digest_data->params_restart, "params:restart");
force_restart(rsc, task, interval_ms, node);
return true;
case RSC_DIGEST_ALL:
case RSC_DIGEST_UNKNOWN:
// Changes that can potentially be handled by an agent reload
if (interval_ms > 0) {
/* Recurring actions aren't reloaded per se, they are just
* re-scheduled so the next run uses the new parameters.
* The old instance will be cancelled automatically.
*/
crm_log_xml_debug(digest_data->params_all, "params:reschedule");
pcmk__reschedule_recurring(rsc, task, interval_ms, node);
} else if (crm_element_value(xml_op,
XML_LRM_ATTR_RESTART_DIGEST) != NULL) {
// Agent supports reload, so use it
trigger_unfencing(rsc, node,
"Device parameters changed (reload)", NULL,
rsc->cluster);
crm_log_xml_debug(digest_data->params_all, "params:reload");
schedule_reload(rsc, node);
} else {
pe_rsc_trace(rsc,
"Restarting %s because agent doesn't support reload",
rsc->id);
crm_log_xml_debug(digest_data->params_restart,
"params:restart");
force_restart(rsc, task, interval_ms, node);
}
return true;
default:
break;
}
return false;
}
/*!
* \internal
* \brief Create a list of resource's action history entries, sorted by call ID
*
* \param[in] rsc Resource whose history should be checked
* \param[in] rsc_entry Resource's status XML
* \param[out] start_index Where to store index of start-like action, if any
* \param[out] stop_index Where to store index of stop action, if any
*/
static GList *
rsc_history_as_list(pe_resource_t *rsc, xmlNode *rsc_entry,
int *start_index, int *stop_index)
{
GList *ops = NULL;
for (xmlNode *rsc_op = first_named_child(rsc_entry, XML_LRM_TAG_RSC_OP);
rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) {
ops = g_list_prepend(ops, rsc_op);
}
ops = g_list_sort(ops, sort_op_by_callid);
calculate_active_ops(ops, start_index, stop_index);
return ops;
}
/*!
* \internal
* \brief Process a resource's action history from the CIB status
*
* Given a resource's action history, if the resource's configuration
* changed since the actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] rsc_entry Resource's status XML
* \param[in] rsc Resource whose history is being processed
* \param[in] node Node whose history is being processed
*/
static void
process_rsc_history(xmlNode *rsc_entry, pe_resource_t *rsc, pe_node_t *node)
{
int offset = -1;
int stop_index = 0;
int start_index = 0;
GList *sorted_op_list = NULL;
if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
if (pe_rsc_is_anon_clone(uber_parent(rsc))) {
pe_rsc_trace(rsc,
"Skipping configuration check "
"for orphaned clone instance %s",
rsc->id);
} else {
pe_rsc_trace(rsc,
"Skipping configuration check and scheduling clean-up "
"for orphaned resource %s", rsc->id);
pcmk__schedule_cleanup(rsc, node, false);
}
return;
}
if (pe_find_node_id(rsc->running_on, node->details->id) == NULL) {
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, false)) {
pcmk__schedule_cleanup(rsc, node, false);
}
pe_rsc_trace(rsc,
"Skipping configuration check for %s "
"because no longer active on %s",
rsc->id, pe__node_name(node));
return;
}
pe_rsc_trace(rsc, "Checking for configuration changes for %s on %s",
rsc->id, pe__node_name(node));
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, true)) {
pcmk__schedule_cleanup(rsc, node, false);
}
sorted_op_list = rsc_history_as_list(rsc, rsc_entry, &start_index,
&stop_index);
if (start_index < stop_index) {
return; // Resource is stopped
}
for (GList *iter = sorted_op_list; iter != NULL; iter = iter->next) {
xmlNode *rsc_op = (xmlNode *) iter->data;
const char *task = NULL;
guint interval_ms = 0;
if (++offset < start_index) {
// Skip actions that happened before a start
continue;
}
task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
crm_element_value_ms(rsc_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
if ((interval_ms > 0)
&& (pcmk_is_set(rsc->flags, pe_rsc_maintenance)
|| node->details->maintenance)) {
// Maintenance mode cancels recurring operations
pcmk__schedule_cancel(rsc,
crm_element_value(rsc_op, XML_LRM_ATTR_CALLID),
task, interval_ms, node, "maintenance mode");
} else if ((interval_ms > 0)
|| pcmk__strcase_any_of(task, RSC_STATUS, RSC_START,
RSC_PROMOTE, RSC_MIGRATED, NULL)) {
/* If a resource operation failed, and the operation's definition
* has changed, clear any fail count so they can be retried fresh.
*/
if (pe__bundle_needs_remote_name(rsc, rsc->cluster)) {
/* We haven't allocated resources to nodes yet, so if the
* REMOTE_CONTAINER_HACK is used, we may calculate the digest
* based on the literal "#uname" value rather than the properly
* substituted value. That would mistakenly make the action
* definition appear to have been changed. Defer the check until
* later in this case.
*/
pe__add_param_check(rsc_op, rsc, node, pe_check_active,
rsc->cluster);
} else if (pcmk__check_action_config(rsc, node, rsc_op)
&& (pe_get_failcount(node, rsc, NULL, pe_fc_effective,
NULL, rsc->cluster) != 0)) {
pe__clear_failcount(rsc, node, "action definition changed",
rsc->cluster);
}
}
}
g_list_free(sorted_op_list);
}
/*!
* \internal
* \brief Process a node's action history from the CIB status
*
* Given a node's resource history, if the resource's configuration changed
* since the actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] node Node whose history is being processed
* \param[in] lrm_rscs Node's from CIB status XML
* \param[in] data_set Cluster working set
*/
static void
process_node_history(pe_node_t *node, xmlNode *lrm_rscs, pe_working_set_t *data_set)
{
crm_trace("Processing node history for %s", pe__node_name(node));
for (xmlNode *rsc_entry = first_named_child(lrm_rscs, XML_LRM_TAG_RESOURCE);
rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
if (xml_has_children(rsc_entry)) {
GList *result = pcmk__rscs_matching_id(ID(rsc_entry), data_set);
for (GList *iter = result; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
if (rsc->variant == pe_native) {
process_rsc_history(rsc_entry, rsc, node);
}
}
g_list_free(result);
}
}
}
// XPath to find a node's resource history
#define XPATH_NODE_HISTORY "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS \
"/" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']" \
"/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES
/*!
* \internal
* \brief Process any resource configuration changes in the CIB status
*
* Go through all nodes' resource history, and if a resource's configuration
* changed since its actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] data_set Cluster working set
*/
void
pcmk__handle_rsc_config_changes(pe_working_set_t *data_set)
{
crm_trace("Check resource and action configuration for changes");
/* Rather than iterate through the status section, iterate through the nodes
* and search for the appropriate status subsection for each. This skips
* orphaned nodes and lets us eliminate some cases before searching the XML.
*/
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
/* Don't bother checking actions for a node that can't run actions ...
* unless it's in maintenance mode, in which case we still need to
* cancel any existing recurring monitors.
*/
if (node->details->maintenance
|| pcmk__node_available(node, false, false)) {
char *xpath = NULL;
xmlNode *history = NULL;
xpath = crm_strdup_printf(XPATH_NODE_HISTORY, node->details->uname);
history = get_xpath_object(xpath, data_set->input, LOG_NEVER);
free(xpath);
process_node_history(node, history, data_set);
}
}
}
diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c
index a30c8184b8..ee7ecd76a5 100644
--- a/lib/pacemaker/pcmk_sched_colocation.c
+++ b/lib/pacemaker/pcmk_sched_colocation.c
@@ -1,1465 +1,1517 @@
/*
* Copyright 2004-2022 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 "crm/common/util.h"
#include "crm/common/xml_internal.h"
#include "crm/msg_xml.h"
#include "libpacemaker_private.h"
#define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do { \
__rsc = pcmk__find_constraint_resource(data_set->resources, __name); \
if (__rsc == NULL) { \
pcmk__config_err("%s: No resource found for %s", __set, __name); \
return; \
} \
} while(0)
// Used to temporarily mark a node as unusable
#define INFINITY_HACK (INFINITY * -100)
static gint
cmp_dependent_priority(gconstpointer a, gconstpointer b)
{
const pcmk__colocation_t *rsc_constraint1 = (const pcmk__colocation_t *) a;
const pcmk__colocation_t *rsc_constraint2 = (const pcmk__colocation_t *) b;
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
CRM_ASSERT(rsc_constraint1->dependent != NULL);
CRM_ASSERT(rsc_constraint1->primary != NULL);
if (rsc_constraint1->dependent->priority > rsc_constraint2->dependent->priority) {
return -1;
}
if (rsc_constraint1->dependent->priority < rsc_constraint2->dependent->priority) {
return 1;
}
/* Process clones before primitives and groups */
if (rsc_constraint1->dependent->variant > rsc_constraint2->dependent->variant) {
return -1;
}
if (rsc_constraint1->dependent->variant < rsc_constraint2->dependent->variant) {
return 1;
}
/* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
* clones (probably unnecessary, but avoids having to update regression
* tests)
*/
if (rsc_constraint1->dependent->variant == pe_clone) {
if (pcmk_is_set(rsc_constraint1->dependent->flags, pe_rsc_promotable)
&& !pcmk_is_set(rsc_constraint2->dependent->flags, pe_rsc_promotable)) {
return -1;
} else if (!pcmk_is_set(rsc_constraint1->dependent->flags, pe_rsc_promotable)
&& pcmk_is_set(rsc_constraint2->dependent->flags, pe_rsc_promotable)) {
return 1;
}
}
return strcmp(rsc_constraint1->dependent->id,
rsc_constraint2->dependent->id);
}
static gint
cmp_primary_priority(gconstpointer a, gconstpointer b)
{
const pcmk__colocation_t *rsc_constraint1 = (const pcmk__colocation_t *) a;
const pcmk__colocation_t *rsc_constraint2 = (const pcmk__colocation_t *) b;
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
CRM_ASSERT(rsc_constraint1->dependent != NULL);
CRM_ASSERT(rsc_constraint1->primary != NULL);
if (rsc_constraint1->primary->priority > rsc_constraint2->primary->priority) {
return -1;
}
if (rsc_constraint1->primary->priority < rsc_constraint2->primary->priority) {
return 1;
}
/* Process clones before primitives and groups */
if (rsc_constraint1->primary->variant > rsc_constraint2->primary->variant) {
return -1;
} else if (rsc_constraint1->primary->variant < rsc_constraint2->primary->variant) {
return 1;
}
/* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
* clones (probably unnecessary, but avoids having to update regression
* tests)
*/
if (rsc_constraint1->primary->variant == pe_clone) {
if (pcmk_is_set(rsc_constraint1->primary->flags, pe_rsc_promotable)
&& !pcmk_is_set(rsc_constraint2->primary->flags, pe_rsc_promotable)) {
return -1;
} else if (!pcmk_is_set(rsc_constraint1->primary->flags, pe_rsc_promotable)
&& pcmk_is_set(rsc_constraint2->primary->flags, pe_rsc_promotable)) {
return 1;
}
}
return strcmp(rsc_constraint1->primary->id, rsc_constraint2->primary->id);
}
/*!
* \internal
* \brief Add a "this with" colocation constraint to a resource
*
* \param[in,out] rsc Resource to add colocation to
* \param[in] colocation Colocation constraint to add to \p rsc
*/
void
pcmk__add_this_with(pe_resource_t *rsc, pcmk__colocation_t *colocation)
{
rsc->rsc_cons = g_list_insert_sorted(rsc->rsc_cons, colocation,
cmp_primary_priority);
}
/*!
* \internal
* \brief Add a "with this" colocation constraint to a resource
*
* \param[in,out] rsc Resource to add colocation to
* \param[in] colocation Colocation constraint to add to \p rsc
*/
void
pcmk__add_with_this(pe_resource_t *rsc, pcmk__colocation_t *colocation)
{
rsc->rsc_cons_lhs = g_list_insert_sorted(rsc->rsc_cons_lhs, colocation,
cmp_dependent_priority);
}
/*!
* \internal
* \brief Add orderings necessary for an anti-colocation constraint
*/
static void
anti_colocation_order(pe_resource_t *first_rsc, int first_role,
pe_resource_t *then_rsc, int then_role,
pe_working_set_t *data_set)
{
const char *first_tasks[] = { NULL, NULL };
const char *then_tasks[] = { NULL, NULL };
/* Actions to make first_rsc lose first_role */
if (first_role == RSC_ROLE_PROMOTED) {
first_tasks[0] = CRMD_ACTION_DEMOTE;
} else {
first_tasks[0] = CRMD_ACTION_STOP;
if (first_role == RSC_ROLE_UNPROMOTED) {
first_tasks[1] = CRMD_ACTION_PROMOTE;
}
}
/* Actions to make then_rsc gain then_role */
if (then_role == RSC_ROLE_PROMOTED) {
then_tasks[0] = CRMD_ACTION_PROMOTE;
} else {
then_tasks[0] = CRMD_ACTION_START;
if (then_role == RSC_ROLE_UNPROMOTED) {
then_tasks[1] = CRMD_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],
pe_order_anti_colocation);
}
}
}
/*!
* \internal
* \brief Add a new colocation constraint to a cluster working set
*
* \param[in] id XML ID for this constraint
* \param[in] node_attr Colocate by this attribute (or NULL for #uname)
* \param[in] score Constraint score
* \param[in] dependent Resource to be colocated
* \param[in] 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] influence Whether colocation constraint has influence
* \param[in] data_set Cluster working set to add constraint to
*/
void
pcmk__new_colocation(const char *id, const char *node_attr, int score,
pe_resource_t *dependent, pe_resource_t *primary,
const char *dependent_role, const char *primary_role,
bool influence, pe_working_set_t *data_set)
{
pcmk__colocation_t *new_con = NULL;
if (score == 0) {
crm_trace("Ignoring colocation '%s' because score is 0", id);
return;
}
if ((dependent == NULL) || (primary == NULL)) {
pcmk__config_err("Ignoring colocation '%s' because resource "
"does not exist", id);
return;
}
new_con = calloc(1, sizeof(pcmk__colocation_t));
if (new_con == NULL) {
return;
}
if (pcmk__str_eq(dependent_role, RSC_ROLE_STARTED_S,
pcmk__str_null_matches|pcmk__str_casei)) {
dependent_role = RSC_ROLE_UNKNOWN_S;
}
if (pcmk__str_eq(primary_role, RSC_ROLE_STARTED_S,
pcmk__str_null_matches|pcmk__str_casei)) {
primary_role = RSC_ROLE_UNKNOWN_S;
}
new_con->id = id;
new_con->dependent = dependent;
new_con->primary = primary;
new_con->score = score;
new_con->dependent_role = text2role(dependent_role);
new_con->primary_role = text2role(primary_role);
new_con->node_attribute = node_attr;
new_con->influence = influence;
if (node_attr == NULL) {
node_attr = CRM_ATTR_UNAME;
}
pe_rsc_trace(dependent, "%s ==> %s (%s %d)",
dependent->id, primary->id, node_attr, score);
pcmk__add_this_with(dependent, new_con);
pcmk__add_with_this(primary, new_con);
data_set->colocation_constraints = g_list_append(data_set->colocation_constraints,
new_con);
if (score <= -INFINITY) {
anti_colocation_order(dependent, new_con->dependent_role, primary,
new_con->primary_role, data_set);
anti_colocation_order(primary, new_con->primary_role, dependent,
new_con->dependent_role, data_set);
}
}
/*!
* \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 influence option
*
* \return true if string evaluates true, false if string evaluates false,
* or value of resource's critical option if string is NULL or invalid
*/
static bool
unpack_influence(const char *coloc_id, const pe_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 "
XML_COLOC_ATTR_INFLUENCE " (using default)",
coloc_id);
} else {
return (influence_i != 0);
}
}
return pcmk_is_set(rsc->flags, pe_rsc_critical);
}
static void
unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
const char *influence_s, pe_working_set_t *data_set)
{
xmlNode *xml_rsc = NULL;
pe_resource_t *with = NULL;
pe_resource_t *resource = NULL;
const char *set_id = ID(set);
const char *role = crm_element_value(set, "role");
const char *ordering = crm_element_value(set, "ordering");
int local_score = score;
bool sequential = false;
const char *score_s = crm_element_value(set, XML_RULE_ATTR_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;
}
if (ordering == NULL) {
ordering = "group";
}
if (pcmk__xe_get_bool_attr(set, "sequential", &sequential) == pcmk_rc_ok && !sequential) {
return;
} else if ((local_score > 0)
&& pcmk__str_eq(ordering, "group", pcmk__str_casei)) {
for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
if (with != NULL) {
pe_rsc_trace(resource, "Colocating %s with %s", resource->id, with->id);
pcmk__new_colocation(set_id, NULL, local_score, resource,
with, role, role,
unpack_influence(coloc_id, resource,
influence_s), data_set);
}
with = resource;
}
} else if (local_score > 0) {
pe_resource_t *last = NULL;
for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
if (last != NULL) {
pe_rsc_trace(resource, "Colocating %s with %s",
last->id, resource->id);
pcmk__new_colocation(set_id, NULL, local_score, last,
resource, role, role,
unpack_influence(coloc_id, last,
influence_s), data_set);
}
last = 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 = first_named_child(set, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
xmlNode *xml_rsc_with = NULL;
bool influence = true;
EXPAND_CONSTRAINT_IDREF(set_id, resource, ID(xml_rsc));
influence = unpack_influence(coloc_id, resource, influence_s);
for (xml_rsc_with = first_named_child(set, XML_TAG_RESOURCE_REF);
xml_rsc_with != NULL;
xml_rsc_with = crm_next_same_xml(xml_rsc_with)) {
if (pcmk__str_eq(resource->id, ID(xml_rsc_with),
pcmk__str_casei)) {
break;
}
EXPAND_CONSTRAINT_IDREF(set_id, with, ID(xml_rsc_with));
pe_rsc_trace(resource, "Anti-Colocating %s with %s", resource->id,
with->id);
pcmk__new_colocation(set_id, NULL, local_score,
resource, with, role, role,
influence, data_set);
}
}
}
}
static void
colocate_rsc_sets(const char *id, xmlNode *set1, xmlNode *set2, int score,
const char *influence_s, pe_working_set_t *data_set)
{
xmlNode *xml_rsc = NULL;
pe_resource_t *rsc_1 = NULL;
pe_resource_t *rsc_2 = NULL;
const char *role_1 = crm_element_value(set1, "role");
const char *role_2 = crm_element_value(set2, "role");
int rc = pcmk_rc_ok;
bool sequential = false;
if (score == 0) {
crm_trace("Ignoring colocation '%s' between sets because score is 0",
id);
return;
}
rc = pcmk__xe_get_bool_attr(set1, "sequential", &sequential);
if (rc != pcmk_rc_ok || sequential) {
// Get the first one
xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
if (xml_rsc != NULL) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
}
}
rc = pcmk__xe_get_bool_attr(set2, "sequential", &sequential);
if (rc != pcmk_rc_ok || sequential) {
// Get the last one
const char *rid = NULL;
for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
rid = ID(xml_rsc);
}
EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
}
if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
unpack_influence(id, rsc_1, influence_s),
data_set);
} else if (rsc_1 != NULL) {
bool influence = unpack_influence(id, rsc_1, influence_s);
for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2, influence, data_set);
}
} else if (rsc_2 != NULL) {
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
role_2,
unpack_influence(id, rsc_1, influence_s),
data_set);
}
} else {
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
xmlNode *xml_rsc_2 = NULL;
bool influence = true;
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
influence = unpack_influence(id, rsc_1, influence_s);
for (xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc_2 != NULL;
xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
role_1, role_2, influence,
data_set);
}
}
}
}
static void
unpack_simple_colocation(xmlNode *xml_obj, const char *id,
const char *influence_s, pe_working_set_t *data_set)
{
int score_i = 0;
const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
const char *dependent_id = crm_element_value(xml_obj,
XML_COLOC_ATTR_SOURCE);
const char *primary_id = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET);
const char *dependent_role = crm_element_value(xml_obj,
XML_COLOC_ATTR_SOURCE_ROLE);
const char *primary_role = crm_element_value(xml_obj,
XML_COLOC_ATTR_TARGET_ROLE);
const char *attr = crm_element_value(xml_obj, XML_COLOC_ATTR_NODE_ATTR);
// @COMPAT: Deprecated since 2.1.5
const char *dependent_instance = crm_element_value(xml_obj,
XML_COLOC_ATTR_SOURCE_INSTANCE);
// @COMPAT: Deprecated since 2.1.5
const char *primary_instance = crm_element_value(xml_obj,
XML_COLOC_ATTR_TARGET_INSTANCE);
pe_resource_t *dependent = pcmk__find_constraint_resource(data_set->resources,
dependent_id);
pe_resource_t *primary = pcmk__find_constraint_resource(data_set->resources,
primary_id);
if (dependent_instance != NULL) {
pe_warn_once(pe_wo_coloc_inst,
"Support for " XML_COLOC_ATTR_SOURCE_INSTANCE " is "
"deprecated and will be removed in a future release.");
}
if (primary_instance != NULL) {
pe_warn_once(pe_wo_coloc_inst,
"Support for " XML_COLOC_ATTR_TARGET_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) && !pe_rsc_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) && !pe_rsc_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, data_set);
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, data_set);
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, XML_CONS_ATTR_SYMMETRICAL)) {
pcmk__config_warn("The colocation constraint '"
XML_CONS_ATTR_SYMMETRICAL
"' attribute has been removed");
}
if (score) {
score_i = char2score(score);
}
pcmk__new_colocation(id, attr, score_i, dependent, primary,
dependent_role, primary_role,
unpack_influence(id, dependent, influence_s), data_set);
}
// \return Standard Pacemaker return code
static int
unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
pe_working_set_t *data_set)
{
const char *id = NULL;
const char *dependent_id = NULL;
const char *primary_id = NULL;
const char *dependent_role = NULL;
const char *primary_role = NULL;
pe_resource_t *dependent = NULL;
pe_resource_t *primary = NULL;
pe_tag_t *dependent_tag = NULL;
pe_tag_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 = ID(xml_obj);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
crm_element_name(xml_obj));
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, data_set);
if (*expanded_xml != NULL) {
crm_log_xml_trace(*expanded_xml, "Expanded rsc_colocation");
return pcmk_rc_ok;
}
dependent_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
primary_id = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET);
if ((dependent_id == NULL) || (primary_id == NULL)) {
return pcmk_rc_ok;
}
if (!pcmk__valid_resource_or_tag(data_set, 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(data_set, 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, XML_COLOC_ATTR_SOURCE_ROLE);
primary_role = crm_element_value(xml_obj, XML_COLOC_ATTR_TARGET_ROLE);
*expanded_xml = copy_xml(xml_obj);
// Convert template/tag reference in "rsc" into resource_set under constraint
if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, XML_COLOC_ATTR_SOURCE,
true, data_set)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (dependent_set != NULL) {
if (dependent_role != NULL) {
// Move "rsc-role" into converted resource_set as "role"
crm_xml_add(dependent_set, "role", dependent_role);
xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_SOURCE_ROLE);
}
any_sets = true;
}
// Convert template/tag reference in "with-rsc" into resource_set under constraint
if (!pcmk__tag_to_set(*expanded_xml, &primary_set, XML_COLOC_ATTR_TARGET,
true, data_set)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (primary_set != NULL) {
if (primary_role != NULL) {
// Move "with-rsc-role" into converted resource_set as "role"
crm_xml_add(primary_set, "role", primary_role);
xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_TARGET_ROLE);
}
any_sets = true;
}
if (any_sets) {
crm_log_xml_trace(*expanded_xml, "Expanded rsc_colocation");
} else {
free_xml(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Parse a colocation constraint from XML into a cluster working set
*
* \param[in] xml_obj Colocation constraint XML to unpack
* \param[in] data_set Cluster working set to add constraint to
*/
void
pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set)
{
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, XML_ATTR_ID);
const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
const char *influence_s = crm_element_value(xml_obj,
XML_COLOC_ATTR_INFLUENCE);
if (score) {
score_i = char2score(score);
}
if (unpack_colocation_tags(xml_obj, &expanded_xml,
data_set) != pcmk_rc_ok) {
return;
}
if (expanded_xml) {
orig_xml = xml_obj;
xml_obj = expanded_xml;
}
for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
set = crm_next_same_xml(set)) {
set = expand_idref(set, data_set->input);
if (set == NULL) { // Configuration error, message already logged
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
unpack_colocation_set(set, score_i, id, influence_s, data_set);
if (last != NULL) {
colocate_rsc_sets(id, last, set, score_i, influence_s, data_set);
}
last = set;
}
if (expanded_xml) {
free_xml(expanded_xml);
xml_obj = orig_xml;
}
if (last == NULL) {
unpack_simple_colocation(xml_obj, id, influence_s, data_set);
}
}
+/*!
+ * \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_start_blocked(pe_resource_t *rsc, pe_resource_t *reason,
- pe_working_set_t *data_set)
+mark_action_blocked(pe_resource_t *rsc, const char *task,
+ const pe_resource_t *reason)
{
char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
for (GList *gIter = rsc->actions; gIter != NULL; gIter = gIter->next) {
pe_action_t *action = (pe_action_t *) gIter->data;
if (pcmk_is_set(action->flags, pe_action_runnable)
- && pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
+ && pcmk__str_eq(action->task, task, pcmk__str_casei)) {
pe__clear_action_flags(action, pe_action_runnable);
pe_action_set_reason(action, reason_text, false);
- pcmk__block_colocated_starts(action, data_set);
- pcmk__update_action_for_orderings(action, data_set);
+ pcmk__block_colocation_dependents(action, rsc->cluster);
+ pcmk__update_action_for_orderings(action, rsc->cluster);
}
}
+
+ // If parent resource can't perform an action, neither can any children
+ for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
+ mark_action_blocked((pe_resource_t *) (iter->data), task, reason);
+ }
free(reason_text);
}
/*!
* \internal
- * \brief If a start action is unrunnable, block starts of colocated resources
+ * \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] action Action to check
* \param[in] data_set Cluster working set
*/
void
-pcmk__block_colocated_starts(pe_action_t *action, pe_working_set_t *data_set)
+pcmk__block_colocation_dependents(pe_action_t *action,
+ pe_working_set_t *data_set)
{
GList *gIter = NULL;
pe_resource_t *rsc = NULL;
+ bool is_start = false;
- if (!pcmk_is_set(action->flags, pe_action_runnable)
- && pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
+ if (pcmk_is_set(action->flags, pe_action_runnable)) {
+ return; // Only unrunnable actions block dependents
+ }
- rsc = uber_parent(action->rsc);
- if (rsc->parent) {
- /* For bundles, uber_parent() returns the clone, not the bundle, so
- * the existence of rsc->parent implies this is a bundle.
- * In this case, we need the bundle resource, so that we can check
- * if all containers are stopped/stopping.
- */
- rsc = rsc->parent;
- }
+ is_start = pcmk__str_eq(action->task, RSC_START, pcmk__str_none);
+ if (!is_start && !pcmk__str_eq(action->task, RSC_PROMOTE, pcmk__str_none)) {
+ return; // Only unrunnable starts and promotes block dependents
}
- if ((rsc == NULL) || (rsc->rsc_cons_lhs == NULL)) {
+ 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->parent != NULL) {
+ rsc = rsc->parent; // Bundle
+ }
+
+ if (rsc->rsc_cons_lhs == NULL) {
return;
}
- // Block colocated starts only if all children (if any) have unrunnable starts
+ // Colocation fails only if entire primary can't reach desired role
for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
- pe_resource_t *child = (pe_resource_t *)gIter->data;
- pe_action_t *start = find_first_action(child->actions, NULL, RSC_START, NULL);
-
- if ((start == NULL) || pcmk_is_set(start->flags, pe_action_runnable)) {
- return;
+ pe_resource_t *child = (pe_resource_t *) gIter->data;
+ pe_action_t *child_action = find_first_action(child->actions, NULL,
+ action->task, NULL);
+
+ if ((child_action == NULL)
+ || pcmk_is_set(child_action->flags, pe_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
for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) {
- pcmk__colocation_t *colocate_with = (pcmk__colocation_t *) gIter->data;
+ pcmk__colocation_t *colocation = (pcmk__colocation_t *) gIter->data;
+
+ if (colocation->score < INFINITY) {
+ continue; // Only mandatory colocations block dependent
+ }
- if (colocate_with->score == INFINITY) {
- mark_start_blocked(colocate_with->dependent, action->rsc, data_set);
+ /* 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 != RSC_ROLE_PROMOTED)) {
+ continue;
+ }
+
+ // Block the dependent from reaching its colocated role
+ if (colocation->dependent_role == RSC_ROLE_PROMOTED) {
+ mark_action_blocked(colocation->dependent, RSC_PROMOTE,
+ action->rsc);
+ } else {
+ mark_action_blocked(colocation->dependent, RSC_START, action->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 allocated
*
* \return How colocation constraint should be applied at this point
*/
enum pcmk__coloc_affects
pcmk__colocation_affects(const pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation, bool preview)
{
if (!preview && pcmk_is_set(primary->flags, pe_rsc_provisional)) {
// Primary resource has not been allocated yet, so we can't do anything
return pcmk__coloc_affects_nothing;
}
if ((colocation->dependent_role >= RSC_ROLE_UNPROMOTED)
&& (dependent->parent != NULL)
&& pcmk_is_set(dependent->parent->flags, pe_rsc_promotable)
&& !pcmk_is_set(dependent->flags, pe_rsc_provisional)) {
/* This is a colocation by role, and the dependent is a promotable clone
* that has already been allocated, so the colocation should now affect
* the role.
*/
return pcmk__coloc_affects_role;
}
if (!preview && !pcmk_is_set(dependent->flags, pe_rsc_provisional)) {
/* The dependent resource has already been through allocation, so the
* constraint no longer has any effect. Log an error if a mandatory
* colocation constraint has been violated.
*/
const pe_node_t *primary_node = primary->allocated_to;
if (dependent->allocated_to == NULL) {
crm_trace("Skipping colocation '%s': %s will not run anywhere",
colocation->id, dependent->id);
} else if (colocation->score >= INFINITY) {
// Dependent resource must colocate with primary resource
if ((primary_node == NULL) ||
(primary_node->details != dependent->allocated_to->details)) {
crm_err("%s must be colocated with %s but is not (%s vs. %s)",
dependent->id, primary->id,
pe__node_name(dependent->allocated_to),
pe__node_name(primary_node));
}
} else if (colocation->score <= -CRM_SCORE_INFINITY) {
// Dependent resource must anti-colocate with primary resource
if ((primary_node != NULL) &&
(dependent->allocated_to->details == primary_node->details)) {
crm_err("%s and %s must be anti-colocated but are allocated "
"to the same node (%s)",
dependent->id, primary->id, pe__node_name(primary_node));
}
}
return pcmk__coloc_affects_nothing;
}
if ((colocation->score > 0)
&& (colocation->dependent_role != RSC_ROLE_UNKNOWN)
&& (colocation->dependent_role != dependent->next_role)) {
crm_trace("Skipping colocation '%s': dependent limited to %s role "
"but %s next role is %s",
colocation->id, role2text(colocation->dependent_role),
dependent->id, role2text(dependent->next_role));
return pcmk__coloc_affects_nothing;
}
if ((colocation->score > 0)
&& (colocation->primary_role != RSC_ROLE_UNKNOWN)
&& (colocation->primary_role != primary->next_role)) {
crm_trace("Skipping colocation '%s': primary limited to %s role "
"but %s next role is %s",
colocation->id, role2text(colocation->primary_role),
primary->id, role2text(primary->next_role));
return pcmk__coloc_affects_nothing;
}
if ((colocation->score < 0)
&& (colocation->dependent_role != RSC_ROLE_UNKNOWN)
&& (colocation->dependent_role == dependent->next_role)) {
crm_trace("Skipping anti-colocation '%s': dependent role %s matches",
colocation->id, role2text(colocation->dependent_role));
return pcmk__coloc_affects_nothing;
}
if ((colocation->score < 0)
&& (colocation->primary_role != RSC_ROLE_UNKNOWN)
&& (colocation->primary_role == primary->next_role)) {
crm_trace("Skipping anti-colocation '%s': primary role %s matches",
colocation->id, role2text(colocation->primary_role));
return pcmk__coloc_affects_nothing;
}
return pcmk__coloc_affects_location;
}
/*!
* \internal
* \brief Apply colocation to dependent for allocation purposes
*
* Update the allowed node weights of the dependent resource in a colocation,
* for the purposes of allocating 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_weights(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *attribute = CRM_ATTR_ID;
const char *value = NULL;
GHashTable *work = NULL;
GHashTableIter iter;
pe_node_t *node = NULL;
if (colocation->node_attribute != NULL) {
attribute = colocation->node_attribute;
}
if (primary->allocated_to != NULL) {
value = pe_node_attribute_raw(primary->allocated_to, attribute);
} else if (colocation->score < 0) {
// Nothing to do (anti-colocation with something that is not running)
return;
}
work = pcmk__copy_node_table(dependent->allowed_nodes);
g_hash_table_iter_init(&iter, work);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (primary->allocated_to == NULL) {
pe_rsc_trace(dependent, "%s: %s@%s -= %d (%s inactive)",
colocation->id, dependent->id, pe__node_name(node),
colocation->score, primary->id);
node->weight = pcmk__add_scores(-colocation->score, node->weight);
} else if (pcmk__str_eq(pe_node_attribute_raw(node, attribute), value,
pcmk__str_casei)) {
if (colocation->score < CRM_SCORE_INFINITY) {
pe_rsc_trace(dependent, "%s: %s@%s += %d",
colocation->id, dependent->id,
pe__node_name(node), colocation->score);
node->weight = pcmk__add_scores(colocation->score,
node->weight);
}
} else if (colocation->score >= CRM_SCORE_INFINITY) {
pe_rsc_trace(dependent, "%s: %s@%s -= %d (%s mismatch)",
colocation->id, dependent->id, pe__node_name(node),
colocation->score, attribute);
node->weight = pcmk__add_scores(-colocation->score, node->weight);
}
}
if ((colocation->score <= -INFINITY) || (colocation->score >= INFINITY)
|| pcmk__any_node_available(work)) {
g_hash_table_destroy(dependent->allowed_nodes);
dependent->allowed_nodes = work;
work = NULL;
} else {
pe_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(pe_resource_t *dependent,
const pe_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const char *dependent_value = NULL;
const char *primary_value = NULL;
const char *attribute = CRM_ATTR_ID;
int score_multiplier = 1;
if ((primary->allocated_to == NULL) || (dependent->allocated_to == NULL)) {
return;
}
if (colocation->node_attribute != NULL) {
attribute = colocation->node_attribute;
}
dependent_value = pe_node_attribute_raw(dependent->allocated_to, attribute);
primary_value = pe_node_attribute_raw(primary->allocated_to, attribute);
if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
if ((colocation->score == INFINITY)
&& (colocation->dependent_role == RSC_ROLE_PROMOTED)) {
dependent->priority = -INFINITY;
}
return;
}
if ((colocation->primary_role != RSC_ROLE_UNKNOWN)
&& (colocation->primary_role != primary->next_role)) {
return;
}
if (colocation->dependent_role == RSC_ROLE_UNPROMOTED) {
score_multiplier = -1;
}
dependent->priority = pcmk__add_scores(score_multiplier * colocation->score,
dependent->priority);
}
/*!
* \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
*/
static int
best_node_score_matching_attr(const pe_resource_t *rsc, const char *attr,
const char *value)
{
GHashTableIter iter;
pe_node_t *node = NULL;
int best_score = -INFINITY;
const char *best_node = NULL;
// Find best allowed node with matching attribute
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if ((node->weight > best_score) && pcmk__node_available(node, false, false)
&& pcmk__str_eq(value, pe_node_attribute_raw(node, attr), pcmk__str_casei)) {
best_score = node->weight;
best_node = node->details->uname;
}
}
if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_casei)) {
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);
}
}
return best_score;
}
/*!
* \internal
* \brief Add resource's colocation matches to current node allocation 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 Hash table of nodes with allocation scores so far
* \param[in] rsc Resource whose allowed nodes should be compared
* \param[in] attr Colocation attribute that must match (NULL for default)
* \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 pe_resource_t *rsc,
const char *attr, float factor,
bool only_positive)
{
GHashTableIter iter;
pe_node_t *node = NULL;
if (attr == NULL) {
attr = CRM_ATTR_UNAME;
}
// Iterate through each node
g_hash_table_iter_init(&iter, nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
float weight_f = 0;
int weight = 0;
int score = 0;
int new_score = 0;
score = best_node_score_matching_attr(rsc, attr,
pe_node_attribute_raw(node, attr));
if ((factor < 0) && (score < 0)) {
/* Negative preference for a node with a negative score
* should not become a positive preference.
*
* @TODO Consider filtering only if weight is -INFINITY
*/
crm_trace("%s: Filtering %d + %f * %d (double negative disallowed)",
pe__node_name(node), node->weight, factor, score);
continue;
}
if (node->weight == INFINITY_HACK) {
crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
pe__node_name(node), node->weight, factor, score);
continue;
}
weight_f = factor * score;
// Round the number; see http://c-faq.com/fp/round.html
weight = (int) ((weight_f < 0)? (weight_f - 0.5) : (weight_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 ((weight == 0) && (score != 0)) {
if (factor > 0.0) {
weight = 1;
} else if (factor < 0.0) {
weight = -1;
}
}
new_score = pcmk__add_scores(weight, node->weight);
if (only_positive && (new_score < 0) && (node->weight > 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d "
"(negative disallowed, marking node unusable)",
pe__node_name(node), node->weight, factor, score,
new_score);
node->weight = INFINITY_HACK;
continue;
}
if (only_positive && (new_score < 0) && (node->weight == 0)) {
crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
pe__node_name(node), node->weight, factor, score,
new_score);
continue;
}
crm_trace("%s: %d + %f * %d = %d", pe__node_name(node),
node->weight, factor, score, new_score);
node->weight = new_score;
}
}
/*!
* \internal
* \brief Initialize colocated node table for a group resource
*
* \param[in] rsc Group resource being colocated with another resource
* \param[in] log_id Resource ID to use in log messages
* \param[in] nodes Nodes to update
* \param[in] attr Colocation attribute (NULL to use default)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \return Table of node scores initialized for colocation, or NULL if resource
* should be ignored for colocation purposes
* \note The caller is responsible for freeing a non-NULL return value using
* g_hash_table_destroy().
*/
static GHashTable *
init_group_colocated_nodes(const pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr, float factor,
uint32_t flags)
{
GHashTable *work = NULL;
pe_resource_t *member = NULL;
// Ignore empty groups (only possible with schema validation disabled)
if (rsc->children == NULL) {
return NULL;
}
if (*nodes == NULL) {
// Only cmp_resources() passes a NULL nodes table
member = pe__last_group_member(rsc);
} else {
/* The first member of the group will recursively incorporate any
* constraints involving other members (including the group internal
* colocation).
*
* @TODO The indirect colocations from the dependent group's other
* members will be incorporated at full strength rather than by
* factor, so the group's combined stickiness will be treated as
* (factor + (#members - 1)) * stickiness. It is questionable what
* the right approach should be.
*/
member = rsc->children->data;
}
pe_rsc_trace(rsc, "%s: Merging scores from group %s using member %s "
"(at %.6f)", log_id, rsc->id, member->id, factor);
work = pcmk__copy_node_table(*nodes);
pcmk__add_colocated_node_scores(member, log_id, &work, attr, factor, flags);
return work;
}
/*!
* \internal
* \brief Initialize colocated node table for a non-group resource
*
* \param[in] rsc Non-group resource being colocated with another resource
* \param[in] log_id Resource ID to use in log messages
* \param[in] nodes Nodes to update
* \param[in] attr Colocation attribute (NULL to use default)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \return Table of node scores initialized for colocation, or NULL if resource
* should be ignored for colocation purposes
* \note The caller is responsible for freeing a non-NULL return value using
* g_hash_table_destroy().
*/
static GHashTable *
init_nongroup_colocated_nodes(const pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr,
float factor, uint32_t flags)
{
GHashTable *work = NULL;
if (*nodes == NULL) {
/* Only cmp_resources() passes a NULL nodes table, which indicates we
* should initialize it with the resource's allowed node scores.
*/
work = pcmk__copy_node_table(rsc->allowed_nodes);
} else {
pe_rsc_trace(rsc, "%s: Merging scores from %s (at %.6f)",
log_id, rsc->id, factor);
work = pcmk__copy_node_table(*nodes);
add_node_scores_matching_attr(work, rsc, attr, factor,
pcmk_is_set(flags,
pcmk__coloc_select_nonnegative));
}
return work;
}
/*!
* \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] rsc Resource to check colocations for
* \param[in] log_id Resource ID to use in logs (if NULL, use \p rsc ID)
* \param[in,out] nodes Nodes to update
* \param[in] attr Colocation attribute (NULL to use default)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note The caller remains responsible for freeing \p *nodes.
*/
void
pcmk__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr,
float factor, uint32_t flags)
{
GHashTable *work = NULL;
CRM_CHECK((rsc != NULL) && (nodes != NULL), return);
if (log_id == NULL) {
log_id = rsc->id;
}
// Avoid infinite recursion
if (pcmk_is_set(rsc->flags, pe_rsc_merging)) {
pe_rsc_info(rsc, "%s: Breaking dependency loop at %s",
log_id, rsc->id);
return;
}
pe__set_resource_flags(rsc, pe_rsc_merging);
if (rsc->variant == pe_group) {
work = init_group_colocated_nodes(rsc, log_id, nodes, attr, factor,
flags);
} else {
work = init_nongroup_colocated_nodes(rsc, log_id, nodes, attr, factor,
flags);
}
if (work == NULL) {
pe__clear_resource_flags(rsc, pe_rsc_merging);
return;
}
if (pcmk__any_node_available(work)) {
GList *gIter = NULL;
float multiplier = (factor < 0.0)? -1.0 : 1.0;
if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
gIter = rsc->rsc_cons;
pe_rsc_trace(rsc,
"Checking additional %d optional '%s with' constraints",
g_list_length(gIter), rsc->id);
} else if (rsc->variant == pe_group) {
pe_resource_t *last_rsc = pe__last_group_member(rsc);
gIter = last_rsc->rsc_cons_lhs;
pe_rsc_trace(rsc, "Checking additional %d optional 'with group %s' "
"constraints using last member %s",
g_list_length(gIter), rsc->id, last_rsc->id);
} else {
gIter = rsc->rsc_cons_lhs;
pe_rsc_trace(rsc,
"Checking additional %d optional 'with %s' constraints",
g_list_length(gIter), rsc->id);
}
for (; gIter != NULL; gIter = gIter->next) {
pe_resource_t *other = NULL;
pcmk__colocation_t *constraint = (pcmk__colocation_t *) gIter->data;
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;
}
pe_rsc_trace(rsc, "Optionally merging score of '%s' constraint (%s with %s)",
constraint->id, constraint->dependent->id,
constraint->primary->id);
factor = multiplier * constraint->score / (float) INFINITY;
pcmk__add_colocated_node_scores(other, log_id, &work,
constraint->node_attribute, factor,
flags|pcmk__coloc_select_active);
pe__show_node_weights(true, NULL, log_id, work, rsc->cluster);
}
} else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
pe_rsc_info(rsc, "%s: Rolling back optional scores from %s",
log_id, rsc->id);
g_hash_table_destroy(work);
pe__clear_resource_flags(rsc, pe_rsc_merging);
return;
}
if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
pe_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->weight == INFINITY_HACK) {
node->weight = 1;
}
}
}
if (*nodes != NULL) {
g_hash_table_destroy(*nodes);
}
*nodes = work;
pe__clear_resource_flags(rsc, pe_rsc_merging);
}
diff --git a/lib/pacemaker/pcmk_sched_group.c b/lib/pacemaker/pcmk_sched_group.c
index 6bbe2d1b88..668fef76a5 100644
--- a/lib/pacemaker/pcmk_sched_group.c
+++ b/lib/pacemaker/pcmk_sched_group.c
@@ -1,763 +1,763 @@
/*
* Copyright 2004-2022 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 Expand a group's colocations to its members
*
* \param[in,out] rsc Group resource
*/
static void
expand_group_colocations(pe_resource_t *rsc)
{
pe_resource_t *member = NULL;
bool any_unmanaged = false;
GList *item = NULL;
if (rsc->children == NULL) {
return;
}
// Treat "group with R" colocations as "first member with R"
member = (pe_resource_t *) rsc->children->data;
for (item = rsc->rsc_cons; item != NULL; item = item->next) {
pcmk__add_this_with(member, (pcmk__colocation_t *) (item->data));
}
/* The above works for the whole group because each group member is
* colocated with the previous one.
*
* However, there is a special case when a group has a mandatory colocation
* with a resource that can't start. In that case,
- * pcmk__block_colocated_starts() will ensure that dependent resources in
- * mandatory colocations (i.e. the first member for groups) can't start
+ * pcmk__block_colocation_dependents() will ensure that dependent resources
+ * in mandatory colocations (i.e. the first member for groups) can't start
* either. But if any group member is unmanaged and already started, the
* internal group colocations are no longer sufficient to make that apply to
* later members.
*
* To handle that case, add mandatory colocations to each member after the
* first.
*/
any_unmanaged = !pcmk_is_set(member->flags, pe_rsc_managed);
for (item = rsc->children->next; item != NULL; item = item->next) {
member = item->data;
if (any_unmanaged) {
for (GList *cons_iter = rsc->rsc_cons; cons_iter != NULL;
cons_iter = cons_iter->next) {
pcmk__colocation_t *constraint = (pcmk__colocation_t *) cons_iter->data;
if (constraint->score == INFINITY) {
pcmk__add_this_with(member, constraint);
}
}
} else if (!pcmk_is_set(member->flags, pe_rsc_managed)) {
any_unmanaged = true;
}
}
g_list_free(rsc->rsc_cons);
rsc->rsc_cons = NULL;
// Treat "R with group" colocations as "R with last member"
member = pe__last_group_member(rsc);
for (item = rsc->rsc_cons_lhs; item != NULL; item = item->next) {
pcmk__add_with_this(member, (pcmk__colocation_t *) (item->data));
}
g_list_free(rsc->rsc_cons_lhs);
rsc->rsc_cons_lhs = NULL;
}
/*!
* \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
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*/
pe_node_t *
pcmk__group_assign(pe_resource_t *rsc, const pe_node_t *prefer)
{
pe_node_t *first_assigned_node = NULL;
pe_resource_t *first_member = NULL;
CRM_ASSERT(rsc != NULL);
if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
return rsc->allocated_to; // Assignment already done
}
if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
pe_rsc_debug(rsc, "Assignment dependency loop detected involving %s",
rsc->id);
return NULL;
}
if (rsc->children == NULL) {
// No members to assign
pe__clear_resource_flags(rsc, pe_rsc_provisional);
return NULL;
}
pe__set_resource_flags(rsc, pe_rsc_allocating);
first_member = (pe_resource_t *) rsc->children->data;
rsc->role = first_member->role;
expand_group_colocations(rsc);
pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_scores),
rsc, __func__, rsc->allowed_nodes, rsc->cluster);
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
pe_node_t *node = NULL;
pe_rsc_trace(rsc, "Assigning group %s member %s",
rsc->id, member->id);
node = member->cmds->assign(member, prefer);
if (first_assigned_node == NULL) {
first_assigned_node = node;
}
}
pe__set_next_role(rsc, first_member->next_role, "first group member");
pe__clear_resource_flags(rsc, pe_rsc_allocating|pe_rsc_provisional);
if (!pe__group_flag_is_set(rsc, pe__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 pe_action_t *
create_group_pseudo_op(pe_resource_t *group, const char *action)
{
pe_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
action, NULL, TRUE, TRUE, group->cluster);
pe__set_action_flags(op, pe_action_pseudo|pe_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(pe_resource_t *rsc)
{
CRM_ASSERT(rsc != NULL);
pe_rsc_trace(rsc, "Creating actions for group %s", rsc->id);
// Create actions for individual group members
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
member->cmds->create_actions(member);
}
// Create pseudo-actions for group itself to serve as ordering points
create_group_pseudo_op(rsc, RSC_START);
create_group_pseudo_op(rsc, RSC_STARTED);
create_group_pseudo_op(rsc, RSC_STOP);
create_group_pseudo_op(rsc, RSC_STOPPED);
if (crm_is_true(g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROMOTABLE))) {
create_group_pseudo_op(rsc, RSC_DEMOTE);
create_group_pseudo_op(rsc, RSC_DEMOTED);
create_group_pseudo_op(rsc, RSC_PROMOTE);
create_group_pseudo_op(rsc, RSC_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;
pe_resource_t *last_active;
pe_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 Group member to create implicit constraints for
*/
static void
member_internal_constraints(gpointer data, gpointer user_data)
{
pe_resource_t *member = (pe_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 = pe_order_implies_first_printed;
// For ordering demote vs demoted or stop vs stopped
uint32_t post_down_flags = pe_order_implies_then_printed;
// Create the individual member's implicit constraints
member->cmds->internal_constraints(member);
if (member_data->previous_member == NULL) {
// This is first member
if (member_data->ordered) {
pe__set_order_flags(down_flags, pe_order_optional);
post_down_flags = pe_order_implies_then;
}
} else if (member_data->colocated) {
// Colocate this member with the previous one
pcmk__new_colocation("group:internal_colocation", NULL, INFINITY,
member, member_data->previous_member, NULL, NULL,
pcmk_is_set(member->flags, pe_rsc_critical),
member->cluster);
}
if (member_data->promotable) {
// Demote group -> demote member -> group is demoted
pcmk__order_resource_actions(member->parent, RSC_DEMOTE,
member, RSC_DEMOTE, down_flags);
pcmk__order_resource_actions(member, RSC_DEMOTE,
member->parent, RSC_DEMOTED,
post_down_flags);
// Promote group -> promote member -> group is promoted
pcmk__order_resource_actions(member, RSC_PROMOTE,
member->parent, RSC_PROMOTED,
pe_order_runnable_left
|pe_order_implies_then
|pe_order_implies_then_printed);
pcmk__order_resource_actions(member->parent, RSC_PROMOTE,
member, RSC_PROMOTE,
pe_order_implies_first_printed);
}
// Stop group -> stop member -> group is stopped
pcmk__order_stops(member->parent, member, down_flags);
pcmk__order_resource_actions(member, RSC_STOP, member->parent, RSC_STOPPED,
post_down_flags);
// Start group -> start member -> group is started
pcmk__order_starts(member->parent, member, pe_order_implies_first_printed);
pcmk__order_resource_actions(member, RSC_START, member->parent, RSC_STARTED,
pe_order_runnable_left
|pe_order_implies_then
|pe_order_implies_then_printed);
if (!member_data->ordered) {
pcmk__order_starts(member->parent, member,
pe_order_implies_then
|pe_order_runnable_left
|pe_order_implies_first_printed);
if (member_data->promotable) {
pcmk__order_resource_actions(member->parent, RSC_PROMOTE, member,
RSC_PROMOTE,
pe_order_implies_then
|pe_order_runnable_left
|pe_order_implies_first_printed);
}
} else if (member_data->previous_member == NULL) {
pcmk__order_starts(member->parent, member, pe_order_none);
if (member_data->promotable) {
pcmk__order_resource_actions(member->parent, RSC_PROMOTE, member,
RSC_PROMOTE, pe_order_none);
}
} else {
// Order this member relative to the previous one
pcmk__order_starts(member_data->previous_member, member,
pe_order_implies_then|pe_order_runnable_left);
pcmk__order_stops(member, member_data->previous_member,
pe_order_optional|pe_order_restart);
if (member_data->promotable) {
pcmk__order_resource_actions(member_data->previous_member,
RSC_PROMOTE, member, RSC_PROMOTE,
pe_order_implies_then
|pe_order_runnable_left);
pcmk__order_resource_actions(member, RSC_DEMOTE,
member_data->previous_member,
RSC_DEMOTE, pe_order_optional);
}
}
// Make sure partially active groups shut down in sequence
if (member->running_on != NULL) {
if (member_data->ordered && (member_data->previous_member != NULL)
&& (member_data->previous_member->running_on == NULL)
&& (member_data->last_active != NULL)
&& (member_data->last_active->running_on != NULL)) {
pcmk__order_stops(member, member_data->last_active, pe_order_optional);
}
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(pe_resource_t *rsc)
{
struct member_data member_data = { false, };
CRM_ASSERT(rsc != NULL);
/* 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, RSC_STOP, rsc, RSC_STOPPED,
pe_order_runnable_left);
pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_START,
pe_order_optional);
pcmk__order_resource_actions(rsc, RSC_START, rsc, RSC_STARTED,
pe_order_runnable_left);
member_data.ordered = pe__group_flag_is_set(rsc, pe__group_ordered);
member_data.colocated = pe__group_flag_is_set(rsc, pe__group_colocated);
member_data.promotable = pcmk_is_set(uber_parent(rsc)->flags, pe_rsc_promotable);
g_list_foreach(rsc->children, member_internal_constraints, &member_data);
}
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint for a group with some other resource, apply the
* score to the dependent's allowed node weights (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(pe_resource_t *dependent, const pe_resource_t *primary,
const pcmk__colocation_t *colocation)
{
pe_resource_t *member = NULL;
if (dependent->children == NULL) {
return;
}
pe_rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
colocation->id, dependent->id, primary->id);
if (pe__group_flag_is_set(dependent, pe__group_colocated)) {
// Colocate first member (internal colocations will handle the rest)
member = (pe_resource_t *) dependent->children->data;
member->cmds->apply_coloc_score(member, primary, colocation, true);
return;
}
if (colocation->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->children; iter != NULL; iter = iter->next) {
member = (pe_resource_t *) iter->data;
member->cmds->apply_coloc_score(member, primary, colocation, true);
}
}
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint for some other resource with a group, apply the
* score to the dependent's allowed node weights (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(pe_resource_t *dependent, const pe_resource_t *primary,
const pcmk__colocation_t *colocation)
{
pe_resource_t *member = NULL;
pe_rsc_trace(primary,
"Processing colocation %s (%s with group %s) for primary",
colocation->id, dependent->id, primary->id);
if (pcmk_is_set(primary->flags, pe_rsc_provisional)) {
return;
}
if (pe__group_flag_is_set(primary, pe__group_colocated)) {
if (colocation->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->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 = (pe_resource_t *) primary->children->data;
}
if (member == NULL) {
return; // Nothing to colocate with
}
member->cmds->apply_coloc_score(dependent, member, colocation, false);
return;
}
if (colocation->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 (GList *iter = primary->children; iter != NULL; iter = iter->next) {
member = (pe_resource_t *) iter->data;
member->cmds->apply_coloc_score(dependent, member, colocation, false);
}
}
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node weights (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(pe_resource_t *dependent,
const pe_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(dependent->variant == pe_native);
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
*/
enum pe_action_flags
pcmk__group_action_flags(pe_action_t *action, const pe_node_t *node)
{
// Default flags for a group action
enum pe_action_flags flags = pe_action_optional
|pe_action_runnable
|pe_action_pseudo;
CRM_ASSERT(action != NULL);
// Update flags considering each member's own flags for same action
for (GList *iter = action->rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
// Check whether member has the same action
enum action_tasks task = get_complex_task(member, action->task, TRUE);
const char *task_s = task2text(task);
pe_action_t *member_action = find_first_action(member->actions, NULL,
task_s, node);
if (member_action != NULL) {
enum pe_action_flags member_flags;
member_flags = member->cmds->action_flags(member_action, node);
// Group action is mandatory if any member action is
if (pcmk_is_set(flags, pe_action_optional)
&& !pcmk_is_set(member_flags, pe_action_optional)) {
pe_rsc_trace(action->rsc, "%s is mandatory because %s is",
action->uuid, member_action->uuid);
pe__clear_raw_action_flags(flags, "group action",
pe_action_optional);
pe__clear_action_flags(action, pe_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, pe_action_runnable)
&& !pcmk_is_set(member_flags, pe_action_runnable)) {
pe_rsc_trace(action->rsc, "%s is unrunnable because %s is",
action->uuid, member_action->uuid);
pe__clear_raw_action_flags(flags, "group action",
pe_action_runnable);
pe__clear_action_flags(action, pe_action_runnable);
}
/* Group (pseudo-)actions other than stop or demote are unrunnable
* unless every member will do it.
*/
} else if ((task != stop_rsc) && (task != action_demote)) {
pe_rsc_trace(action->rsc,
"%s is not runnable because %s will not %s",
action->uuid, member->id, task_s);
pe__clear_raw_action_flags(flags, "group action",
pe_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. In some cases, the ordering could be disabled 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 pe_action_optional to affect only mandatory
* actions, and pe_action_runnable to affect only
* runnable actions)
* \param[in] type Group of enum pe_ordering flags to apply
* \param[in,out] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__group_update_ordered_actions(pe_action_t *first, pe_action_t *then,
const pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set)
{
uint32_t changed = pcmk__updated_none;
CRM_ASSERT((first != NULL) && (then != NULL) && (data_set != NULL));
// Group method can be called only for group action as "then" action
CRM_ASSERT(then->rsc != NULL);
// Update the actions for the group itself
changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
type, data_set);
// Update the actions for each group member
for (GList *iter = then->rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
pe_action_t *member_action = find_first_action(member->actions, NULL,
then->task, node);
if (member_action != NULL) {
changed |= member->cmds->update_ordered_actions(first,
member_action, node,
flags, filter, type,
data_set);
}
}
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(pe_resource_t *rsc, pe__location_t *location)
{
GList *node_list_orig = NULL;
GList *node_list_copy = NULL;
bool reset_scores = true;
CRM_ASSERT((rsc != NULL) && (location != NULL));
node_list_orig = location->node_list_rh;
node_list_copy = pcmk__copy_node_list(node_list_orig, true);
reset_scores = pe__group_flag_is_set(rsc, pe__group_colocated);
// Apply the constraint for the group itself (updates node scores)
pcmk__apply_location(rsc, location);
// Apply the constraint for each member
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
member->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.
*/
reset_scores = false;
location->node_list_rh = node_list_copy;
}
}
location->node_list_rh = node_list_orig;
g_list_free_full(node_list_copy, free);
}
// Group implementation of resource_alloc_functions_t:colocated_resources()
GList *
pcmk__group_colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs)
{
pe_resource_t *member = NULL;
CRM_ASSERT(rsc != NULL);
if (orig_rsc == NULL) {
orig_rsc = rsc;
}
if (pe__group_flag_is_set(rsc, pe__group_colocated)
|| pe_rsc_is_clone(rsc->parent)) {
/* This group has colocated members and/or is cloned -- either way,
* add every child's colocated resources to the list.
*/
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
member = (pe_resource_t *) iter->data;
colocated_rscs = member->cmds->colocated_resources(member, orig_rsc,
colocated_rscs);
}
} else if (rsc->children != NULL) {
/* This group's members are not colocated, and the group is not cloned,
* so just add the first child's colocations to the list.
*/
member = (pe_resource_t *) rsc->children->data;
colocated_rscs = member->cmds->colocated_resources(member, orig_rsc,
colocated_rscs);
}
// Now consider colocations where the group itself is specified
colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc, colocated_rscs);
return colocated_rscs;
}
// Group implementation of resource_alloc_functions_t:add_utilization()
void
pcmk__group_add_utilization(const pe_resource_t *rsc,
const pe_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
pe_resource_t *member = NULL;
CRM_ASSERT((rsc != NULL) && (orig_rsc != NULL) && (utilization != NULL));
if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
return;
}
pe_rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
orig_rsc->id, rsc->id);
if (pe__group_flag_is_set(rsc, pe__group_colocated)
|| pe_rsc_is_clone(rsc->parent)) {
// Every group member will be on same node, so sum all members
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
member = (pe_resource_t *) iter->data;
if (pcmk_is_set(member->flags, pe_rsc_provisional)
&& (g_list_find(all_rscs, member) == NULL)) {
member->cmds->add_utilization(member, orig_rsc, all_rscs,
utilization);
}
}
} else if (rsc->children != NULL) {
// Just add first member's utilization
member = (pe_resource_t *) rsc->children->data;
if ((member != NULL)
&& pcmk_is_set(member->flags, pe_rsc_provisional)
&& (g_list_find(all_rscs, member) == NULL)) {
member->cmds->add_utilization(member, orig_rsc, all_rscs,
utilization);
}
}
}
// Group implementation of resource_alloc_functions_t:shutdown_lock()
void
pcmk__group_shutdown_lock(pe_resource_t *rsc)
{
CRM_ASSERT(rsc != NULL);
for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
pe_resource_t *member = (pe_resource_t *) iter->data;
member->cmds->shutdown_lock(member);
}
}
diff --git a/lib/pacemaker/pcmk_sched_ordering.c b/lib/pacemaker/pcmk_sched_ordering.c
index 527b97569a..2b498a672f 100644
--- a/lib/pacemaker/pcmk_sched_ordering.c
+++ b/lib/pacemaker/pcmk_sched_ordering.c
@@ -1,1466 +1,1466 @@
/*
* Copyright 2004-2022 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 // PRIx32
#include
#include
#include
#include
#include "libpacemaker_private.h"
enum pe_order_kind {
pe_order_kind_optional,
pe_order_kind_mandatory,
pe_order_kind_serialize,
};
enum ordering_symmetry {
ordering_asymmetric, // the only relation in an asymmetric ordering
ordering_symmetric, // the normal relation in a symmetric ordering
ordering_symmetric_inverse, // the inverse relation in a symmetric ordering
};
#define EXPAND_CONSTRAINT_IDREF(__set, __rsc, __name) do { \
__rsc = pcmk__find_constraint_resource(data_set->resources, __name); \
if (__rsc == NULL) { \
pcmk__config_err("%s: No resource found for %s", __set, __name); \
return pcmk_rc_unpack_error; \
} \
} while (0)
static const char *
invert_action(const char *action)
{
if (pcmk__str_eq(action, RSC_START, pcmk__str_casei)) {
return RSC_STOP;
} else if (pcmk__str_eq(action, RSC_STOP, pcmk__str_casei)) {
return RSC_START;
} else if (pcmk__str_eq(action, RSC_PROMOTE, pcmk__str_casei)) {
return RSC_DEMOTE;
} else if (pcmk__str_eq(action, RSC_DEMOTE, pcmk__str_casei)) {
return RSC_PROMOTE;
} else if (pcmk__str_eq(action, RSC_PROMOTED, pcmk__str_casei)) {
return RSC_DEMOTED;
} else if (pcmk__str_eq(action, RSC_DEMOTED, pcmk__str_casei)) {
return RSC_PROMOTED;
} else if (pcmk__str_eq(action, RSC_STARTED, pcmk__str_casei)) {
return RSC_STOPPED;
} else if (pcmk__str_eq(action, RSC_STOPPED, pcmk__str_casei)) {
return RSC_STARTED;
}
crm_warn("Unknown action '%s' specified in order constraint", action);
return NULL;
}
static enum pe_order_kind
get_ordering_type(xmlNode *xml_obj)
{
enum pe_order_kind kind_e = pe_order_kind_mandatory;
const char *kind = crm_element_value(xml_obj, XML_ORDER_ATTR_KIND);
if (kind == NULL) {
const char *score = crm_element_value(xml_obj, XML_RULE_ATTR_SCORE);
kind_e = pe_order_kind_mandatory;
if (score) {
// @COMPAT deprecated informally since 1.0.7, formally since 2.0.1
int score_i = char2score(score);
if (score_i == 0) {
kind_e = pe_order_kind_optional;
}
pe_warn_once(pe_wo_order_score,
"Support for 'score' in rsc_order is deprecated "
"and will be removed in a future release "
"(use 'kind' instead)");
}
} else if (pcmk__str_eq(kind, "Mandatory", pcmk__str_casei)) {
kind_e = pe_order_kind_mandatory;
} else if (pcmk__str_eq(kind, "Optional", pcmk__str_casei)) {
kind_e = pe_order_kind_optional;
} else if (pcmk__str_eq(kind, "Serialize", pcmk__str_casei)) {
kind_e = pe_order_kind_serialize;
} else {
pcmk__config_err("Resetting '" XML_ORDER_ATTR_KIND "' for constraint "
"%s to 'Mandatory' because '%s' is not valid",
pcmk__s(ID(xml_obj), "missing ID"), kind);
}
return kind_e;
}
/*!
* \internal
* \brief Get ordering symmetry from XML
*
* \param[in] xml_obj Ordering XML
* \param[in] parent_kind Default ordering kind
* \param[in] parent_symmetrical_s Parent element's symmetrical setting, if any
*
* \retval ordering_symmetric Ordering is symmetric
* \retval ordering_asymmetric Ordering is asymmetric
*/
static enum ordering_symmetry
get_ordering_symmetry(xmlNode *xml_obj, enum pe_order_kind parent_kind,
const char *parent_symmetrical_s)
{
int rc = pcmk_rc_ok;
bool symmetric = false;
enum pe_order_kind kind = parent_kind; // Default to parent's kind
// Check ordering XML for explicit kind
if ((crm_element_value(xml_obj, XML_ORDER_ATTR_KIND) != NULL)
|| (crm_element_value(xml_obj, XML_RULE_ATTR_SCORE) != NULL)) {
kind = get_ordering_type(xml_obj);
}
// Check ordering XML (and parent) for explicit symmetrical setting
rc = pcmk__xe_get_bool_attr(xml_obj, XML_CONS_ATTR_SYMMETRICAL, &symmetric);
if (rc != pcmk_rc_ok && parent_symmetrical_s != NULL) {
symmetric = crm_is_true(parent_symmetrical_s);
rc = pcmk_rc_ok;
}
if (rc == pcmk_rc_ok) {
if (symmetric) {
if (kind == pe_order_kind_serialize) {
pcmk__config_warn("Ignoring " XML_CONS_ATTR_SYMMETRICAL
" for '%s' because not valid with "
XML_ORDER_ATTR_KIND " of 'Serialize'",
ID(xml_obj));
} else {
return ordering_symmetric;
}
}
return ordering_asymmetric;
}
// Use default symmetry
if (kind == pe_order_kind_serialize) {
return ordering_asymmetric;
}
return ordering_symmetric;
}
/*!
* \internal
* \brief Get ordering flags appropriate to ordering kind
*
* \param[in] kind Ordering kind
* \param[in] first Action name for 'first' action
* \param[in] symmetry This ordering's symmetry role
*
* \return Minimal ordering flags appropriate to \p kind
*/
static uint32_t
ordering_flags_for_kind(enum pe_order_kind kind, const char *first,
enum ordering_symmetry symmetry)
{
uint32_t flags = pe_order_none; // so we trace-log all flags set
pe__set_order_flags(flags, pe_order_optional);
switch (kind) {
case pe_order_kind_optional:
break;
case pe_order_kind_serialize:
pe__set_order_flags(flags, pe_order_serialize_only);
break;
case pe_order_kind_mandatory:
switch (symmetry) {
case ordering_asymmetric:
pe__set_order_flags(flags, pe_order_asymmetrical);
break;
case ordering_symmetric:
pe__set_order_flags(flags, pe_order_implies_then);
if (pcmk__strcase_any_of(first, RSC_START, RSC_PROMOTE,
NULL)) {
pe__set_order_flags(flags, pe_order_runnable_left);
}
break;
case ordering_symmetric_inverse:
pe__set_order_flags(flags, pe_order_implies_first);
break;
}
break;
}
return flags;
}
/*!
* \internal
* \brief Find resource corresponding to ID specified in ordering
*
* \param[in] xml Ordering XML
* \param[in] resource_attr XML attribute name for resource ID
* \param[in] instance_attr XML attribute name for instance number.
* This option is deprecated and will be removed in a
* future release.
* \param[in] data_set Cluster working set
*
* \return Resource corresponding to \p id, or NULL if none
*/
static pe_resource_t *
get_ordering_resource(xmlNode *xml, const char *resource_attr,
const char *instance_attr, pe_working_set_t *data_set)
{
// @COMPAT: instance_attr and instance_id variables deprecated since 2.1.5
pe_resource_t *rsc = NULL;
const char *rsc_id = crm_element_value(xml, resource_attr);
const char *instance_id = crm_element_value(xml, instance_attr);
if (rsc_id == NULL) {
pcmk__config_err("Ignoring constraint '%s' without %s",
ID(xml), resource_attr);
return NULL;
}
rsc = pcmk__find_constraint_resource(data_set->resources, rsc_id);
if (rsc == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not exist", ID(xml), rsc_id);
return NULL;
}
if (instance_id != NULL) {
pe_warn_once(pe_wo_order_inst,
"Support for " XML_ORDER_ATTR_FIRST_INSTANCE " and "
XML_ORDER_ATTR_THEN_INSTANCE " is deprecated and will be "
"removed in a future release.");
if (!pe_rsc_is_clone(rsc)) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"is not a clone but instance '%s' was requested",
ID(xml), rsc_id, instance_id);
return NULL;
}
rsc = find_clone_instance(rsc, instance_id, data_set);
if (rsc == NULL) {
pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
"does not have an instance '%s'",
"'%s'", ID(xml), rsc_id, instance_id);
return NULL;
}
}
return rsc;
}
/*!
* \internal
* \brief Determine minimum number of 'first' instances required in ordering
*
* \param[in] rsc 'First' resource in ordering
* \param[in] xml Ordering XML
*
* \return Minimum 'first' instances required (or 0 if not applicable)
*/
static int
get_minimum_first_instances(pe_resource_t *rsc, xmlNode *xml)
{
const char *clone_min = NULL;
bool require_all = false;
if (!pe_rsc_is_clone(rsc)) {
return 0;
}
clone_min = g_hash_table_lookup(rsc->meta,
XML_RSC_ATTR_INCARNATION_MIN);
if (clone_min != NULL) {
int clone_min_int = 0;
pcmk__scan_min_int(clone_min, &clone_min_int, 0);
return clone_min_int;
}
/* @COMPAT 1.1.13:
* require-all=false is deprecated equivalent of clone-min=1
*/
if (pcmk__xe_get_bool_attr(xml, "require-all", &require_all) != ENODATA) {
pe_warn_once(pe_wo_require_all,
"Support for require-all in ordering constraints "
"is deprecated and will be removed in a future release"
" (use clone-min clone meta-attribute instead)");
if (!require_all) {
return 1;
}
}
return 0;
}
/*!
* \internal
* \brief Create orderings for a constraint with clone-min > 0
*
* \param[in] id Ordering ID
* \param[in] rsc_first 'First' resource in ordering (a clone)
* \param[in] action_first 'First' action in ordering
* \param[in] rsc_then 'Then' resource in ordering
* \param[in] action_then 'Then' action in ordering
* \param[in] flags Ordering flags
* \param[in] clone_min Minimum required instances of 'first'
* \param[in] data_set Cluster working set
*/
static void
clone_min_ordering(const char *id,
pe_resource_t *rsc_first, const char *action_first,
pe_resource_t *rsc_then, const char *action_then,
uint32_t flags, int clone_min, pe_working_set_t *data_set)
{
// Create a pseudo-action for when the minimum instances are active
char *task = crm_strdup_printf(CRM_OP_RELAXED_CLONE ":%s", id);
pe_action_t *clone_min_met = get_pseudo_op(task, data_set);
free(task);
/* Require the pseudo-action to have the required number of actions to be
* considered runnable before allowing the pseudo-action to be runnable.
*/
clone_min_met->required_runnable_before = clone_min;
pe__set_action_flags(clone_min_met, pe_action_requires_any);
// Order the actions for each clone instance before the pseudo-action
for (GList *rIter = rsc_first->children; rIter != NULL;
rIter = rIter->next) {
pe_resource_t *child = rIter->data;
pcmk__new_ordering(child, pcmk__op_key(child->id, action_first, 0),
NULL, NULL, NULL, clone_min_met,
pe_order_one_or_more|pe_order_implies_then_printed,
data_set);
}
// Order "then" action after the pseudo-action (if runnable)
pcmk__new_ordering(NULL, NULL, clone_min_met, rsc_then,
pcmk__op_key(rsc_then->id, action_then, 0),
NULL, flags|pe_order_runnable_left, data_set);
}
/*!
* \internal
* \brief Update ordering flags for restart-type=restart
*
* \param[in] rsc 'Then' resource in ordering
* \param[in] kind Ordering kind
* \param[in] flag Ordering flag to set (when applicable)
* \param[out] flags Ordering flag set to update
*
* \compat The restart-type resource meta-attribute is deprecated. Eventually,
* it will be removed, and pe_restart_ignore will be the only behavior,
* at which time this can just be removed entirely.
*/
#define handle_restart_type(rsc, kind, flag, flags) do { \
if (((kind) == pe_order_kind_optional) \
&& ((rsc)->restart_type == pe_restart_restart)) { \
pe__set_order_flags((flags), (flag)); \
} \
} while (0)
/*!
* \internal
* \brief Create new ordering for inverse of symmetric constraint
*
* \param[in] id Ordering ID (for logging only)
* \param[in] kind Ordering kind
* \param[in] rsc_first 'First' resource in ordering (a clone)
* \param[in] action_first 'First' action in ordering
* \param[in] rsc_then 'Then' resource in ordering
* \param[in] action_then 'Then' action in ordering
* \param[in] data_set Cluster working set
*/
static void
inverse_ordering(const char *id, enum pe_order_kind kind,
pe_resource_t *rsc_first, const char *action_first,
pe_resource_t *rsc_then, const char *action_then,
pe_working_set_t *data_set)
{
action_then = invert_action(action_then);
action_first = invert_action(action_first);
if ((action_then == NULL) || (action_first == NULL)) {
pcmk__config_warn("Cannot invert constraint '%s' "
"(please specify inverse manually)", id);
} else {
uint32_t flags = ordering_flags_for_kind(kind, action_first,
ordering_symmetric_inverse);
handle_restart_type(rsc_then, kind, pe_order_implies_first, flags);
pcmk__order_resource_actions(rsc_then, action_then, rsc_first,
action_first, flags);
}
}
static void
unpack_simple_rsc_order(xmlNode *xml_obj, pe_working_set_t *data_set)
{
pe_resource_t *rsc_then = NULL;
pe_resource_t *rsc_first = NULL;
int min_required_before = 0;
enum pe_order_kind kind = pe_order_kind_mandatory;
uint32_t cons_weight = pe_order_none;
enum ordering_symmetry symmetry;
const char *action_then = NULL;
const char *action_first = NULL;
const char *id = NULL;
CRM_CHECK(xml_obj != NULL, return);
id = crm_element_value(xml_obj, XML_ATTR_ID);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
crm_element_name(xml_obj));
return;
}
rsc_first = get_ordering_resource(xml_obj, XML_ORDER_ATTR_FIRST,
XML_ORDER_ATTR_FIRST_INSTANCE,
data_set);
if (rsc_first == NULL) {
return;
}
rsc_then = get_ordering_resource(xml_obj, XML_ORDER_ATTR_THEN,
XML_ORDER_ATTR_THEN_INSTANCE,
data_set);
if (rsc_then == NULL) {
return;
}
action_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST_ACTION);
if (action_first == NULL) {
action_first = RSC_START;
}
action_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN_ACTION);
if (action_then == NULL) {
action_then = action_first;
}
kind = get_ordering_type(xml_obj);
symmetry = get_ordering_symmetry(xml_obj, kind, NULL);
cons_weight = ordering_flags_for_kind(kind, action_first, symmetry);
handle_restart_type(rsc_then, kind, pe_order_implies_then, cons_weight);
/* If there is a minimum number of instances that must be runnable before
* the 'then' action is runnable, we use a pseudo-action for convenience:
* minimum number of clone instances have runnable actions ->
* pseudo-action is runnable -> dependency is runnable.
*/
min_required_before = get_minimum_first_instances(rsc_first, xml_obj);
if (min_required_before > 0) {
clone_min_ordering(id, rsc_first, action_first, rsc_then, action_then,
cons_weight, min_required_before, data_set);
} else {
pcmk__order_resource_actions(rsc_first, action_first, rsc_then,
action_then, cons_weight);
}
if (symmetry == ordering_symmetric) {
inverse_ordering(id, kind, rsc_first, action_first,
rsc_then, action_then, data_set);
}
}
/*!
* \internal
* \brief Create a new ordering between two actions
*
* \param[in] first_rsc Resource for 'first' action (if NULL and
* \p first_action is a resource action, that
* resource will be used)
* \param[in] first_action_task Action key for 'first' action (if NULL and
* \p first_action is not NULL, its UUID will be
* used)
* \param[in] first_action 'first' action (if NULL, \p first_rsc and
* \p first_action_task must be set)
*
* \param[in] then_rsc Resource for 'then' action (if NULL and
* \p then_action is a resource action, that
* resource will be used)
* \param[in] then_action_task Action key for 'then' action (if NULL and
* \p then_action is not NULL, its UUID will be
* used)
* \param[in] then_action 'then' action (if NULL, \p then_rsc and
* \p then_action_task must be set)
*
* \param[in] type Flag set of enum pe_ordering
* \param[in] data_set Cluster working set to add ordering to
*
* \note This function takes ownership of first_action_task and
* then_action_task, which do not need to be freed by the caller.
*/
void
pcmk__new_ordering(pe_resource_t *first_rsc, char *first_action_task,
pe_action_t *first_action, pe_resource_t *then_rsc,
char *then_action_task, pe_action_t *then_action,
uint32_t flags, pe_working_set_t *data_set)
{
pe__ordering_t *order = NULL;
// One of action or resource must be specified for each side
CRM_CHECK(((first_action != NULL) || (first_rsc != NULL))
&& ((then_action != NULL) || (then_rsc != NULL)),
free(first_action_task); free(then_action_task); return);
if ((first_rsc == NULL) && (first_action != NULL)) {
first_rsc = first_action->rsc;
}
if ((then_rsc == NULL) && (then_action != NULL)) {
then_rsc = then_action->rsc;
}
order = calloc(1, sizeof(pe__ordering_t));
CRM_ASSERT(order != NULL);
order->id = data_set->order_id++;
order->flags = flags;
order->lh_rsc = first_rsc;
order->rh_rsc = then_rsc;
order->lh_action = first_action;
order->rh_action = then_action;
order->lh_action_task = first_action_task;
order->rh_action_task = then_action_task;
if ((order->lh_action_task == NULL) && (first_action != NULL)) {
order->lh_action_task = strdup(first_action->uuid);
}
if ((order->rh_action_task == NULL) && (then_action != NULL)) {
order->rh_action_task = strdup(then_action->uuid);
}
if ((order->lh_rsc == NULL) && (first_action != NULL)) {
order->lh_rsc = first_action->rsc;
}
if ((order->rh_rsc == NULL) && (then_action != NULL)) {
order->rh_rsc = then_action->rsc;
}
pe_rsc_trace(first_rsc, "Created ordering %d for %s then %s",
(data_set->order_id - 1),
((first_action_task == NULL)? "?" : first_action_task),
((then_action_task == NULL)? "?" : then_action_task));
data_set->ordering_constraints = g_list_prepend(data_set->ordering_constraints,
order);
pcmk__order_migration_equivalents(order);
}
/*!
* \brief Unpack a set in an ordering constraint
*
* \param[in] set Set XML to unpack
* \param[in] parent_kind rsc_order XML "kind" attribute
* \param[in] parent_symmetrical_s rsc_order XML "symmetrical" attribute
* \param[in] data_set Cluster working set
*
* \return Standard Pacemaker return code
*/
static int
unpack_order_set(xmlNode *set, enum pe_order_kind parent_kind,
const char *parent_symmetrical_s, pe_working_set_t *data_set)
{
xmlNode *xml_rsc = NULL;
GList *set_iter = NULL;
GList *resources = NULL;
pe_resource_t *last = NULL;
pe_resource_t *resource = NULL;
int local_kind = parent_kind;
bool sequential = false;
uint32_t flags = pe_order_optional;
enum ordering_symmetry symmetry;
char *key = NULL;
const char *id = ID(set);
const char *action = crm_element_value(set, "action");
const char *sequential_s = crm_element_value(set, "sequential");
const char *kind_s = crm_element_value(set, XML_ORDER_ATTR_KIND);
if (action == NULL) {
action = RSC_START;
}
if (kind_s) {
local_kind = get_ordering_type(set);
}
if (sequential_s == NULL) {
sequential_s = "1";
}
sequential = crm_is_true(sequential_s);
symmetry = get_ordering_symmetry(set, parent_kind, parent_symmetrical_s);
flags = ordering_flags_for_kind(local_kind, action, symmetry);
for (xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, resource, ID(xml_rsc));
resources = g_list_append(resources, resource);
}
if (pcmk__list_of_1(resources)) {
crm_trace("Single set: %s", id);
goto done;
}
set_iter = resources;
while (set_iter != NULL) {
resource = (pe_resource_t *) set_iter->data;
set_iter = set_iter->next;
key = pcmk__op_key(resource->id, action, 0);
if (local_kind == pe_order_kind_serialize) {
/* Serialize before everything that comes after */
for (GList *gIter = set_iter; gIter != NULL; gIter = gIter->next) {
pe_resource_t *then_rsc = (pe_resource_t *) gIter->data;
char *then_key = pcmk__op_key(then_rsc->id, action, 0);
pcmk__new_ordering(resource, strdup(key), NULL, then_rsc,
then_key, NULL, flags, data_set);
}
} else if (sequential) {
if (last != NULL) {
pcmk__order_resource_actions(last, action, resource, action,
flags);
}
last = resource;
}
free(key);
}
if (symmetry == ordering_asymmetric) {
goto done;
}
last = NULL;
action = invert_action(action);
flags = ordering_flags_for_kind(local_kind, action,
ordering_symmetric_inverse);
set_iter = resources;
while (set_iter != NULL) {
resource = (pe_resource_t *) set_iter->data;
set_iter = set_iter->next;
if (sequential) {
if (last != NULL) {
pcmk__order_resource_actions(resource, action, last, action,
flags);
}
last = resource;
}
}
done:
g_list_free(resources);
return pcmk_rc_ok;
}
/*!
* \brief Order two resource sets relative to each other
*
* \param[in] id Ordering ID (for logging)
* \param[in] set1 First listed set
* \param[in] set2 Second listed set
* \param[in] kind Ordering kind
* \param[in] data_set Cluster working set
* \param[in] symmetry Which ordering symmetry applies to this relation
*
* \return Standard Pacemaker return code
*/
static int
order_rsc_sets(const char *id, xmlNode *set1, xmlNode *set2,
enum pe_order_kind kind, pe_working_set_t *data_set,
enum ordering_symmetry symmetry)
{
xmlNode *xml_rsc = NULL;
xmlNode *xml_rsc_2 = NULL;
pe_resource_t *rsc_1 = NULL;
pe_resource_t *rsc_2 = NULL;
const char *action_1 = crm_element_value(set1, "action");
const char *action_2 = crm_element_value(set2, "action");
uint32_t flags = pe_order_none;
bool require_all = true;
(void) pcmk__xe_get_bool_attr(set1, "require-all", &require_all);
if (action_1 == NULL) {
action_1 = RSC_START;
}
if (action_2 == NULL) {
action_2 = RSC_START;
}
if (symmetry == ordering_symmetric_inverse) {
action_1 = invert_action(action_1);
action_2 = invert_action(action_2);
}
if (pcmk__str_eq(RSC_STOP, action_1, pcmk__str_casei)
|| pcmk__str_eq(RSC_DEMOTE, action_1, pcmk__str_casei)) {
/* Assuming: A -> ( B || C) -> D
* The one-or-more logic only applies during the start/promote phase.
* During shutdown neither B nor can shutdown until D is down, so simply
* turn require_all back on.
*/
require_all = true;
}
// @TODO is action_2 correct here?
flags = ordering_flags_for_kind(kind, action_2, symmetry);
/* If we have an unordered set1, whether it is sequential or not is
* irrelevant in regards to set2.
*/
if (!require_all) {
char *task = crm_strdup_printf(CRM_OP_RELAXED_SET ":%s", ID(set1));
pe_action_t *unordered_action = get_pseudo_op(task, data_set);
free(task);
pe__set_action_flags(unordered_action, pe_action_requires_any);
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
/* Add an ordering constraint between every element in set1 and the
* pseudo action. If any action in set1 is runnable the pseudo
* action will be runnable.
*/
pcmk__new_ordering(rsc_1, pcmk__op_key(rsc_1->id, action_1, 0),
NULL, NULL, NULL, unordered_action,
pe_order_one_or_more|pe_order_implies_then_printed,
data_set);
}
for (xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc_2 != NULL; xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
/* Add an ordering constraint between the pseudo-action and every
* element in set2. If the pseudo-action is runnable, every action
* in set2 will be runnable.
*/
pcmk__new_ordering(NULL, NULL, unordered_action,
rsc_2, pcmk__op_key(rsc_2->id, action_2, 0),
NULL, flags|pe_order_runnable_left, data_set);
}
return pcmk_rc_ok;
}
if (pcmk__xe_attr_is_true(set1, "sequential")) {
if (symmetry == ordering_symmetric_inverse) {
// Get the first one
xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
if (xml_rsc != NULL) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
}
} else {
// Get the last one
const char *rid = NULL;
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
rid = ID(xml_rsc);
}
EXPAND_CONSTRAINT_IDREF(id, rsc_1, rid);
}
}
if (pcmk__xe_attr_is_true(set2, "sequential")) {
if (symmetry == ordering_symmetric_inverse) {
// Get the last one
const char *rid = NULL;
for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
rid = ID(xml_rsc);
}
EXPAND_CONSTRAINT_IDREF(id, rsc_2, rid);
} else {
// Get the first one
xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
if (xml_rsc != NULL) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
}
}
}
if ((rsc_1 != NULL) && (rsc_2 != NULL)) {
pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2, flags);
} else if (rsc_1 != NULL) {
for (xml_rsc = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc));
pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
flags);
}
} else if (rsc_2 != NULL) {
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
pcmk__order_resource_actions(rsc_1, action_1, rsc_2, action_2,
flags);
}
} else {
for (xml_rsc = first_named_child(set1, XML_TAG_RESOURCE_REF);
xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_1, ID(xml_rsc));
for (xmlNode *xml_rsc_2 = first_named_child(set2, XML_TAG_RESOURCE_REF);
xml_rsc_2 != NULL; xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
EXPAND_CONSTRAINT_IDREF(id, rsc_2, ID(xml_rsc_2));
pcmk__order_resource_actions(rsc_1, action_1, rsc_2,
action_2, flags);
}
}
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief If an ordering constraint uses resource tags, expand them
*
* \param[in] xml_obj Ordering constraint XML
* \param[out] expanded_xml Equivalent XML with tags expanded
* \param[in] data_set Cluster working set
*
* \return Standard Pacemaker return code (specifically, pcmk_rc_ok on success,
* and pcmk_rc_unpack_error on invalid configuration)
*/
static int
unpack_order_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
pe_working_set_t *data_set)
{
const char *id_first = NULL;
const char *id_then = NULL;
const char *action_first = NULL;
const char *action_then = NULL;
pe_resource_t *rsc_first = NULL;
pe_resource_t *rsc_then = NULL;
pe_tag_t *tag_first = NULL;
pe_tag_t *tag_then = NULL;
xmlNode *rsc_set_first = NULL;
xmlNode *rsc_set_then = NULL;
bool any_sets = false;
// Check whether there are any resource sets with template or tag references
*expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
if (*expanded_xml != NULL) {
crm_log_xml_trace(*expanded_xml, "Expanded rsc_order");
return pcmk_rc_ok;
}
id_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST);
id_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN);
if ((id_first == NULL) || (id_then == NULL)) {
return pcmk_rc_ok;
}
if (!pcmk__valid_resource_or_tag(data_set, id_first, &rsc_first,
&tag_first)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", ID(xml_obj), id_first);
return pcmk_rc_unpack_error;
}
if (!pcmk__valid_resource_or_tag(data_set, id_then, &rsc_then, &tag_then)) {
pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
"valid resource or tag", ID(xml_obj), id_then);
return pcmk_rc_unpack_error;
}
if ((rsc_first != NULL) && (rsc_then != NULL)) {
// Neither side references a template or tag
return pcmk_rc_ok;
}
action_first = crm_element_value(xml_obj, XML_ORDER_ATTR_FIRST_ACTION);
action_then = crm_element_value(xml_obj, XML_ORDER_ATTR_THEN_ACTION);
*expanded_xml = copy_xml(xml_obj);
// Convert template/tag reference in "first" into resource_set under constraint
if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_first, XML_ORDER_ATTR_FIRST,
true, data_set)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (rsc_set_first != NULL) {
if (action_first != NULL) {
// Move "first-action" into converted resource_set as "action"
crm_xml_add(rsc_set_first, "action", action_first);
xml_remove_prop(*expanded_xml, XML_ORDER_ATTR_FIRST_ACTION);
}
any_sets = true;
}
// Convert template/tag reference in "then" into resource_set under constraint
if (!pcmk__tag_to_set(*expanded_xml, &rsc_set_then, XML_ORDER_ATTR_THEN,
true, data_set)) {
free_xml(*expanded_xml);
*expanded_xml = NULL;
return pcmk_rc_unpack_error;
}
if (rsc_set_then != NULL) {
if (action_then != NULL) {
// Move "then-action" into converted resource_set as "action"
crm_xml_add(rsc_set_then, "action", action_then);
xml_remove_prop(*expanded_xml, XML_ORDER_ATTR_THEN_ACTION);
}
any_sets = true;
}
if (any_sets) {
crm_log_xml_trace(*expanded_xml, "Expanded rsc_order");
} else {
free_xml(*expanded_xml);
*expanded_xml = NULL;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Unpack ordering constraint XML
*
* \param[in] xml_obj Ordering constraint XML to unpack
* \param[in,out] data_set Cluster working set
*/
void
pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set)
{
xmlNode *set = NULL;
xmlNode *last = NULL;
xmlNode *orig_xml = NULL;
xmlNode *expanded_xml = NULL;
const char *id = crm_element_value(xml_obj, XML_ATTR_ID);
const char *invert = crm_element_value(xml_obj, XML_CONS_ATTR_SYMMETRICAL);
enum pe_order_kind kind = get_ordering_type(xml_obj);
enum ordering_symmetry symmetry = get_ordering_symmetry(xml_obj, kind,
NULL);
// Expand any resource tags in the constraint XML
if (unpack_order_tags(xml_obj, &expanded_xml, data_set) != pcmk_rc_ok) {
return;
}
if (expanded_xml != NULL) {
orig_xml = xml_obj;
xml_obj = expanded_xml;
}
// If the constraint has resource sets, unpack them
for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET);
set != NULL; set = crm_next_same_xml(set)) {
set = expand_idref(set, data_set->input);
if ((set == NULL) // Configuration error, message already logged
|| (unpack_order_set(set, kind, invert, data_set) != pcmk_rc_ok)) {
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
if (last != NULL) {
if (order_rsc_sets(id, last, set, kind, data_set,
symmetry) != pcmk_rc_ok) {
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
if ((symmetry == ordering_symmetric)
&& (order_rsc_sets(id, set, last, kind, data_set,
ordering_symmetric_inverse) != pcmk_rc_ok)) {
if (expanded_xml != NULL) {
free_xml(expanded_xml);
}
return;
}
}
last = set;
}
if (expanded_xml) {
free_xml(expanded_xml);
xml_obj = orig_xml;
}
// If the constraint has no resource sets, unpack it as a simple ordering
if (last == NULL) {
return unpack_simple_rsc_order(xml_obj, data_set);
}
}
static bool
ordering_is_invalid(pe_action_t *action, pe_action_wrapper_t *input)
{
/* Prevent user-defined ordering constraints between resources
* running in a guest node and the resource that defines that node.
*/
if (!pcmk_is_set(input->type, pe_order_preserve)
&& (input->action->rsc != NULL)
&& pcmk__rsc_corresponds_to_guest(action->rsc, input->action->node)) {
crm_warn("Invalid ordering constraint between %s and %s",
input->action->rsc->id, action->rsc->id);
return true;
}
/* If there's an order like
* "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1"
*
* then rscA is being migrated from node1 to node2, while rscB is being
* migrated from node2 to node1. If there would be a graph loop,
* break the order "load_stopped_node2" -> "rscA_migrate_to node1".
*/
if ((input->type == pe_order_load) && action->rsc
&& pcmk__str_eq(action->task, RSC_MIGRATE, pcmk__str_casei)
&& pcmk__graph_has_loop(action, action, input)) {
return true;
}
return false;
}
void
pcmk__disable_invalid_orderings(pe_working_set_t *data_set)
{
for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
pe_action_t *action = (pe_action_t *) iter->data;
pe_action_wrapper_t *input = NULL;
for (GList *input_iter = action->actions_before;
input_iter != NULL; input_iter = input_iter->next) {
input = (pe_action_wrapper_t *) input_iter->data;
if (ordering_is_invalid(action, input)) {
input->type = pe_order_none;
}
}
}
}
/*!
* \internal
* \brief Order stops on a node before the node's shutdown
*
* \param[in] node Node being shut down
* \param[in] shutdown_op Shutdown action for node
*/
void
pcmk__order_stops_before_shutdown(pe_node_t *node, pe_action_t *shutdown_op)
{
for (GList *iter = node->details->data_set->actions;
iter != NULL; iter = iter->next) {
pe_action_t *action = (pe_action_t *) iter->data;
// Only stops on the node shutting down are relevant
if ((action->rsc == NULL) || (action->node == NULL)
|| (action->node->details != node->details)
|| !pcmk__str_eq(action->task, RSC_STOP, pcmk__str_casei)) {
continue;
}
// Resources and nodes in maintenance mode won't be touched
if (pcmk_is_set(action->rsc->flags, pe_rsc_maintenance)) {
pe_rsc_trace(action->rsc,
"Not ordering %s before shutdown of %s because "
"resource in maintenance mode",
action->uuid, pe__node_name(node));
continue;
} else if (node->details->maintenance) {
pe_rsc_trace(action->rsc,
"Not ordering %s before shutdown of %s because "
"node in maintenance mode",
action->uuid, pe__node_name(node));
continue;
}
/* Don't touch a resource that is unmanaged or blocked, to avoid
* blocking the shutdown (though if another action depends on this one,
* we may still end up blocking)
*/
if (!pcmk_any_flags_set(action->rsc->flags,
pe_rsc_managed|pe_rsc_block)) {
pe_rsc_trace(action->rsc,
"Not ordering %s before shutdown of %s because "
"resource is unmanaged or blocked",
action->uuid, pe__node_name(node));
continue;
}
pe_rsc_trace(action->rsc, "Ordering %s before shutdown of %s",
action->uuid, pe__node_name(node));
pe__clear_action_flags(action, pe_action_optional);
pcmk__new_ordering(action->rsc, NULL, action, NULL,
strdup(CRM_OP_SHUTDOWN), shutdown_op,
pe_order_optional|pe_order_runnable_left,
node->details->data_set);
}
}
/*!
* \brief Find resource actions matching directly or as child
*
* \param[in] rsc Resource to check
* \param[in] original_key Action key to search for (possibly referencing
* parent of \rsc)
*
* \return Newly allocated list of matching actions
* \note It is the caller's responsibility to free the result with g_list_free()
*/
static GList *
find_actions_by_task(const pe_resource_t *rsc, const char *original_key)
{
// Search under given task key directly
GList *list = find_actions(rsc->actions, original_key, NULL);
if (list == NULL) {
// Search again using this resource's ID
char *key = NULL;
char *task = NULL;
guint interval_ms = 0;
if (parse_op_key(original_key, NULL, &task, &interval_ms)) {
key = pcmk__op_key(rsc->id, task, interval_ms);
list = find_actions(rsc->actions, key, NULL);
free(key);
free(task);
} else {
crm_err("Invalid operation key (bug?): %s", original_key);
}
}
return list;
}
/*!
* \internal
* \brief Order relevant resource actions after a given action
*
* \param[in,out] first_action Action to order after (or NULL if none runnable)
* \param[in] rsc Resource whose actions should be ordered
* \param[in,out] order Ordering constraint being applied
*/
static void
order_resource_actions_after(pe_action_t *first_action,
const pe_resource_t *rsc, pe__ordering_t *order)
{
GList *then_actions = NULL;
uint32_t flags = pe_order_none;
CRM_CHECK((rsc != NULL) && (order != NULL), return);
flags = order->flags;
pe_rsc_trace(rsc, "Applying ordering %d for 'then' resource %s",
order->id, rsc->id);
if (order->rh_action != NULL) {
then_actions = g_list_prepend(NULL, order->rh_action);
} else {
then_actions = find_actions_by_task(rsc, order->rh_action_task);
}
if (then_actions == NULL) {
pe_rsc_trace(rsc, "Ignoring ordering %d: no %s actions found for %s",
order->id, order->rh_action_task, rsc->id);
return;
}
if ((first_action != NULL) && (first_action->rsc == rsc)
&& pcmk_is_set(first_action->flags, pe_action_dangle)) {
pe_rsc_trace(rsc,
"Detected dangling migration ordering (%s then %s %s)",
first_action->uuid, order->rh_action_task, rsc->id);
pe__clear_order_flags(flags, pe_order_implies_then);
}
if ((first_action == NULL) && !pcmk_is_set(flags, pe_order_implies_then)) {
pe_rsc_debug(rsc,
"Ignoring ordering %d for %s: No first action found",
order->id, rsc->id);
g_list_free(then_actions);
return;
}
for (GList *iter = then_actions; iter != NULL; iter = iter->next) {
pe_action_t *then_action_iter = (pe_action_t *) iter->data;
if (first_action != NULL) {
order_actions(first_action, then_action_iter, flags);
} else {
pe__clear_action_flags(then_action_iter, pe_action_runnable);
crm_warn("%s of %s is unrunnable because there is no %s of %s "
"to order it after", then_action_iter->task, rsc->id,
order->lh_action_task, order->lh_rsc->id);
}
}
g_list_free(then_actions);
}
static void
rsc_order_first(pe_resource_t *first_rsc, pe__ordering_t *order,
pe_working_set_t *data_set)
{
GList *first_actions = NULL;
pe_action_t *first_action = order->lh_action;
pe_resource_t *then_rsc = order->rh_rsc;
CRM_ASSERT(first_rsc != NULL);
pe_rsc_trace(first_rsc, "Applying ordering constraint %d (first: %s)",
order->id, first_rsc->id);
if (first_action != NULL) {
first_actions = g_list_prepend(NULL, first_action);
} else {
first_actions = find_actions_by_task(first_rsc, order->lh_action_task);
}
if ((first_actions == NULL) && (first_rsc == then_rsc)) {
pe_rsc_trace(first_rsc,
"Ignoring constraint %d: first (%s for %s) not found",
order->id, order->lh_action_task, first_rsc->id);
} else if (first_actions == NULL) {
char *key = NULL;
char *op_type = NULL;
guint interval_ms = 0;
parse_op_key(order->lh_action_task, NULL, &op_type, &interval_ms);
key = pcmk__op_key(first_rsc->id, op_type, interval_ms);
if ((first_rsc->fns->state(first_rsc, TRUE) == RSC_ROLE_STOPPED)
&& pcmk__str_eq(op_type, RSC_STOP, pcmk__str_casei)) {
free(key);
pe_rsc_trace(first_rsc,
"Ignoring constraint %d: first (%s for %s) not found",
order->id, order->lh_action_task, first_rsc->id);
} else if ((first_rsc->fns->state(first_rsc, TRUE) == RSC_ROLE_UNPROMOTED)
&& pcmk__str_eq(op_type, RSC_DEMOTE, pcmk__str_casei)) {
free(key);
pe_rsc_trace(first_rsc,
"Ignoring constraint %d: first (%s for %s) not found",
order->id, order->lh_action_task, first_rsc->id);
} else {
pe_rsc_trace(first_rsc,
"Creating first (%s for %s) for constraint %d ",
order->lh_action_task, first_rsc->id, order->id);
first_action = custom_action(first_rsc, key, op_type, NULL, TRUE,
TRUE, data_set);
first_actions = g_list_prepend(NULL, first_action);
}
free(op_type);
}
if (then_rsc == NULL) {
if (order->rh_action == NULL) {
pe_rsc_trace(first_rsc, "Ignoring constraint %d: then not found",
order->id);
return;
}
then_rsc = order->rh_action->rsc;
}
for (GList *gIter = first_actions; gIter != NULL; gIter = gIter->next) {
first_action = (pe_action_t *) gIter->data;
if (then_rsc == NULL) {
order_actions(first_action, order->rh_action, order->flags);
} else {
order_resource_actions_after(first_action, then_rsc, order);
}
}
g_list_free(first_actions);
}
void
pcmk__apply_orderings(pe_working_set_t *data_set)
{
crm_trace("Applying ordering constraints");
/* Ordering constraints need to be processed in the order they were created.
* rsc_order_first() and order_resource_actions_after() require the relevant
* actions to already exist in some cases, but rsc_order_first() will create
* the 'first' action in certain cases. Thus calling rsc_order_first() can
* change the behavior of later-created orderings.
*
* Also, g_list_append() should be avoided for performance reasons, so we
* prepend orderings when creating them and reverse the list here.
*
* @TODO This is brittle and should be carefully redesigned so that the
* order of creation doesn't matter, and the reverse becomes unneeded.
*/
data_set->ordering_constraints = g_list_reverse(data_set->ordering_constraints);
for (GList *gIter = data_set->ordering_constraints;
gIter != NULL; gIter = gIter->next) {
pe__ordering_t *order = gIter->data;
pe_resource_t *rsc = order->lh_rsc;
if (rsc != NULL) {
rsc_order_first(rsc, order, data_set);
continue;
}
rsc = order->rh_rsc;
if (rsc != NULL) {
order_resource_actions_after(order->lh_action, rsc, order);
} else {
crm_trace("Applying ordering constraint %d (non-resource actions)",
order->id);
order_actions(order->lh_action, order->rh_action, order->flags);
}
}
- g_list_foreach(data_set->actions, (GFunc) pcmk__block_colocated_starts,
+ g_list_foreach(data_set->actions, (GFunc) pcmk__block_colocation_dependents,
data_set);
crm_trace("Ordering probes");
pcmk__order_probes(data_set);
crm_trace("Updating %d actions", g_list_length(data_set->actions));
g_list_foreach(data_set->actions,
(GFunc) pcmk__update_action_for_orderings, data_set);
pcmk__disable_invalid_orderings(data_set);
}
/*!
* \internal
* \brief Order a given action after each action in a given list
*
* \param[in] after "After" action
* \param[in] list List of "before" actions
*/
void
pcmk__order_after_each(pe_action_t *after, GList *list)
{
const char *after_desc = (after->task == NULL)? after->uuid : after->task;
for (GList *iter = list; iter != NULL; iter = iter->next) {
pe_action_t *before = (pe_action_t *) iter->data;
const char *before_desc = before->task? before->task : before->uuid;
crm_debug("Ordering %s on %s before %s on %s",
before_desc, pe__node_name(before->node),
after_desc, pe__node_name(after->node));
order_actions(before, after, pe_order_optional);
}
}
/*!
* \internal
* \brief Order promotions and demotions for restarts of a clone or bundle
*
* \param[in] rsc Clone or bundle to order
*/
void
pcmk__promotable_restart_ordering(pe_resource_t *rsc)
{
// Order start and promote after all instances are stopped
pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_START,
pe_order_optional);
pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_PROMOTE,
pe_order_optional);
// Order stop, start, and promote after all instances are demoted
pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_STOP,
pe_order_optional);
pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_START,
pe_order_optional);
pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_PROMOTE,
pe_order_optional);
// Order promote after all instances are started
pcmk__order_resource_actions(rsc, RSC_STARTED, rsc, RSC_PROMOTE,
pe_order_optional);
// Order demote after all instances are demoted
pcmk__order_resource_actions(rsc, RSC_DEMOTE, rsc, RSC_DEMOTED,
pe_order_optional);
}