diff --git a/cts/cts-scheduler.in b/cts/cts-scheduler.in
index dd26c27d95..dc4a9489d9 100644
--- a/cts/cts-scheduler.in
+++ b/cts/cts-scheduler.in
@@ -1,1744 +1,1743 @@
#!@PYTHON@
""" Regression tests for Pacemaker's scheduler
"""
__copyright__ = "Copyright 2004-2024 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"
import io
import os
import re
import sys
import stat
import shlex
import shutil
import argparse
import subprocess
import platform
import tempfile
# These imports allow running from a source checkout after running `make`.
# Note that while this doesn't necessarily mean it will successfully run tests,
# but being able to see --help output can be useful.
if os.path.exists("@abs_top_srcdir@/python"):
sys.path.insert(0, "@abs_top_srcdir@/python")
if os.path.exists("@abs_top_builddir@/python") and "@abs_top_builddir@" != "@abs_top_srcdir@":
sys.path.insert(0, "@abs_top_builddir@/python")
from pacemaker.buildoptions import BuildOptions
from pacemaker.exitstatus import ExitStatus
DESC = """Regression tests for Pacemaker's scheduler"""
class SchedulerTest:
def __init__(self, name, desc, args=None):
self.name = name
self.desc = desc
if args is None:
self.args = []
else:
self.args = args
class SchedulerTestGroup:
def __init__(self, tests):
self.tests = tests
# Each entry in TESTS is a group of tests, where each test consists of a
# test base name, test description, and additional test arguments.
# Test groups will be separated by newlines in output.
TESTS = [
SchedulerTestGroup([
SchedulerTest("simple1", "Offline"),
SchedulerTest("simple2", "Start"),
SchedulerTest("simple3", "Start 2"),
SchedulerTest("simple4", "Start Failed"),
SchedulerTest("simple6", "Stop Start"),
SchedulerTest("simple7", "Shutdown"),
SchedulerTest("simple11", "Priority (ne)"),
SchedulerTest("simple12", "Priority (eq)"),
SchedulerTest("simple8", "Stickiness"),
]),
SchedulerTestGroup([
SchedulerTest("group1", "Group"),
SchedulerTest("group2", "Group + Native"),
SchedulerTest("group3", "Group + Group"),
SchedulerTest("group4", "Group + Native (nothing)"),
SchedulerTest("group5", "Group + Native (move)"),
SchedulerTest("group6", "Group + Group (move)"),
SchedulerTest("group7", "Group colocation"),
SchedulerTest("group13", "Group colocation (cant run)"),
SchedulerTest("group8", "Group anti-colocation"),
SchedulerTest("group9", "Group recovery"),
SchedulerTest("group10", "Group partial recovery"),
SchedulerTest("group11", "Group target_role"),
SchedulerTest("group14", "Group stop (graph terminated)"),
SchedulerTest("group15", "Negative group colocation"),
SchedulerTest("bug-1573", "Partial stop of a group with two children"),
SchedulerTest("bug-1718", "Mandatory group ordering - Stop group_FUN"),
SchedulerTest("failed-sticky-group", "Move group on last member failure despite infinite stickiness"),
SchedulerTest("failed-sticky-anticolocated-group",
"Move group on last member failure despite infinite stickiness and optional anti-colocation"),
SchedulerTest("bug-lf-2619", "Move group on clone failure"),
SchedulerTest("group-fail", "Ensure stop order is preserved for partially active groups"),
SchedulerTest("group-unmanaged", "No need to restart r115 because r114 is unmanaged"),
SchedulerTest("group-unmanaged-stopped", "Make sure r115 is stopped when r114 fails"),
SchedulerTest("partial-unmanaged-group", "New member in partially unmanaged group"),
SchedulerTest("group-dependents", "Account for the location preferences of things colocated with a group"),
SchedulerTest("group-stop-ordering", "Ensure blocked group member stop does not force other member stops"),
SchedulerTest("colocate-unmanaged-group", "Respect mandatory colocations even if earlier group member is unmanaged"),
SchedulerTest("coloc-with-inner-group-member", "Consider explicit colocations with inner group members"),
SchedulerTest("banned-group-inner-constraints",
"Group banned from current node, inner member constrained"),
]),
SchedulerTestGroup([
SchedulerTest("rsc_dep1", "Must not"),
SchedulerTest("rsc_dep3", "Must"),
SchedulerTest("rsc_dep5", "Must not 3"),
SchedulerTest("rsc_dep7", "Must 3"),
SchedulerTest("rsc_dep10", "Must (but cant)"),
SchedulerTest("rsc_dep2", "Must (running)"),
SchedulerTest("rsc_dep8", "Must (running : alt)"),
SchedulerTest("rsc_dep4", "Must (running + move)"),
SchedulerTest("asymmetric", "Asymmetric - require explicit location constraints"),
]),
SchedulerTestGroup([
SchedulerTest("orphan-0", "Orphan ignore"),
SchedulerTest("orphan-1", "Orphan stop"),
SchedulerTest("orphan-2", "Orphan stop, remove failcount"),
]),
SchedulerTestGroup([
SchedulerTest("params-0", "Params: No change"),
SchedulerTest("params-1", "Params: Changed"),
SchedulerTest("params-2", "Params: Resource definition"),
SchedulerTest("params-3", "Params: Restart instead of reload if start pending"),
SchedulerTest("params-4", "Params: Reload"),
SchedulerTest("params-5", "Params: Restart based on probe digest"),
SchedulerTest("novell-251689", "Resource definition change + target_role=stopped"),
SchedulerTest("bug-lf-2106", "Restart all anonymous clone instances after config change"),
SchedulerTest("params-6", "Params: Detect reload in previously migrated resource"),
SchedulerTest("nvpair-id-ref", "Support id-ref in nvpair with optional name"),
SchedulerTest("not-reschedule-unneeded-monitor",
"Do not reschedule unneeded monitors while resource definitions have changed"),
SchedulerTest("reload-becomes-restart", "Cancel reload if restart becomes required"),
SchedulerTest("restart-with-extra-op-params", "Restart if with extra operation parameters upon changes of any"),
]),
SchedulerTestGroup([
SchedulerTest("target-0", "Target Role : baseline"),
SchedulerTest("target-1", "Target Role : promoted"),
SchedulerTest("target-2", "Target Role : invalid"),
]),
SchedulerTestGroup([
SchedulerTest("base-score", "Set a node's default score for all nodes"),
]),
SchedulerTestGroup([
SchedulerTest("date-1", "Dates", ["-t", "2005-020"]),
SchedulerTest("date-2", "Date Spec - Pass", ["-t", "2005-020T12:30"]),
SchedulerTest("date-3", "Date Spec - Fail", ["-t", "2005-020T11:30"]),
SchedulerTest("origin", "Timing of recurring operations", ["-t", "2014-05-07 00:28:00"]),
SchedulerTest("probe-0", "Probe (anon clone)"),
SchedulerTest("probe-1", "Pending Probe"),
SchedulerTest("probe-2", "Correctly re-probe cloned groups"),
SchedulerTest("probe-3", "Probe (pending node)"),
SchedulerTest("probe-4", "Probe (pending node + stopped resource)"),
SchedulerTest("probe-pending-node", "Probe (pending node + unmanaged resource)"),
SchedulerTest("failed-probe-primitive", "Maskable vs. unmaskable probe failures on primitive resources"),
SchedulerTest("failed-probe-clone", "Maskable vs. unmaskable probe failures on cloned resources"),
SchedulerTest("expired-failed-probe-primitive", "Maskable, expired probe failure on primitive resources"),
SchedulerTest("standby", "Standby"),
SchedulerTest("comments", "Comments"),
]),
SchedulerTestGroup([
SchedulerTest("one-or-more-0", "Everything starts"),
SchedulerTest("one-or-more-1", "Nothing starts because of A"),
SchedulerTest("one-or-more-2", "D can start because of C"),
SchedulerTest("one-or-more-3", "D cannot start because of B and C"),
SchedulerTest("one-or-more-4", "D cannot start because of target-role"),
SchedulerTest("one-or-more-5", "Start A and F even though C and D are stopped"),
SchedulerTest("one-or-more-6", "Leave A running even though B is stopped"),
SchedulerTest("one-or-more-7", "Leave A running even though C is stopped"),
SchedulerTest("bug-5140-require-all-false", "Allow basegrp:0 to stop"),
SchedulerTest("clone-require-all-1", "clone B starts node 3 and 4"),
SchedulerTest("clone-require-all-2", "clone B remains stopped everywhere"),
SchedulerTest("clone-require-all-3", "clone B stops everywhere because A stops everywhere"),
SchedulerTest("clone-require-all-4", "clone B remains on node 3 and 4 with only one instance of A remaining"),
SchedulerTest("clone-require-all-5", "clone B starts on node 1 3 and 4"),
SchedulerTest("clone-require-all-6", "clone B remains active after shutting down instances of A"),
SchedulerTest("clone-require-all-7",
"clone A and B both start at the same time. all instances of A start before B"),
SchedulerTest("clone-require-all-no-interleave-1", "C starts everywhere after A and B"),
SchedulerTest("clone-require-all-no-interleave-2",
"C starts on nodes 1, 2, and 4 with only one active instance of B"),
SchedulerTest("clone-require-all-no-interleave-3",
"C remains active when instance of B is stopped on one node and started on another"),
SchedulerTest("one-or-more-unrunnable-instances", "Avoid dependencies on instances that won't ever be started"),
]),
SchedulerTestGroup([
SchedulerTest("location-date-rules-1", "Use location constraints with ineffective date-based rules"),
SchedulerTest("location-date-rules-2", "Use location constraints with effective date-based rules"),
SchedulerTest("nvpair-date-rules-1", "Use nvpair blocks with a variety of date-based rules"),
SchedulerTest("value-source", "Use location constraints with node attribute expressions using value-source"),
SchedulerTest("rule-dbl-as-auto-number-match",
"Floating-point rule values default to number comparison: match"),
SchedulerTest("rule-dbl-as-auto-number-no-match",
"Floating-point rule values default to number comparison: no match"),
SchedulerTest("rule-dbl-as-integer-match",
"Floating-point rule values set to integer comparison: match"),
SchedulerTest("rule-dbl-as-integer-no-match",
"Floating-point rule values set to integer comparison: no match"),
SchedulerTest("rule-dbl-as-number-match",
"Floating-point rule values set to number comparison: match"),
SchedulerTest("rule-dbl-as-number-no-match",
"Floating-point rule values set to number comparison: no match"),
SchedulerTest("rule-dbl-parse-fail-default-str-match",
"Floating-point rule values fail to parse, default to string "
"comparison: match"),
SchedulerTest("rule-dbl-parse-fail-default-str-no-match",
"Floating-point rule values fail to parse, default to string "
"comparison: no match"),
SchedulerTest("rule-int-as-auto-integer-match",
"Integer rule values default to integer comparison: match"),
SchedulerTest("rule-int-as-auto-integer-no-match",
"Integer rule values default to integer comparison: no match"),
SchedulerTest("rule-int-as-integer-match",
"Integer rule values set to integer comparison: match"),
SchedulerTest("rule-int-as-integer-no-match",
"Integer rule values set to integer comparison: no match"),
SchedulerTest("rule-int-as-number-match",
"Integer rule values set to number comparison: match"),
SchedulerTest("rule-int-as-number-no-match",
"Integer rule values set to number comparison: no match"),
SchedulerTest("rule-int-parse-fail-default-str-match",
"Integer rule values fail to parse, default to string "
"comparison: match"),
SchedulerTest("rule-int-parse-fail-default-str-no-match",
"Integer rule values fail to parse, default to string "
"comparison: no match"),
- SchedulerTest("timeout-by-node", "Start timeout varies by node"),
]),
SchedulerTestGroup([
SchedulerTest("order1", "Order start 1"),
SchedulerTest("order2", "Order start 2"),
SchedulerTest("order3", "Order stop"),
SchedulerTest("order4", "Order (multiple)"),
SchedulerTest("order5", "Order (move)"),
SchedulerTest("order6", "Order (move w/ restart)"),
SchedulerTest("order7", "Order (mandatory)"),
SchedulerTest("order-optional", "Order (score=0)"),
SchedulerTest("order-required", "Order (score=INFINITY)"),
SchedulerTest("bug-lf-2171", "Prevent group start when clone is stopped"),
SchedulerTest("order-clone", "Clone ordering should be able to prevent startup of dependent clones"),
SchedulerTest("order-sets", "Ordering for resource sets"),
SchedulerTest("order-serialize", "Serialize resources without inhibiting migration"),
SchedulerTest("order-serialize-set", "Serialize a set of resources without inhibiting migration"),
SchedulerTest("clone-order-primitive", "Order clone start after a primitive"),
SchedulerTest("clone-order-16instances", "Verify ordering of 16 cloned resources"),
SchedulerTest("order-optional-keyword", "Order (optional keyword)"),
SchedulerTest("order-mandatory", "Order (mandatory keyword)"),
SchedulerTest("bug-lf-2493", "Don't imply colocation requirements when applying ordering constraints with clones"),
SchedulerTest("ordered-set-basic-startup", "Constraint set with default order settings"),
SchedulerTest("ordered-set-natural", "Allow natural set ordering"),
SchedulerTest("order-wrong-kind", "Order (error)"),
]),
SchedulerTestGroup([
SchedulerTest("coloc-loop", "Colocation - loop"),
SchedulerTest("coloc-many-one", "Colocation - many-to-one"),
SchedulerTest("coloc-list", "Colocation - many-to-one with list"),
SchedulerTest("coloc-group", "Colocation - groups"),
SchedulerTest("coloc-unpromoted-anti", "Anti-colocation with unpromoted shouldn't prevent promoted colocation"),
SchedulerTest("coloc-attr", "Colocation based on node attributes"),
SchedulerTest("coloc-negative-group", "Negative colocation with a group"),
SchedulerTest("coloc-intra-set", "Intra-set colocation"),
SchedulerTest("bug-lf-2435", "Colocation sets with a negative score"),
SchedulerTest("coloc-clone-stays-active",
"Ensure clones don't get stopped/demoted because a dependent must stop"),
SchedulerTest("coloc_fp_logic", "Verify floating point calculations in colocation are working"),
SchedulerTest("colo_promoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating promoted with primitive"),
SchedulerTest("colo_unpromoted_w_native",
"cl#5070 - Verify promotion order is affected when colocating unpromoted with primitive"),
SchedulerTest("anti-colocation-order",
"cl#5187 - Prevent resources in an anti-colocation from even temporarily running on a same node"),
SchedulerTest("anti-colocation-promoted", "Organize order of actions for promoted resources in anti-colocations"),
SchedulerTest("anti-colocation-unpromoted", "Organize order of actions for unpromoted resources in anti-colocations"),
SchedulerTest("group-anticolocation", "Group with failed last member anti-colocated with another group"),
SchedulerTest("group-anticolocation-2",
"Group with failed last member anti-colocated with another sticky group"),
SchedulerTest("group-anticolocation-3",
"Group with failed last member mandatorily anti-colocated with another group"),
SchedulerTest("group-anticolocation-4",
"Group with failed last member anti-colocated without influence with another group"),
SchedulerTest("group-anticolocation-5",
"Group with failed last member anti-colocated with another group (third node allowed)"),
SchedulerTest("group-colocation-failure",
"Group with sole member failed, colocated with another group"),
SchedulerTest("enforce-colo1", "Always enforce B with A INFINITY"),
SchedulerTest("complex_enforce_colo", "Always enforce B with A INFINITY. (make sure heat-engine stops)"),
SchedulerTest("coloc-dependee-should-stay", "Stickiness outweighs group colocation"),
SchedulerTest("coloc-dependee-should-move", "Group colocation outweighs stickiness"),
SchedulerTest("colocation-influence", "Respect colocation influence"),
SchedulerTest("colocation-priority-group", "Apply group colocations in order of primary priority"),
SchedulerTest("colocation-vs-stickiness", "Group stickiness outweighs anti-colocation score"),
SchedulerTest("promoted-with-blocked", "Promoted role colocated with a resource with blocked start"),
SchedulerTest("primitive-with-group-with-clone",
"Consider group dependent when colocating with clone"),
SchedulerTest("primitive-with-group-with-promoted",
"Consider group dependent when colocating with promoted role"),
SchedulerTest("primitive-with-unrunnable-group",
"Block primitive colocated with group that can't start"),
SchedulerTest("coloc-cloned-group-promoted-dependent1",
"Cloned group promoted role with primitive (mandatory)"),
SchedulerTest("coloc-cloned-group-promoted-dependent2",
"Cloned group promoted role with primitive (optional)"),
SchedulerTest("coloc-optional-promoted-dependent-moves-1",
"Colocation score less than promotion score "
+ "difference: move"),
SchedulerTest("coloc-optional-promoted-dependent-moves-2",
"Colocation score greater than promotion score "
+ "difference: move"),
SchedulerTest("coloc-optional-promoted-dependent-stays-1",
"Colocation score greater than promotion score "
+ "difference: stay"),
SchedulerTest("coloc-optional-promoted-dependent-stays-2",
"Colocation score less than promotion score "
+ "difference: stay"),
]),
SchedulerTestGroup([
SchedulerTest("rsc-sets-seq-true", "Resource Sets - sequential=false"),
SchedulerTest("rsc-sets-seq-false", "Resource Sets - sequential=true"),
SchedulerTest("rsc-sets-clone", "Resource Sets - Clone"),
SchedulerTest("rsc-sets-promoted", "Resource Sets - Promoted"),
SchedulerTest("rsc-sets-clone-1", "Resource Sets - Clone (lf#2404)"),
]),
SchedulerTestGroup([
SchedulerTest("attrs1", "string: eq (and)"),
SchedulerTest("attrs2", "string: lt / gt (and)"),
SchedulerTest("attrs3", "string: ne (or)"),
SchedulerTest("attrs4", "string: exists"),
SchedulerTest("attrs5", "string: not_exists"),
SchedulerTest("attrs6", "is_dc: true"),
SchedulerTest("attrs7", "is_dc: false"),
SchedulerTest("attrs8", "score_attribute"),
SchedulerTest("per-node-attrs", "Per node resource parameters"),
]),
SchedulerTestGroup([
SchedulerTest("mon-rsc-1", "Schedule Monitor - start"),
SchedulerTest("mon-rsc-2", "Schedule Monitor - move"),
SchedulerTest("mon-rsc-3", "Schedule Monitor - pending start"),
SchedulerTest("mon-rsc-4", "Schedule Monitor - move/pending start"),
]),
SchedulerTestGroup([
SchedulerTest("rec-rsc-0", "Resource Recover - no start"),
SchedulerTest("rec-rsc-1", "Resource Recover - start"),
SchedulerTest("rec-rsc-2", "Resource Recover - monitor"),
SchedulerTest("rec-rsc-3", "Resource Recover - stop - ignore"),
SchedulerTest("rec-rsc-4", "Resource Recover - stop - block"),
SchedulerTest("rec-rsc-5", "Resource Recover - stop - fence"),
SchedulerTest("rec-rsc-6", "Resource Recover - multiple - restart"),
SchedulerTest("rec-rsc-7", "Resource Recover - multiple - stop"),
SchedulerTest("rec-rsc-8", "Resource Recover - multiple - block"),
SchedulerTest("rec-rsc-9", "Resource Recover - group/group"),
SchedulerTest("stop-unexpected", "Recover multiply active group with stop_unexpected"),
SchedulerTest("stop-unexpected-2", "Resource multiply active primitve with stop_unexpected"),
SchedulerTest("monitor-recovery", "on-fail=block + resource recovery detected by recurring monitor"),
SchedulerTest("stop-failure-no-quorum", "Stop failure without quorum"),
SchedulerTest("stop-failure-no-fencing", "Stop failure without fencing available"),
SchedulerTest("stop-failure-with-fencing", "Stop failure with fencing available"),
SchedulerTest("multiple-active-block-group", "Support of multiple-active=block for resource groups"),
SchedulerTest("multiple-monitor-one-failed",
"Consider resource failed if any of the configured monitor operations failed"),
]),
SchedulerTestGroup([
SchedulerTest("quorum-1", "No quorum - ignore"),
SchedulerTest("quorum-2", "No quorum - freeze"),
SchedulerTest("quorum-3", "No quorum - stop"),
SchedulerTest("quorum-4", "No quorum - start anyway"),
SchedulerTest("quorum-5", "No quorum - start anyway (group)"),
SchedulerTest("quorum-6", "No quorum - start anyway (clone)"),
SchedulerTest("bug-cl-5212", "No promotion with no-quorum-policy=freeze"),
SchedulerTest("suicide-needed-inquorate", "no-quorum-policy=suicide: suicide necessary"),
SchedulerTest("suicide-not-needed-initial-quorum",
"no-quorum-policy=suicide: suicide not necessary at initial quorum"),
SchedulerTest("suicide-not-needed-never-quorate",
"no-quorum-policy=suicide: suicide not necessary if never quorate"),
SchedulerTest("suicide-not-needed-quorate", "no-quorum-policy=suicide: suicide necessary if quorate"),
]),
SchedulerTestGroup([
SchedulerTest("rec-node-1", "Node Recover - Startup - no fence"),
SchedulerTest("rec-node-2", "Node Recover - Startup - fence"),
SchedulerTest("rec-node-3", "Node Recover - HA down - no fence"),
SchedulerTest("rec-node-4", "Node Recover - HA down - fence"),
SchedulerTest("rec-node-5", "Node Recover - CRM down - no fence"),
SchedulerTest("rec-node-6", "Node Recover - CRM down - fence"),
SchedulerTest("rec-node-7", "Node Recover - no quorum - ignore"),
SchedulerTest("rec-node-8", "Node Recover - no quorum - freeze"),
SchedulerTest("rec-node-9", "Node Recover - no quorum - stop"),
SchedulerTest("rec-node-10", "Node Recover - no quorum - stop w/fence"),
SchedulerTest("rec-node-11", "Node Recover - CRM down w/ group - fence"),
SchedulerTest("rec-node-12", "Node Recover - nothing active - fence"),
SchedulerTest("rec-node-13", "Node Recover - failed resource + shutdown - fence"),
SchedulerTest("rec-node-15", "Node Recover - unknown lrm section"),
SchedulerTest("rec-node-14", "Serialize all stonith's"),
]),
SchedulerTestGroup([
SchedulerTest("multi1", "Multiple Active (stop/start)"),
]),
SchedulerTestGroup([
SchedulerTest("migrate-begin", "Normal migration"),
SchedulerTest("migrate-success", "Completed migration"),
SchedulerTest("migrate-partial-1", "Completed migration, missing stop on source"),
SchedulerTest("migrate-partial-2", "Successful migrate_to only"),
SchedulerTest("migrate-partial-3", "Successful migrate_to only, target down"),
SchedulerTest("migrate-partial-4", "Migrate from the correct host after migrate_to+migrate_from"),
SchedulerTest("bug-5186-partial-migrate", "Handle partial migration when src node loses membership"),
SchedulerTest("migrate-fail-2", "Failed migrate_from"),
SchedulerTest("migrate-fail-3", "Failed migrate_from + stop on source"),
SchedulerTest("migrate-fail-4",
"Failed migrate_from + stop on target - ideally we wouldn't need to re-stop on target"),
SchedulerTest("migrate-fail-5", "Failed migrate_from + stop on source and target"),
SchedulerTest("migrate-fail-6", "Failed migrate_to"),
SchedulerTest("migrate-fail-7", "Failed migrate_to + stop on source"),
SchedulerTest("migrate-fail-8",
"Failed migrate_to + stop on target - ideally we wouldn't need to re-stop on target"),
SchedulerTest("migrate-fail-9", "Failed migrate_to + stop on source and target"),
SchedulerTest("migration-ping-pong", "Old migrate_to failure + successful migrate_from on same node"),
SchedulerTest("migrate-stop", "Migration in a stopping stack"),
SchedulerTest("migrate-start", "Migration in a starting stack"),
SchedulerTest("migrate-stop_start", "Migration in a restarting stack"),
SchedulerTest("migrate-stop-complex", "Migration in a complex stopping stack"),
SchedulerTest("migrate-start-complex", "Migration in a complex starting stack"),
SchedulerTest("migrate-stop-start-complex", "Migration in a complex moving stack"),
SchedulerTest("migrate-shutdown", "Order the post-migration 'stop' before node shutdown"),
SchedulerTest("migrate-1", "Migrate (migrate)"),
SchedulerTest("migrate-2", "Migrate (stable)"),
SchedulerTest("migrate-3", "Migrate (failed migrate_to)"),
SchedulerTest("migrate-4", "Migrate (failed migrate_from)"),
SchedulerTest("novell-252693", "Migration in a stopping stack"),
SchedulerTest("novell-252693-2", "Migration in a starting stack"),
SchedulerTest("novell-252693-3", "Non-Migration in a starting and stopping stack"),
SchedulerTest("bug-1820", "Migration in a group"),
SchedulerTest("bug-1820-1", "Non-migration in a group"),
SchedulerTest("migrate-5", "Primitive migration with a clone"),
SchedulerTest("migrate-fencing", "Migration after Fencing"),
SchedulerTest("migrate-both-vms", "Migrate two VMs that have no colocation"),
SchedulerTest("migration-behind-migrating-remote", "Migrate resource behind migrating remote connection"),
SchedulerTest("1-a-then-bm-move-b", "Advanced migrate logic. A then B. migrate B"),
SchedulerTest("2-am-then-b-move-a", "Advanced migrate logic, A then B, migrate A without stopping B"),
SchedulerTest("3-am-then-bm-both-migrate", "Advanced migrate logic. A then B. migrate both"),
SchedulerTest("4-am-then-bm-b-not-migratable", "Advanced migrate logic, A then B, B not migratable"),
SchedulerTest("5-am-then-bm-a-not-migratable", "Advanced migrate logic. A then B. move both, a not migratable"),
SchedulerTest("6-migrate-group", "Advanced migrate logic, migrate a group"),
SchedulerTest("7-migrate-group-one-unmigratable",
"Advanced migrate logic, migrate group mixed with allow-migrate true/false"),
SchedulerTest("8-am-then-bm-a-migrating-b-stopping",
"Advanced migrate logic, A then B, A migrating, B stopping"),
SchedulerTest("9-am-then-bm-b-migrating-a-stopping",
"Advanced migrate logic, A then B, B migrate, A stopping"),
SchedulerTest("10-a-then-bm-b-move-a-clone",
"Advanced migrate logic, A clone then B, migrate B while stopping A"),
SchedulerTest("11-a-then-bm-b-move-a-clone-starting",
"Advanced migrate logic, A clone then B, B moving while A is start/stopping"),
SchedulerTest("a-promote-then-b-migrate", "A promote then B start. migrate B"),
SchedulerTest("a-demote-then-b-migrate", "A demote then B stop. migrate B"),
SchedulerTest("probe-target-of-failed-migrate_to-1", "Failed migrate_to, target rejoins"),
SchedulerTest("probe-target-of-failed-migrate_to-2", "Failed migrate_to, target rejoined and probed"),
SchedulerTest("partial-live-migration-multiple-active", "Prevent running on multiple nodes due to partial live migration"),
SchedulerTest("migration-intermediary-cleaned",
"Probe live-migration intermediary with no history"),
SchedulerTest("bug-lf-2422", "Dependency on partially active group - stop ocfs:*"),
]),
SchedulerTestGroup([
SchedulerTest("clone-anon-probe-1", "Probe the correct (anonymous) clone instance for each node"),
SchedulerTest("clone-anon-probe-2", "Avoid needless re-probing of anonymous clones"),
SchedulerTest("clone-anon-failcount", "Merge failcounts for anonymous clones"),
SchedulerTest("force-anon-clone-max", "Update clone-max properly when forcing a clone to be anonymous"),
SchedulerTest("anon-instance-pending", "Assign anonymous clone instance numbers properly when action pending"),
SchedulerTest("inc0", "Incarnation start"),
SchedulerTest("inc1", "Incarnation start order"),
SchedulerTest("inc2", "Incarnation silent restart, stop, move"),
SchedulerTest("inc3", "Inter-incarnation ordering, silent restart, stop, move"),
SchedulerTest("inc4", "Inter-incarnation ordering, silent restart, stop, move (ordered)"),
SchedulerTest("inc5", "Inter-incarnation ordering, silent restart, stop, move (restart 1)"),
SchedulerTest("inc6", "Inter-incarnation ordering, silent restart, stop, move (restart 2)"),
SchedulerTest("inc7", "Clone colocation"),
SchedulerTest("inc8", "Clone anti-colocation"),
SchedulerTest("inc9", "Non-unique clone"),
SchedulerTest("inc10", "Non-unique clone (stop)"),
SchedulerTest("inc11", "Primitive colocation with clones"),
SchedulerTest("inc12", "Clone shutdown"),
SchedulerTest("cloned-group", "Make sure only the correct number of cloned groups are started"),
SchedulerTest("cloned-group-stop", "Ensure stopping qpidd also stops glance and cinder"),
SchedulerTest("clone-no-shuffle", "Don't prioritize allocation of instances that must be moved"),
SchedulerTest("clone-recover-no-shuffle-1",
"Don't shuffle instances when starting a new primitive instance"),
SchedulerTest("clone-recover-no-shuffle-2",
"Don't shuffle instances when starting a new group instance"),
SchedulerTest("clone-recover-no-shuffle-3",
"Don't shuffle instances when starting a new bundle instance"),
SchedulerTest("clone-recover-no-shuffle-4",
"Don't shuffle instances when starting a new primitive instance with "
"location preference "),
SchedulerTest("clone-recover-no-shuffle-5",
"Don't shuffle instances when starting a new group instance with "
"location preference"),
SchedulerTest("clone-recover-no-shuffle-6",
"Don't shuffle instances when starting a new bundle instance with "
"location preference"),
SchedulerTest("clone-recover-no-shuffle-7",
"Don't shuffle instances when starting a new primitive instance that "
"will be promoted"),
SchedulerTest("clone-recover-no-shuffle-8",
"Don't shuffle instances when starting a new group instance that "
"will be promoted "),
SchedulerTest("clone-recover-no-shuffle-9",
"Don't shuffle instances when starting a new bundle instance that "
"will be promoted "),
SchedulerTest("clone-recover-no-shuffle-10",
"Don't shuffle instances when starting a new primitive instance that "
"won't be promoted"),
SchedulerTest("clone-recover-no-shuffle-11",
"Don't shuffle instances when starting a new group instance that "
"won't be promoted "),
SchedulerTest("clone-recover-no-shuffle-12",
"Don't shuffle instances when starting a new bundle instance that "
"won't be promoted "),
SchedulerTest("clone-max-zero", "Orphan processing with clone-max=0"),
SchedulerTest("clone-anon-dup",
"Bug LF#2087 - Correctly parse the state of anonymous clones that are active more than once per node"),
SchedulerTest("bug-lf-2160", "Don't shuffle clones due to colocation"),
SchedulerTest("bug-lf-2213", "clone-node-max enforcement for cloned groups"),
SchedulerTest("bug-lf-2153", "Clone ordering constraints"),
SchedulerTest("bug-lf-2361", "Ensure clones observe mandatory ordering constraints if the LHS is unrunnable"),
SchedulerTest("bug-lf-2317", "Avoid needless restart of primitive depending on a clone"),
SchedulerTest("bug-lf-2453", "Enforce mandatory clone ordering without colocation"),
SchedulerTest("bug-lf-2508", "Correctly reconstruct the status of anonymous cloned groups"),
SchedulerTest("bug-lf-2544", "Balanced clone placement"),
SchedulerTest("bug-lf-2445", "Redistribute clones with node-max > 1 and stickiness = 0"),
SchedulerTest("bug-lf-2574", "Avoid clone shuffle"),
SchedulerTest("bug-lf-2581", "Avoid group restart due to unrelated clone (re)start"),
SchedulerTest("bug-cl-5168", "Don't shuffle clones"),
SchedulerTest("bug-cl-5170", "Prevent clone from starting with on-fail=block"),
SchedulerTest("clone-fail-block-colocation", "Move colocated group when failed clone has on-fail=block"),
SchedulerTest("clone-interleave-1",
"Clone-3 cannot start on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("clone-interleave-2", "Clone-3 must stop on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("clone-interleave-3",
"Clone-3 must be recovered on pcmk-1 due to interleaved ordering (no colocation)"),
SchedulerTest("rebalance-unique-clones", "Rebalance unique clone instances with no stickiness"),
SchedulerTest("clone-requires-quorum-recovery", "Clone with requires=quorum on failed node needing recovery"),
SchedulerTest("clone-requires-quorum",
"Clone with requires=quorum with presumed-inactive instance on failed node"),
]),
SchedulerTestGroup([
SchedulerTest("cloned_start_one", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_start_two", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_stop_one", "order first clone then clone... first clone_min=2"),
SchedulerTest("cloned_stop_two", "order first clone then clone... first clone_min=2"),
SchedulerTest("clone_min_interleave_start_one",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_start_two",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_stop_one",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_interleave_stop_two",
"order first clone then clone... first clone_min=2 and then has interleave=true"),
SchedulerTest("clone_min_start_one", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_start_two", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_all", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_one", "order first clone then primitive... first clone_min=2"),
SchedulerTest("clone_min_stop_two", "order first clone then primitive... first clone_min=2"),
]),
SchedulerTestGroup([
SchedulerTest("unfence-startup", "Clean unfencing"),
SchedulerTest("unfence-definition", "Unfencing when the agent changes"),
SchedulerTest("unfence-parameters", "Unfencing when the agent parameters changes"),
SchedulerTest("unfence-device", "Unfencing when a cluster has only fence devices"),
]),
SchedulerTestGroup([
SchedulerTest("promoted-0", "Stopped -> Unpromoted"),
SchedulerTest("promoted-1", "Stopped -> Promote"),
SchedulerTest("promoted-2", "Stopped -> Promote : notify"),
SchedulerTest("promoted-3", "Stopped -> Promote : promoted location"),
SchedulerTest("promoted-4", "Started -> Promote : promoted location"),
SchedulerTest("promoted-5", "Promoted -> Promoted"),
SchedulerTest("promoted-6", "Promoted -> Promoted (2)"),
SchedulerTest("promoted-7", "Promoted -> Fenced"),
SchedulerTest("promoted-8", "Promoted -> Fenced -> Moved"),
SchedulerTest("promoted-9", "Stopped + Promotable + No quorum"),
SchedulerTest("promoted-10", "Stopped -> Promotable : notify with monitor"),
SchedulerTest("promoted-11", "Stopped -> Promote : colocation"),
SchedulerTest("novell-239082", "Demote/Promote ordering"),
SchedulerTest("novell-239087", "Stable promoted placement"),
SchedulerTest("promoted-12", "Promotion based solely on rsc_location constraints"),
SchedulerTest("promoted-13", "Include preferences of colocated resources when placing promoted"),
SchedulerTest("promoted-demote", "Ordering when actions depends on demoting an unpromoted resource"),
SchedulerTest("promoted-ordering", "Prevent resources from starting that need a promoted"),
SchedulerTest("bug-1765", "Verify promoted-with-promoted colocation does not stop unpromoted instances"),
SchedulerTest("promoted-group", "Promotion of cloned groups"),
SchedulerTest("bug-lf-1852", "Don't shuffle promotable instances unnecessarily"),
SchedulerTest("promoted-failed-demote", "Don't retry failed demote actions"),
SchedulerTest("promoted-failed-demote-2", "Don't retry failed demote actions (notify=false)"),
SchedulerTest("promoted-depend",
"Ensure resources that depend on promoted instance don't get allocated until that does"),
SchedulerTest("promoted-reattach", "Re-attach to a running promoted"),
SchedulerTest("promoted-allow-start", "Don't include promoted score if it would prevent allocation"),
SchedulerTest("promoted-colocation",
"Allow promoted instances placemaker to be influenced by colocation constraints"),
SchedulerTest("promoted-pseudo", "Make sure promote/demote pseudo actions are created correctly"),
SchedulerTest("promoted-role", "Prevent target-role from promoting more than promoted-max instances"),
SchedulerTest("bug-lf-2358", "Anti-colocation of promoted instances"),
SchedulerTest("promoted-promotion-constraint", "Mandatory promoted colocation constraints"),
SchedulerTest("unmanaged-promoted", "Ensure role is preserved for unmanaged resources"),
SchedulerTest("promoted-unmanaged-monitor", "Start correct monitor for unmanaged promoted instances"),
SchedulerTest("promoted-demote-2", "Demote does not clear past failure"),
SchedulerTest("promoted-move", "Move promoted based on failure of colocated group"),
SchedulerTest("promoted-probed-score", "Observe the promotion score of probed resources"),
SchedulerTest("colocation_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by colocation constraint"),
SchedulerTest("colocation_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by colocation constraint"),
SchedulerTest("order_constraint_stops_promoted",
"cl#5054 - Ensure promoted is demoted when stopped by order constraint"),
SchedulerTest("order_constraint_stops_unpromoted",
"cl#5054 - Ensure unpromoted is not demoted when stopped by order constraint"),
SchedulerTest("promoted_monitor_restart", "cl#5072 - Ensure promoted monitor operation will start after promotion"),
SchedulerTest("bug-rh-880249", "Handle replacement of an m/s resource with a primitive"),
SchedulerTest("bug-5143-ms-shuffle", "Prevent promoted instance shuffling due to promotion score"),
SchedulerTest("promoted-demote-block", "Block promotion if demote fails with on-fail=block"),
SchedulerTest("promoted-dependent-ban",
"Don't stop instances from being active because a dependent is banned from that host"),
SchedulerTest("promoted-stop", "Stop instances due to location constraint with role=Started"),
SchedulerTest("promoted-partially-demoted-group", "Allow partially demoted group to finish demoting"),
SchedulerTest("bug-cl-5213", "Ensure role colocation with -INFINITY is enforced"),
SchedulerTest("bug-cl-5219", "Allow unrelated resources with a common colocation target to remain promoted"),
SchedulerTest("promoted-asymmetrical-order",
"Fix the behaviors of multi-state resources with asymmetrical ordering"),
SchedulerTest("promoted-notify", "Promotion with notifications"),
SchedulerTest("promoted-score-startup", "Use permanent promoted scores without LRM history"),
SchedulerTest("failed-demote-recovery", "Recover resource in unpromoted role after demote fails"),
SchedulerTest("failed-demote-recovery-promoted", "Recover resource in promoted role after demote fails"),
SchedulerTest("on_fail_demote1", "Recovery with on-fail=\"demote\" on healthy cluster, remote, guest, and bundle nodes"),
SchedulerTest("on_fail_demote2", "Recovery with on-fail=\"demote\" with promotion on different node"),
SchedulerTest("on_fail_demote3", "Recovery with on-fail=\"demote\" with no promotion"),
SchedulerTest("on_fail_demote4", "Recovery with on-fail=\"demote\" on failed cluster, remote, guest, and bundle nodes"),
SchedulerTest("no_quorum_demote", "Promotable demotion and primitive stop with no-quorum-policy=\"demote\""),
SchedulerTest("no-promote-on-unrunnable-guest", "Don't select bundle instance for promotion when container can't run"),
SchedulerTest("leftover-pending-monitor", "Prevent a leftover pending monitor from causing unexpected stop of other instances"),
]),
SchedulerTestGroup([
SchedulerTest("history-1", "Correctly parse stateful-1 resource state"),
]),
SchedulerTestGroup([
SchedulerTest("managed-0", "Managed (reference)"),
SchedulerTest("managed-1", "Not managed - down"),
SchedulerTest("managed-2", "Not managed - up"),
SchedulerTest("bug-5028", "Shutdown should block if anything depends on an unmanaged resource"),
SchedulerTest("bug-5028-detach", "Ensure detach still works"),
SchedulerTest("bug-5028-bottom",
"Ensure shutdown still blocks if the blocked resource is at the bottom of the stack"),
SchedulerTest("unmanaged-stop-1",
"cl#5155 - Block the stop of resources if any depending resource is unmanaged"),
SchedulerTest("unmanaged-stop-2",
"cl#5155 - Block the stop of resources if the first resource in a mandatory stop order is unmanaged"),
SchedulerTest("unmanaged-stop-3",
"cl#5155 - Block the stop of resources if any depending resource in a group is unmanaged"),
SchedulerTest("unmanaged-stop-4",
"cl#5155 - Block the stop of resources if any depending resource in the middle of a group is unmanaged"),
SchedulerTest("unmanaged-block-restart",
"Block restart of resources if any dependent resource in a group is unmanaged"),
]),
SchedulerTestGroup([
SchedulerTest("interleave-0", "Interleave (reference)"),
SchedulerTest("interleave-1", "coloc - not interleaved"),
SchedulerTest("interleave-2", "coloc - interleaved"),
SchedulerTest("interleave-3", "coloc - interleaved (2)"),
SchedulerTest("interleave-pseudo-stop", "Interleaved clone during stonith"),
SchedulerTest("interleave-stop", "Interleaved clone during stop"),
SchedulerTest("interleave-restart", "Interleaved clone during dependency restart"),
]),
SchedulerTestGroup([
SchedulerTest("notify-0", "Notify reference"),
SchedulerTest("notify-1", "Notify simple"),
SchedulerTest("notify-2", "Notify simple, confirm"),
SchedulerTest("notify-3", "Notify move, confirm"),
SchedulerTest("novell-239079", "Notification priority"),
SchedulerTest("notifs-for-unrunnable", "Don't schedule notifications for an unrunnable action"),
SchedulerTest("route-remote-notify", "Route remote notify actions through correct cluster node"),
SchedulerTest("notify-behind-stopping-remote", "Don't schedule notifications behind stopped remote"),
]),
SchedulerTestGroup([
SchedulerTest("594", "OSDL #594 - Unrunnable actions scheduled in transition"),
SchedulerTest("662", "OSDL #662 - Two resources start on one node when incarnation_node_max = 1"),
SchedulerTest("696", "OSDL #696 - CRM starts stonith RA without monitor"),
SchedulerTest("726", "OSDL #726 - Attempting to schedule rsc_posic041_monitor_5000 _after_ a stop"),
SchedulerTest("735", "OSDL #735 - Correctly detect that rsc_hadev1 is stopped on hadev3"),
SchedulerTest("764", "OSDL #764 - Missing monitor op for DoFencing:child_DoFencing:1"),
SchedulerTest("797", "OSDL #797 - Assert triggered: task_id_i > max_call_id"),
SchedulerTest("829", "OSDL #829"),
SchedulerTest("994",
"OSDL #994 - Stopping the last resource in a resource group causes the entire group to be restarted"),
SchedulerTest("994-2", "OSDL #994 - with a dependent resource"),
SchedulerTest("1360", "OSDL #1360 - Clone stickiness"),
SchedulerTest("1484", "OSDL #1484 - on_fail=stop"),
SchedulerTest("1494", "OSDL #1494 - Clone stability"),
SchedulerTest("unrunnable-1", "Unrunnable"),
SchedulerTest("unrunnable-2", "Unrunnable 2"),
SchedulerTest("stonith-0", "Stonith loop - 1"),
SchedulerTest("stonith-1", "Stonith loop - 2"),
SchedulerTest("stonith-2", "Stonith loop - 3"),
SchedulerTest("stonith-3", "Stonith startup"),
SchedulerTest("stonith-4", "Stonith node state"),
SchedulerTest("dc-fence-ordering", "DC needs fencing while other nodes are shutting down"),
SchedulerTest("bug-1572-1", "Recovery of groups depending on promotable role"),
SchedulerTest("bug-1572-2", "Recovery of groups depending on promotable role when promoted is not re-promoted"),
SchedulerTest("bug-1685", "Depends-on-promoted ordering"),
SchedulerTest("bug-1822", "Don't promote partially active groups"),
SchedulerTest("bug-pm-11", "New resource added to a m/s group"),
SchedulerTest("bug-pm-12", "Recover only the failed portion of a cloned group"),
SchedulerTest("bug-n-387749", "Don't shuffle clone instances"),
SchedulerTest("bug-n-385265",
"Don't ignore the failure stickiness of group children - resource_idvscommon should stay stopped"),
SchedulerTest("bug-n-385265-2",
"Ensure groups are migrated instead of remaining partially active on the current node"),
SchedulerTest("bug-lf-1920", "Correctly handle probes that find active resources"),
SchedulerTest("bnc-515172", "Location constraint with multiple expressions"),
SchedulerTest("colocate-primitive-with-clone", "Optional colocation with a clone"),
SchedulerTest("use-after-free-merge", "Use-after-free in native_merge_weights"),
SchedulerTest("bug-lf-2551", "STONITH ordering for stop"),
SchedulerTest("bug-lf-2606", "Stonith implies demote"),
SchedulerTest("bug-lf-2474", "Ensure resource op timeout takes precedence over op_defaults"),
SchedulerTest("bug-suse-707150", "Prevent vm-01 from starting due to colocation/ordering"),
SchedulerTest("bug-5014-A-start-B-start", "Verify when A starts B starts using symmetrical=false"),
SchedulerTest("bug-5014-A-stop-B-started",
"Verify when A stops B does not stop if it has already started using symmetric=false"),
SchedulerTest("bug-5014-A-stopped-B-stopped",
"Verify when A is stopped and B has not started, B does not start before A using symmetric=false"),
SchedulerTest("bug-5014-CthenAthenB-C-stopped",
"Verify when C then A is symmetrical=true, A then B is symmetric=false, and C is stopped that nothing starts"),
SchedulerTest("bug-5014-CLONE-A-start-B-start",
"Verify when A starts B starts using clone resources with symmetric=false"),
SchedulerTest("bug-5014-CLONE-A-stop-B-started",
"Verify when A stops B does not stop if it has already started using clone resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-start-B-start",
"Verify when A starts B starts when using group resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-stopped-B-started",
"Verify when A stops B does not stop if it has already started using group resources with symmetric=false"),
SchedulerTest("bug-5014-GROUP-A-stopped-B-stopped",
"Verify when A is stopped and B has not started, B does not start before A using group resources with symmetric=false"),
SchedulerTest("bug-5014-ordered-set-symmetrical-false",
"Verify ordered sets work with symmetrical=false"),
SchedulerTest("bug-5014-ordered-set-symmetrical-true",
"Verify ordered sets work with symmetrical=true"),
SchedulerTest("clbz5007-promotable-colocation",
"Verify use of colocation scores other than INFINITY and -INFINITY work on multi-state resources"),
SchedulerTest("bug-5038", "Prevent restart of anonymous clones when clone-max decreases"),
SchedulerTest("bug-5025-1", "Automatically clean up failcount after resource config change with reload"),
SchedulerTest("bug-5025-2", "Make sure clear failcount action isn't set when config does not change"),
SchedulerTest("bug-5025-3", "Automatically clean up failcount after resource config change with restart"),
SchedulerTest("bug-5025-4", "Clear failcount when last failure is a start op and rsc attributes changed"),
SchedulerTest("failcount", "Ensure failcounts are correctly expired"),
SchedulerTest("failcount-block", "Ensure failcounts are not expired when on-fail=block is present"),
SchedulerTest("per-op-failcount", "Ensure per-operation failcount is handled and not passed to fence agent"),
SchedulerTest("on-fail-ignore", "Ensure on-fail=ignore works even beyond migration-threshold"),
SchedulerTest("monitor-onfail-restart", "bug-5058 - Monitor failure with on-fail set to restart"),
SchedulerTest("monitor-onfail-stop", "bug-5058 - Monitor failure wiht on-fail set to stop"),
SchedulerTest("bug-5059", "No need to restart p_stateful1:*"),
SchedulerTest("bug-5069-op-enabled", "Test on-fail=ignore with failure when monitor is enabled"),
SchedulerTest("bug-5069-op-disabled", "Test on-fail-ignore with failure when monitor is disabled"),
SchedulerTest("obsolete-lrm-resource", "cl#5115 - Do not use obsolete lrm_resource sections"),
SchedulerTest("expire-non-blocked-failure",
"Ignore failure-timeout only if the failed operation has on-fail=block"),
SchedulerTest("asymmetrical-order-move", "Respect asymmetrical ordering when trying to move resources"),
SchedulerTest("asymmetrical-order-restart", "Respect asymmetrical ordering when restarting dependent resource"),
SchedulerTest("start-then-stop-with-unfence", "Avoid graph loop with start-then-stop constraint plus unfencing"),
SchedulerTest("order-expired-failure", "Order failcount cleanup after remote fencing"),
SchedulerTest("expired-stop-1", "Expired stop failure should not block resource"),
SchedulerTest("ignore_stonith_rsc_order1",
"cl#5056- Ignore order constraint between stonith and non-stonith rsc"),
SchedulerTest("ignore_stonith_rsc_order2",
"cl#5056- Ignore order constraint with group rsc containing mixed stonith and non-stonith"),
SchedulerTest("ignore_stonith_rsc_order3", "cl#5056- Ignore order constraint, stonith clone and mixed group"),
SchedulerTest("ignore_stonith_rsc_order4",
"cl#5056- Ignore order constraint, stonith clone and clone with nested mixed group"),
SchedulerTest("honor_stonith_rsc_order1",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(single rsc)"),
SchedulerTest("honor_stonith_rsc_order2",
"cl#5056- Honor order constraint, stonith clone and pure stonith group(multiple rsc)"),
SchedulerTest("honor_stonith_rsc_order3",
"cl#5056- Honor order constraint, stonith clones with nested pure stonith group"),
SchedulerTest("honor_stonith_rsc_order4",
"cl#5056- Honor order constraint, between two native stonith rscs"),
SchedulerTest("multiply-active-stonith", "Multiply active stonith"),
SchedulerTest("probe-timeout", "cl#5099 - Default probe timeout"),
SchedulerTest("order-first-probes",
"cl#5301 - respect order constraints when relevant resources are being probed"),
SchedulerTest("concurrent-fencing", "Allow performing fencing operations in parallel"),
SchedulerTest("priority-fencing-delay", "Delay fencing targeting the more significant node"),
SchedulerTest("pending-node-no-uname", "Do not fence a pending node that doesn't have an uname in node state yet"),
SchedulerTest("node-pending-timeout", "Fence a pending node that has reached `node-pending-timeout`"),
]),
SchedulerTestGroup([
SchedulerTest("systemhealth1", "System Health () #1"),
SchedulerTest("systemhealth2", "System Health () #2"),
SchedulerTest("systemhealth3", "System Health () #3"),
SchedulerTest("systemhealthn1", "System Health (None) #1"),
SchedulerTest("systemhealthn2", "System Health (None) #2"),
SchedulerTest("systemhealthn3", "System Health (None) #3"),
SchedulerTest("systemhealthm1", "System Health (Migrate On Red) #1"),
SchedulerTest("systemhealthm2", "System Health (Migrate On Red) #2"),
SchedulerTest("systemhealthm3", "System Health (Migrate On Red) #3"),
SchedulerTest("systemhealtho1", "System Health (Only Green) #1"),
SchedulerTest("systemhealtho2", "System Health (Only Green) #2"),
SchedulerTest("systemhealtho3", "System Health (Only Green) #3"),
SchedulerTest("systemhealthp1", "System Health (Progessive) #1"),
SchedulerTest("systemhealthp2", "System Health (Progessive) #2"),
SchedulerTest("systemhealthp3", "System Health (Progessive) #3"),
SchedulerTest("allow-unhealthy-nodes", "System Health (migrate-on-red + allow-unhealth-nodes)"),
]),
SchedulerTestGroup([
SchedulerTest("utilization", "Placement Strategy - utilization"),
SchedulerTest("minimal", "Placement Strategy - minimal"),
SchedulerTest("balanced", "Placement Strategy - balanced"),
]),
SchedulerTestGroup([
SchedulerTest("placement-stickiness", "Optimized Placement Strategy - stickiness"),
SchedulerTest("placement-priority", "Optimized Placement Strategy - priority"),
SchedulerTest("placement-location", "Optimized Placement Strategy - location"),
SchedulerTest("placement-capacity", "Optimized Placement Strategy - capacity"),
]),
SchedulerTestGroup([
SchedulerTest("utilization-order1", "Utilization Order - Simple"),
SchedulerTest("utilization-order2", "Utilization Order - Complex"),
SchedulerTest("utilization-order3", "Utilization Order - Migrate"),
SchedulerTest("utilization-order4", "Utilization Order - Live Migration (bnc#695440)"),
SchedulerTest("utilization-complex", "Utilization with complex relationships"),
SchedulerTest("utilization-shuffle",
"Don't displace prmExPostgreSQLDB2 on act2, Start prmExPostgreSQLDB1 on act3"),
SchedulerTest("load-stopped-loop", "Avoid transition loop due to load_stopped (cl#5044)"),
SchedulerTest("load-stopped-loop-2",
"cl#5235 - Prevent graph loops that can be introduced by load_stopped -> migrate_to ordering"),
]),
SchedulerTestGroup([
SchedulerTest("colocated-utilization-primitive-1", "Colocated Utilization - Primitive"),
SchedulerTest("colocated-utilization-primitive-2", "Colocated Utilization - Choose the most capable node"),
SchedulerTest("colocated-utilization-group", "Colocated Utilization - Group"),
SchedulerTest("colocated-utilization-clone", "Colocated Utilization - Clone"),
SchedulerTest("utilization-check-allowed-nodes",
"Only check the capacities of the nodes that can run the resource"),
]),
SchedulerTestGroup([
SchedulerTest("node-maintenance-1", "cl#5128 - Node maintenance"),
SchedulerTest("node-maintenance-2", "cl#5128 - Node maintenance (coming out of maintenance mode)"),
SchedulerTest("shutdown-maintenance-node", "Do not fence a maintenance node if it shuts down cleanly"),
SchedulerTest("rsc-maintenance", "Per-resource maintenance"),
]),
SchedulerTestGroup([
SchedulerTest("not-installed-agent", "The resource agent is missing"),
SchedulerTest("not-installed-tools", "Something the resource agent needs is missing"),
]),
SchedulerTestGroup([
SchedulerTest("stopped-monitor-00", "Stopped Monitor - initial start"),
SchedulerTest("stopped-monitor-01", "Stopped Monitor - failed started"),
SchedulerTest("stopped-monitor-02", "Stopped Monitor - started multi-up"),
SchedulerTest("stopped-monitor-03", "Stopped Monitor - stop started"),
SchedulerTest("stopped-monitor-04", "Stopped Monitor - failed stop"),
SchedulerTest("stopped-monitor-05", "Stopped Monitor - start unmanaged"),
SchedulerTest("stopped-monitor-06", "Stopped Monitor - unmanaged multi-up"),
SchedulerTest("stopped-monitor-07", "Stopped Monitor - start unmanaged multi-up"),
SchedulerTest("stopped-monitor-08", "Stopped Monitor - migrate"),
SchedulerTest("stopped-monitor-09", "Stopped Monitor - unmanage started"),
SchedulerTest("stopped-monitor-10", "Stopped Monitor - unmanaged started multi-up"),
SchedulerTest("stopped-monitor-11", "Stopped Monitor - stop unmanaged started"),
SchedulerTest("stopped-monitor-12", "Stopped Monitor - unmanaged started multi-up (target-role=Stopped)"),
SchedulerTest("stopped-monitor-20", "Stopped Monitor - initial stop"),
SchedulerTest("stopped-monitor-21", "Stopped Monitor - stopped single-up"),
SchedulerTest("stopped-monitor-22", "Stopped Monitor - stopped multi-up"),
SchedulerTest("stopped-monitor-23", "Stopped Monitor - start stopped"),
SchedulerTest("stopped-monitor-24", "Stopped Monitor - unmanage stopped"),
SchedulerTest("stopped-monitor-25", "Stopped Monitor - unmanaged stopped multi-up"),
SchedulerTest("stopped-monitor-26", "Stopped Monitor - start unmanaged stopped"),
SchedulerTest("stopped-monitor-27", "Stopped Monitor - unmanaged stopped multi-up (target-role=Started)"),
SchedulerTest("stopped-monitor-30", "Stopped Monitor - new node started"),
SchedulerTest("stopped-monitor-31", "Stopped Monitor - new node stopped"),
]),
SchedulerTestGroup([
# This is a combo test to check:
# - probe timeout defaults to the minimum-interval monitor's
# - duplicate recurring operations are ignored
# - if timeout spec is bad, the default timeout is used
# - failure is blocked with on-fail=block even if ISO8601 interval is specified
# - started/stopped role monitors are started/stopped on right nodes
SchedulerTest("intervals", "Recurring monitor interval handling"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-primitive-1", "Ticket - Primitive (loss-policy=stop, initial)"),
SchedulerTest("ticket-primitive-2", "Ticket - Primitive (loss-policy=stop, granted)"),
SchedulerTest("ticket-primitive-3", "Ticket - Primitive (loss-policy-stop, revoked)"),
SchedulerTest("ticket-primitive-4", "Ticket - Primitive (loss-policy=demote, initial)"),
SchedulerTest("ticket-primitive-5", "Ticket - Primitive (loss-policy=demote, granted)"),
SchedulerTest("ticket-primitive-6", "Ticket - Primitive (loss-policy=demote, revoked)"),
SchedulerTest("ticket-primitive-7", "Ticket - Primitive (loss-policy=fence, initial)"),
SchedulerTest("ticket-primitive-8", "Ticket - Primitive (loss-policy=fence, granted)"),
SchedulerTest("ticket-primitive-9", "Ticket - Primitive (loss-policy=fence, revoked)"),
SchedulerTest("ticket-primitive-10", "Ticket - Primitive (loss-policy=freeze, initial)"),
SchedulerTest("ticket-primitive-11", "Ticket - Primitive (loss-policy=freeze, granted)"),
SchedulerTest("ticket-primitive-12", "Ticket - Primitive (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-primitive-13", "Ticket - Primitive (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-primitive-14", "Ticket - Primitive (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-primitive-15", "Ticket - Primitive (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-primitive-16", "Ticket - Primitive (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-primitive-17", "Ticket - Primitive (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-primitive-18", "Ticket - Primitive (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-primitive-19", "Ticket - Primitive (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-primitive-20", "Ticket - Primitive (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-primitive-21", "Ticket - Primitive (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-primitive-22", "Ticket - Primitive (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-primitive-23", "Ticket - Primitive (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-primitive-24", "Ticket - Primitive (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-group-1", "Ticket - Group (loss-policy=stop, initial)"),
SchedulerTest("ticket-group-2", "Ticket - Group (loss-policy=stop, granted)"),
SchedulerTest("ticket-group-3", "Ticket - Group (loss-policy-stop, revoked)"),
SchedulerTest("ticket-group-4", "Ticket - Group (loss-policy=demote, initial)"),
SchedulerTest("ticket-group-5", "Ticket - Group (loss-policy=demote, granted)"),
SchedulerTest("ticket-group-6", "Ticket - Group (loss-policy=demote, revoked)"),
SchedulerTest("ticket-group-7", "Ticket - Group (loss-policy=fence, initial)"),
SchedulerTest("ticket-group-8", "Ticket - Group (loss-policy=fence, granted)"),
SchedulerTest("ticket-group-9", "Ticket - Group (loss-policy=fence, revoked)"),
SchedulerTest("ticket-group-10", "Ticket - Group (loss-policy=freeze, initial)"),
SchedulerTest("ticket-group-11", "Ticket - Group (loss-policy=freeze, granted)"),
SchedulerTest("ticket-group-12", "Ticket - Group (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-group-13", "Ticket - Group (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-group-14", "Ticket - Group (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-group-15", "Ticket - Group (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-group-16", "Ticket - Group (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-group-17", "Ticket - Group (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-group-18", "Ticket - Group (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-group-19", "Ticket - Group (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-group-20", "Ticket - Group (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-group-21", "Ticket - Group (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-group-22", "Ticket - Group (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-group-23", "Ticket - Group (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-group-24", "Ticket - Group (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-clone-1", "Ticket - Clone (loss-policy=stop, initial)"),
SchedulerTest("ticket-clone-2", "Ticket - Clone (loss-policy=stop, granted)"),
SchedulerTest("ticket-clone-3", "Ticket - Clone (loss-policy-stop, revoked)"),
SchedulerTest("ticket-clone-4", "Ticket - Clone (loss-policy=demote, initial)"),
SchedulerTest("ticket-clone-5", "Ticket - Clone (loss-policy=demote, granted)"),
SchedulerTest("ticket-clone-6", "Ticket - Clone (loss-policy=demote, revoked)"),
SchedulerTest("ticket-clone-7", "Ticket - Clone (loss-policy=fence, initial)"),
SchedulerTest("ticket-clone-8", "Ticket - Clone (loss-policy=fence, granted)"),
SchedulerTest("ticket-clone-9", "Ticket - Clone (loss-policy=fence, revoked)"),
SchedulerTest("ticket-clone-10", "Ticket - Clone (loss-policy=freeze, initial)"),
SchedulerTest("ticket-clone-11", "Ticket - Clone (loss-policy=freeze, granted)"),
SchedulerTest("ticket-clone-12", "Ticket - Clone (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-clone-13", "Ticket - Clone (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-clone-14", "Ticket - Clone (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-clone-15", "Ticket - Clone (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-clone-16", "Ticket - Clone (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-clone-17", "Ticket - Clone (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-clone-18", "Ticket - Clone (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-clone-19", "Ticket - Clone (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-clone-20", "Ticket - Clone (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-clone-21", "Ticket - Clone (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-clone-22", "Ticket - Clone (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-clone-23", "Ticket - Clone (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-clone-24", "Ticket - Clone (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-promoted-1", "Ticket - Promoted (loss-policy=stop, initial)"),
SchedulerTest("ticket-promoted-2", "Ticket - Promoted (loss-policy=stop, granted)"),
SchedulerTest("ticket-promoted-3", "Ticket - Promoted (loss-policy-stop, revoked)"),
SchedulerTest("ticket-promoted-4", "Ticket - Promoted (loss-policy=demote, initial)"),
SchedulerTest("ticket-promoted-5", "Ticket - Promoted (loss-policy=demote, granted)"),
SchedulerTest("ticket-promoted-6", "Ticket - Promoted (loss-policy=demote, revoked)"),
SchedulerTest("ticket-promoted-7", "Ticket - Promoted (loss-policy=fence, initial)"),
SchedulerTest("ticket-promoted-8", "Ticket - Promoted (loss-policy=fence, granted)"),
SchedulerTest("ticket-promoted-9", "Ticket - Promoted (loss-policy=fence, revoked)"),
SchedulerTest("ticket-promoted-10", "Ticket - Promoted (loss-policy=freeze, initial)"),
SchedulerTest("ticket-promoted-11", "Ticket - Promoted (loss-policy=freeze, granted)"),
SchedulerTest("ticket-promoted-12", "Ticket - Promoted (loss-policy=freeze, revoked)"),
SchedulerTest("ticket-promoted-13", "Ticket - Promoted (loss-policy=stop, standby, granted)"),
SchedulerTest("ticket-promoted-14", "Ticket - Promoted (loss-policy=stop, granted, standby)"),
SchedulerTest("ticket-promoted-15", "Ticket - Promoted (loss-policy=stop, standby, revoked)"),
SchedulerTest("ticket-promoted-16", "Ticket - Promoted (loss-policy=demote, standby, granted)"),
SchedulerTest("ticket-promoted-17", "Ticket - Promoted (loss-policy=demote, granted, standby)"),
SchedulerTest("ticket-promoted-18", "Ticket - Promoted (loss-policy=demote, standby, revoked)"),
SchedulerTest("ticket-promoted-19", "Ticket - Promoted (loss-policy=fence, standby, granted)"),
SchedulerTest("ticket-promoted-20", "Ticket - Promoted (loss-policy=fence, granted, standby)"),
SchedulerTest("ticket-promoted-21", "Ticket - Promoted (loss-policy=fence, standby, revoked)"),
SchedulerTest("ticket-promoted-22", "Ticket - Promoted (loss-policy=freeze, standby, granted)"),
SchedulerTest("ticket-promoted-23", "Ticket - Promoted (loss-policy=freeze, granted, standby)"),
SchedulerTest("ticket-promoted-24", "Ticket - Promoted (loss-policy=freeze, standby, revoked)"),
]),
SchedulerTestGroup([
SchedulerTest("ticket-rsc-sets-1", "Ticket - Resource sets (1 ticket, initial)"),
SchedulerTest("ticket-rsc-sets-2", "Ticket - Resource sets (1 ticket, granted)"),
SchedulerTest("ticket-rsc-sets-3", "Ticket - Resource sets (1 ticket, revoked)"),
SchedulerTest("ticket-rsc-sets-4", "Ticket - Resource sets (2 tickets, initial)"),
SchedulerTest("ticket-rsc-sets-5", "Ticket - Resource sets (2 tickets, granted)"),
SchedulerTest("ticket-rsc-sets-6", "Ticket - Resource sets (2 tickets, granted)"),
SchedulerTest("ticket-rsc-sets-7", "Ticket - Resource sets (2 tickets, revoked)"),
SchedulerTest("ticket-rsc-sets-8", "Ticket - Resource sets (1 ticket, standby, granted)"),
SchedulerTest("ticket-rsc-sets-9", "Ticket - Resource sets (1 ticket, granted, standby)"),
SchedulerTest("ticket-rsc-sets-10", "Ticket - Resource sets (1 ticket, standby, revoked)"),
SchedulerTest("ticket-rsc-sets-11", "Ticket - Resource sets (2 tickets, standby, granted)"),
SchedulerTest("ticket-rsc-sets-12", "Ticket - Resource sets (2 tickets, standby, granted)"),
SchedulerTest("ticket-rsc-sets-13", "Ticket - Resource sets (2 tickets, granted, standby)"),
SchedulerTest("ticket-rsc-sets-14", "Ticket - Resource sets (2 tickets, standby, revoked)"),
SchedulerTest("cluster-specific-params", "Cluster-specific instance attributes based on rules"),
SchedulerTest("site-specific-params", "Site-specific instance attributes based on rules"),
]),
SchedulerTestGroup([
SchedulerTest("template-1", "Template - 1"),
SchedulerTest("template-2", "Template - 2"),
SchedulerTest("template-3", "Template - 3 (merge operations)"),
SchedulerTest("template-coloc-1", "Template - Colocation 1"),
SchedulerTest("template-coloc-2", "Template - Colocation 2"),
SchedulerTest("template-coloc-3", "Template - Colocation 3"),
SchedulerTest("template-order-1", "Template - Order 1"),
SchedulerTest("template-order-2", "Template - Order 2"),
SchedulerTest("template-order-3", "Template - Order 3"),
SchedulerTest("template-ticket", "Template - Ticket"),
SchedulerTest("template-rsc-sets-1", "Template - Resource Sets 1"),
SchedulerTest("template-rsc-sets-2", "Template - Resource Sets 2"),
SchedulerTest("template-rsc-sets-3", "Template - Resource Sets 3"),
SchedulerTest("template-rsc-sets-4", "Template - Resource Sets 4"),
SchedulerTest("template-clone-primitive", "Cloned primitive from template"),
SchedulerTest("template-clone-group", "Cloned group from template"),
SchedulerTest("location-sets-templates", "Resource sets and templates - Location"),
SchedulerTest("tags-coloc-order-1", "Tags - Colocation and Order (Simple)"),
SchedulerTest("tags-coloc-order-2", "Tags - Colocation and Order (Resource Sets with Templates)"),
SchedulerTest("tags-location", "Tags - Location"),
SchedulerTest("tags-ticket", "Tags - Ticket"),
]),
SchedulerTestGroup([
SchedulerTest("container-1", "Container - initial"),
SchedulerTest("container-2", "Container - monitor failed"),
SchedulerTest("container-3", "Container - stop failed"),
SchedulerTest("container-4", "Container - reached migration-threshold"),
SchedulerTest("container-group-1", "Container in group - initial"),
SchedulerTest("container-group-2", "Container in group - monitor failed"),
SchedulerTest("container-group-3", "Container in group - stop failed"),
SchedulerTest("container-group-4", "Container in group - reached migration-threshold"),
SchedulerTest("container-is-remote-node", "Place resource within container when container is remote-node"),
SchedulerTest("bug-rh-1097457", "Kill user defined container/contents ordering"),
SchedulerTest("bug-cl-5247", "Graph loop when recovering m/s resource in a container"),
SchedulerTest("bundle-order-startup", "Bundle startup ordering"),
SchedulerTest("bundle-order-partial-start",
"Bundle startup ordering when some dependencies are already running"),
SchedulerTest("bundle-order-partial-start-2",
"Bundle startup ordering when some dependencies and the container are already running"),
SchedulerTest("bundle-order-stop", "Bundle stop ordering"),
SchedulerTest("bundle-order-partial-stop", "Bundle startup ordering when some dependencies are already stopped"),
SchedulerTest("bundle-order-stop-on-remote", "Stop nested resource after bringing up the connection"),
SchedulerTest("bundle-order-startup-clone", "Prevent startup because bundle isn't promoted"),
SchedulerTest("bundle-order-startup-clone-2", "Bundle startup with clones"),
SchedulerTest("bundle-order-stop-clone", "Stop bundle because clone is stopping"),
SchedulerTest("bundle-interleave-start", "Interleave bundle starts"),
SchedulerTest("bundle-interleave-promote", "Interleave bundle promotes"),
SchedulerTest("bundle-nested-colocation", "Colocation of nested connection resources"),
SchedulerTest("bundle-order-fencing",
"Order pseudo bundle fencing after parent node fencing if both are happening"),
SchedulerTest("bundle-probe-order-1", "order 1"),
SchedulerTest("bundle-probe-order-2", "order 2"),
SchedulerTest("bundle-probe-order-3", "order 3"),
SchedulerTest("bundle-probe-remotes", "Ensure remotes get probed too"),
SchedulerTest("bundle-replicas-change", "Change bundle from 1 replica to multiple"),
SchedulerTest("bundle-connection-with-container", "Don't move a container due to connection preferences"),
SchedulerTest("nested-remote-recovery", "Recover bundle's container hosted on remote node"),
SchedulerTest("bundle-promoted-location-1",
"Promotable bundle, positive location"),
SchedulerTest("bundle-promoted-location-2",
"Promotable bundle, negative location"),
SchedulerTest("bundle-promoted-location-3",
"Promotable bundle, positive location for promoted role"),
SchedulerTest("bundle-promoted-location-4",
"Promotable bundle, negative location for promoted role"),
SchedulerTest("bundle-promoted-location-5",
"Promotable bundle, positive location for unpromoted role"),
SchedulerTest("bundle-promoted-location-6",
"Promotable bundle, negative location for unpromoted role"),
SchedulerTest("bundle-promoted-colocation-1",
"Primary promoted bundle, dependent primitive (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-2",
"Primary promoted bundle, dependent primitive (optional coloc)"),
SchedulerTest("bundle-promoted-colocation-3",
"Dependent promoted bundle, primary primitive (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-4",
"Dependent promoted bundle, primary primitive (optional coloc)"),
SchedulerTest("bundle-promoted-colocation-5",
"Primary and dependent promoted bundle instances (mandatory coloc)"),
SchedulerTest("bundle-promoted-colocation-6",
"Primary and dependent promoted bundle instances (optional coloc)"),
SchedulerTest("bundle-promoted-anticolocation-1",
"Primary promoted bundle, dependent primitive (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-2",
"Primary promoted bundle, dependent primitive (optional anti)"),
SchedulerTest("bundle-promoted-anticolocation-3",
"Dependent promoted bundle, primary primitive (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-4",
"Dependent promoted bundle, primary primitive (optional anti)"),
SchedulerTest("bundle-promoted-anticolocation-5",
"Primary and dependent promoted bundle instances (mandatory anti)"),
SchedulerTest("bundle-promoted-anticolocation-6",
"Primary and dependent promoted bundle instances (optional anti)"),
]),
SchedulerTestGroup([
SchedulerTest("whitebox-fail1", "Fail whitebox container rsc"),
SchedulerTest("whitebox-fail2", "Fail cluster connection to guest node"),
SchedulerTest("whitebox-fail3", "Failed containers should not run nested on remote nodes"),
SchedulerTest("whitebox-start", "Start whitebox container with resources assigned to it"),
SchedulerTest("whitebox-stop", "Stop whitebox container with resources assigned to it"),
SchedulerTest("whitebox-move", "Move whitebox container with resources assigned to it"),
SchedulerTest("whitebox-asymmetric", "Verify connection rsc opts-in based on container resource"),
SchedulerTest("whitebox-ms-ordering", "Verify promote/demote can not occur before connection is established"),
SchedulerTest("whitebox-ms-ordering-move", "Stop/Start cycle within a moving container"),
SchedulerTest("whitebox-orphaned", "Properly shutdown orphaned whitebox container"),
SchedulerTest("whitebox-orphan-ms", "Properly tear down orphan ms resources on remote-nodes"),
SchedulerTest("whitebox-unexpectedly-running", "Recover container nodes the cluster did not start"),
SchedulerTest("whitebox-migrate1", "Migrate both container and connection resource"),
SchedulerTest("whitebox-imply-stop-on-fence",
"imply stop action on container node rsc when host node is fenced"),
SchedulerTest("whitebox-nested-group", "Verify guest remote-node works nested in a group"),
SchedulerTest("guest-node-host-dies", "Verify guest node is recovered if host goes away"),
SchedulerTest("guest-node-cleanup", "Order guest node connection recovery after container probe"),
SchedulerTest("guest-host-not-fenceable", "Actions on guest node are unrunnable if host is unclean and cannot be fenced"),
]),
SchedulerTestGroup([
SchedulerTest("remote-startup-probes", "Baremetal remote-node startup probes"),
SchedulerTest("remote-startup", "Startup a newly discovered remote-nodes with no status"),
SchedulerTest("remote-fence-unclean", "Fence unclean baremetal remote-node"),
SchedulerTest("remote-fence-unclean2",
"Fence baremetal remote-node after cluster node fails and connection can not be recovered"),
SchedulerTest("remote-fence-unclean-3", "Probe failed remote nodes (triggers fencing)"),
SchedulerTest("remote-move", "Move remote-node connection resource"),
SchedulerTest("remote-disable", "Disable a baremetal remote-node"),
SchedulerTest("remote-probe-disable", "Probe then stop a baremetal remote-node"),
SchedulerTest("remote-orphaned", "Properly shutdown orphaned connection resource"),
SchedulerTest("remote-orphaned2",
"verify we can handle orphaned remote connections with active resources on the remote"),
SchedulerTest("remote-recover", "Recover connection resource after cluster-node fails"),
SchedulerTest("remote-stale-node-entry",
"Make sure we properly handle leftover remote-node entries in the node section"),
SchedulerTest("remote-partial-migrate",
"Make sure partial migrations are handled before ops on the remote node"),
SchedulerTest("remote-partial-migrate2",
"Make sure partial migration target is prefered for remote connection"),
SchedulerTest("remote-recover-fail", "Make sure start failure causes fencing if rsc are active on remote"),
SchedulerTest("remote-start-fail",
"Make sure a start failure does not result in fencing if no active resources are on remote"),
SchedulerTest("remote-unclean2",
"Make monitor failure always results in fencing, even if no rsc are active on remote"),
SchedulerTest("remote-fence-before-reconnect", "Fence before clearing recurring monitor failure"),
SchedulerTest("remote-recovery", "Recover remote connections before attempting demotion"),
SchedulerTest("remote-recover-connection", "Optimistically recovery of only the connection"),
SchedulerTest("remote-recover-all", "Fencing when the connection has no home"),
SchedulerTest("remote-recover-no-resources", "Fencing when the connection has no home and no active resources"),
SchedulerTest("remote-recover-unknown",
"Fencing when the connection has no home and the remote has no operation history"),
SchedulerTest("remote-reconnect-delay", "Waiting for remote reconnect interval to expire"),
SchedulerTest("remote-connection-unrecoverable",
"Remote connection host must be fenced, with connection unrecoverable"),
SchedulerTest("remote-connection-shutdown", "Remote connection shutdown"),
SchedulerTest("cancel-behind-moving-remote",
"Route recurring monitor cancellations through original node of a moving remote connection"),
]),
SchedulerTestGroup([
SchedulerTest("resource-discovery", "Exercises resource-discovery location constraint option"),
SchedulerTest("rsc-discovery-per-node", "Disable resource discovery per node"),
SchedulerTest("shutdown-lock", "Ensure shutdown lock works properly"),
SchedulerTest("shutdown-lock-expiration", "Ensure shutdown lock expiration works properly"),
]),
SchedulerTestGroup([
SchedulerTest("op-defaults", "Test op_defaults conditional expressions"),
SchedulerTest("op-defaults-2", "Test op_defaults AND'ed conditional expressions"),
SchedulerTest("op-defaults-3", "Test op_defaults precedence"),
SchedulerTest("rsc-defaults", "Test rsc_defaults conditional expressions"),
SchedulerTest("rsc-defaults-2", "Test rsc_defaults conditional expressions without type"),
]),
SchedulerTestGroup([
SchedulerTest("stop-all-resources", "Test stop-all-resources=true "),
]),
SchedulerTestGroup([
SchedulerTest("ocf_degraded-remap-ocf_ok", "Test degraded remapped to OK"),
SchedulerTest("ocf_degraded_promoted-remap-ocf_ok", "Test degraded promoted remapped to OK"),
]),
]
TESTS_64BIT = [
SchedulerTestGroup([
SchedulerTest("year-2038", "Check handling of timestamps beyond 2038-01-19 03:14:08 UTC"),
]),
]
def is_executable(path):
""" Check whether a file at a given path is executable. """
try:
return os.stat(path)[stat.ST_MODE] & stat.S_IXUSR
except OSError:
return False
def diff(file1, file2, **kwargs):
""" Call diff on two files """
return subprocess.call([ "diff", "-u", "-N", "--ignore-all-space",
"--ignore-blank-lines", file1, file2 ], **kwargs)
def sort_file(filename):
""" Sort a file alphabetically """
with io.open(filename, "rt") as f:
lines = sorted(f)
with io.open(filename, "wt") as f:
f.writelines(lines)
def remove_files(filenames):
""" Remove a list of files """
for filename in filenames:
try:
os.remove(filename)
except OSError:
pass
def normalize(filename):
""" Remove text from a file that isn't important for comparison """
if not hasattr(normalize, "patterns"):
normalize.patterns = [
re.compile(r'crm_feature_set="[^"]*"'),
re.compile(r'batch-limit="[0-9]*"')
]
if os.path.isfile(filename):
with io.open(filename, "rt") as f:
lines = f.readlines()
with io.open(filename, "wt") as f:
for line in lines:
for pattern in normalize.patterns:
line = pattern.sub("", line)
f.write(line)
def cat(filename, dest=sys.stdout):
""" Copy a file to a destination file descriptor """
with io.open(filename, "rt") as f:
shutil.copyfileobj(f, dest)
class CtsScheduler(object):
""" Regression tests for Pacemaker's scheduler """
def _parse_args(self, argv):
""" Parse command-line arguments """
parser = argparse.ArgumentParser(description=DESC)
parser.add_argument('-V', '--verbose', action='count',
help='Display any differences from expected output')
parser.add_argument('--run', metavar='TEST',
help=('Run only single specified test (any further '
'arguments will be passed to crm_simulate)'))
parser.add_argument('--update', action='store_true',
help='Update expected results with actual results')
parser.add_argument('-b', '--binary', metavar='PATH',
help='Specify path to crm_simulate')
parser.add_argument('-i', '--io-dir', metavar='PATH',
help='Specify path to regression test data directory')
parser.add_argument('-o', '--out-dir', metavar='PATH',
help='Specify where intermediate and output files should go')
parser.add_argument('-v', '--valgrind', action='store_true',
help='Run all commands under valgrind')
parser.add_argument('--valgrind-dhat', action='store_true',
help='Run all commands under valgrind with heap analyzer')
parser.add_argument('--valgrind-skip-output', action='store_true',
help='If running under valgrind, do not display output')
parser.add_argument('--testcmd-options', metavar='OPTIONS', default='',
help='Additional options for command under test')
# argparse can't handle "everything after --run TEST", so grab that
self.single_test_args = []
narg = 0
for arg in argv:
narg = narg + 1
if arg == '--run':
(argv, self.single_test_args) = (argv[:narg+1], argv[narg+1:])
break
self.args = parser.parse_args(argv[1:])
def _error(self, s):
print(" * ERROR: %s" % s)
def _failed(self, s):
print(" * FAILED: %s" % s)
def _get_valgrind_cmd(self):
""" Return command arguments needed (or not) to run valgrind """
if self.args.valgrind:
os.environ['G_SLICE'] = "always-malloc"
return [
"valgrind",
"-q",
"--gen-suppressions=all",
"--time-stamp=yes",
"--trace-children=no",
"--show-reachable=no",
"--leak-check=full",
"--num-callers=20",
"--suppressions=%s/valgrind-pcmk.suppressions" % (self.test_home)
]
if self.args.valgrind_dhat:
os.environ['G_SLICE'] = "always-malloc"
return [
"valgrind",
"--tool=exp-dhat",
"--time-stamp=yes",
"--trace-children=no",
"--show-top-n=100",
"--num-callers=4"
]
return []
def _get_simulator_cmd(self):
""" Locate the simulation binary """
if self.args.binary is None:
self.args.binary = BuildOptions._BUILD_DIR + "/tools/crm_simulate"
if not is_executable(self.args.binary):
self.args.binary = BuildOptions.SBIN_DIR + "/crm_simulate"
if not is_executable(self.args.binary):
# @TODO it would be more pythonic to raise an exception
self._error("Test binary " + self.args.binary + " not found")
sys.exit(ExitStatus.NOT_INSTALLED)
return [ self.args.binary ] + shlex.split(self.args.testcmd_options)
def set_schema_env(self):
""" Ensure schema directory environment variable is set, if possible """
try:
return os.environ['PCMK_schema_directory']
except KeyError:
for d in [ os.path.join(BuildOptions._BUILD_DIR, "xml"),
BuildOptions.SCHEMA_DIR ]:
if os.path.isdir(d):
os.environ['PCMK_schema_directory'] = d
return d
return None
def __init__(self, argv=sys.argv):
# Ensure all command output is in portable locale for comparison
os.environ['LC_ALL'] = "C"
self._parse_args(argv)
# Where this executable lives
self.test_home = os.path.dirname(os.path.realpath(argv[0]))
# Where test data resides
if self.args.io_dir is None:
self.args.io_dir = os.path.join(self.test_home, "scheduler")
self.xml_input_dir = os.path.join(self.args.io_dir, "xml")
self.expected_dir = os.path.join(self.args.io_dir, "exp")
self.dot_expected_dir = os.path.join(self.args.io_dir, "dot")
self.scores_dir = os.path.join(self.args.io_dir, "scores")
self.summary_dir = os.path.join(self.args.io_dir, "summary")
self.stderr_expected_dir = os.path.join(self.args.io_dir, "stderr")
# Create a temporary directory to store diff file
self.failed_dir = tempfile.mkdtemp(prefix='cts-scheduler_')
# Where to store generated files
if self.args.out_dir is None:
self.args.out_dir = self.args.io_dir
self.failed_filename = os.path.join(self.failed_dir, "test-output.diff")
else:
self.failed_filename = os.path.join(self.args.out_dir, "test-output.diff")
os.environ['CIB_shadow_dir'] = self.args.out_dir
self.failed_file = None
self.outfile_out_dir = os.path.join(self.args.out_dir, "out")
self.dot_out_dir = os.path.join(self.args.out_dir, "dot")
self.scores_out_dir = os.path.join(self.args.out_dir, "scores")
self.summary_out_dir = os.path.join(self.args.out_dir, "summary")
self.stderr_out_dir = os.path.join(self.args.out_dir, "stderr")
self.valgrind_out_dir = os.path.join(self.args.out_dir, "valgrind")
# Single test mode (if requested)
try:
# User can give test base name or file name of a test input
self.args.run = os.path.splitext(os.path.basename(self.args.run))[0]
except (AttributeError, TypeError):
pass # --run was not specified
self.set_schema_env()
# Arguments needed (or not) to run commands
self.valgrind_args = self._get_valgrind_cmd()
self.simulate_args = self._get_simulator_cmd()
# Test counters
self.num_failed = 0
self.num_tests = 0
# Ensure that the main output directory exists
# We don't want to create it with os.makedirs below
if not os.path.isdir(self.args.out_dir):
self._error("Output directory missing; can't create output files")
sys.exit(ExitStatus.CANTCREAT)
# Create output subdirectories if they don't exist
try:
os.makedirs(self.outfile_out_dir, 0o755, True)
os.makedirs(self.dot_out_dir, 0o755, True)
os.makedirs(self.scores_out_dir, 0o755, True)
os.makedirs(self.summary_out_dir, 0o755, True)
os.makedirs(self.stderr_out_dir, 0o755, True)
if self.valgrind_args:
os.makedirs(self.valgrind_out_dir, 0o755, True)
except OSError as ex:
self._error("Unable to create output subdirectory: %s" % ex)
remove_files([
self.outfile_out_dir,
self.dot_out_dir,
self.scores_out_dir,
self.summary_out_dir,
self.stderr_out_dir,
])
sys.exit(ExitStatus.CANTCREAT)
def _compare_files(self, filename1, filename2):
""" Add any file differences to failed results """
if diff(filename1, filename2, stdout=subprocess.DEVNULL) != 0:
diff(filename1, filename2, stdout=self.failed_file, stderr=subprocess.DEVNULL)
self.failed_file.write("\n")
return True
return False
def run_one(self, test_name, test_desc, test_args):
""" Run one scheduler test """
print(" Test %-41s %s" % ((test_name + ":"), test_desc))
did_fail = False
self.num_tests = self.num_tests + 1
# Test inputs
input_filename = os.path.join(
self.xml_input_dir, "%s.xml" % test_name)
expected_filename = os.path.join(
self.expected_dir, "%s.exp" % test_name)
dot_expected_filename = os.path.join(
self.dot_expected_dir, "%s.dot" % test_name)
scores_filename = os.path.join(
self.scores_dir, "%s.scores" % test_name)
summary_filename = os.path.join(
self.summary_dir, "%s.summary" % test_name)
stderr_expected_filename = os.path.join(
self.stderr_expected_dir, "%s.stderr" % test_name)
# (Intermediate) test outputs
output_filename = os.path.join(
self.outfile_out_dir, "%s.out" % test_name)
dot_output_filename = os.path.join(
self.dot_out_dir, "%s.dot.pe" % test_name)
score_output_filename = os.path.join(
self.scores_out_dir, "%s.scores.pe" % test_name)
summary_output_filename = os.path.join(
self.summary_out_dir, "%s.summary.pe" % test_name)
stderr_output_filename = os.path.join(
self.stderr_out_dir, "%s.stderr.pe" % test_name)
valgrind_output_filename = os.path.join(
self.valgrind_out_dir, "%s.valgrind" % test_name)
# Common arguments for running test
test_cmd = []
if self.valgrind_args:
test_cmd = self.valgrind_args + [ "--log-file=%s" % valgrind_output_filename ]
test_cmd = test_cmd + self.simulate_args
# @TODO It would be more pythonic to raise exceptions for errors,
# then perhaps it would be nice to make a single-test class
# Ensure necessary test inputs exist
if not os.path.isfile(input_filename):
self._error("No input")
self.num_failed = self.num_failed + 1
return ExitStatus.NOINPUT
if not self.args.update and not os.path.isfile(expected_filename):
self._error("no stored output")
return ExitStatus.NOINPUT
# Run simulation to generate summary output
test_cmd_full = test_cmd + [ '-x', input_filename, '-S' ] + test_args
if self.args.run: # Single test mode
print(" ".join(test_cmd_full))
with io.open(summary_output_filename, "wt") as f:
simulation = subprocess.Popen(test_cmd_full, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=os.environ)
# This makes diff happy regardless of --enable-compat-2.0.
# Use sed -E to make Linux and BSD special characters more compatible.
sed = subprocess.Popen(["sed", "-E",
"-e", "s/ocf::/ocf:/g",
"-e", r"s/Masters:/Promoted:/",
"-e", r"s/Slaves:/Unpromoted:/",
"-e", r"s/ Master( |\[|$)/ Promoted\1/",
"-e", r"s/ Slave / Unpromoted /",
], stdin=simulation.stdout, stdout=f,
stderr=subprocess.STDOUT)
simulation.stdout.close()
sed.communicate()
if self.args.run:
cat(summary_output_filename)
# Re-run simulation to generate dot, graph, and scores
test_cmd_full = test_cmd + [
'-x', input_filename,
'-D', dot_output_filename,
'-G', output_filename,
'-sSQ' ] + test_args
with io.open(stderr_output_filename, "wt") as f_stderr, \
io.open(score_output_filename, "wt") as f_score:
rc = subprocess.call(test_cmd_full, stdout=f_score, stderr=f_stderr, env=os.environ)
# Check for test command failure
if rc != ExitStatus.OK:
self._failed("Test returned: %d" % rc)
did_fail = True
print(" ".join(test_cmd_full))
# Check for valgrind errors
if self.valgrind_args and not self.args.valgrind_skip_output:
if os.stat(valgrind_output_filename).st_size > 0:
self._failed("Valgrind reported errors")
did_fail = True
cat(valgrind_output_filename)
remove_files([ valgrind_output_filename ])
# Check for core dump
if os.path.isfile("core"):
self._failed("Core-file detected: core." + test_name)
did_fail = True
os.rename("core", "%s/core.%s" % (self.test_home, test_name))
# Check any stderr output
if os.path.isfile(stderr_expected_filename):
if self._compare_files(stderr_expected_filename, stderr_output_filename):
self._failed("stderr changed")
did_fail = True
elif os.stat(stderr_output_filename).st_size > 0:
self._failed("Output was written to stderr")
did_fail = True
cat(stderr_output_filename)
remove_files([ stderr_output_filename ])
# Check whether output graph exists, and normalize it
if (not os.path.isfile(output_filename)
or os.stat(output_filename).st_size == 0):
self._error("No graph produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ output_filename ])
return ExitStatus.ERROR
normalize(output_filename)
# Check whether dot output exists, and sort it
if (not os.path.isfile(dot_output_filename) or
os.stat(dot_output_filename).st_size == 0):
self._error("No dot-file summary produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ dot_output_filename, output_filename ])
return ExitStatus.ERROR
with io.open(dot_output_filename, "rt") as f:
first_line = f.readline() # "digraph" line with opening brace
lines = f.readlines()
last_line = lines[-1] # closing brace
del lines[-1]
lines = sorted(set(lines)) # unique sort
with io.open(dot_output_filename, "wt") as f:
f.write(first_line)
f.writelines(lines)
f.write(last_line)
# Check whether score output exists, and sort it
if (not os.path.isfile(score_output_filename)
or os.stat(score_output_filename).st_size == 0):
self._error("No allocation scores produced")
did_fail = True
self.num_failed = self.num_failed + 1
remove_files([ score_output_filename, output_filename ])
return ExitStatus.ERROR
else:
sort_file(score_output_filename)
if self.args.update:
shutil.copyfile(output_filename, expected_filename)
shutil.copyfile(dot_output_filename, dot_expected_filename)
shutil.copyfile(score_output_filename, scores_filename)
shutil.copyfile(summary_output_filename, summary_filename)
print(" Updated expected outputs")
if self._compare_files(summary_filename, summary_output_filename):
self._failed("summary changed")
did_fail = True
if self._compare_files(dot_expected_filename, dot_output_filename):
self._failed("dot-file summary changed")
did_fail = True
else:
remove_files([ dot_output_filename ])
if self._compare_files(expected_filename, output_filename):
self._failed("xml-file changed")
did_fail = True
if self._compare_files(scores_filename, score_output_filename):
self._failed("scores-file changed")
did_fail = True
remove_files([ output_filename,
dot_output_filename,
score_output_filename,
summary_output_filename])
if did_fail:
self.num_failed = self.num_failed + 1
return ExitStatus.ERROR
return ExitStatus.OK
def run_all(self):
""" Run all defined tests """
if platform.architecture()[0] == "64bit":
TESTS.extend(TESTS_64BIT)
for group in TESTS:
for test in group.tests:
self.run_one(test.name, test.desc, test.args)
print()
def _print_summary(self):
""" Print a summary of parameters for this test run """
print("Test home is:\t" + self.test_home)
print("Test binary is:\t" + self.args.binary)
if 'PCMK_schema_directory' in os.environ:
print("Schema home is:\t" + os.environ['PCMK_schema_directory'])
if self.valgrind_args != []:
print("Activating memory testing with valgrind")
print()
def _test_results(self):
if self.num_failed == 0:
shutil.rmtree(self.failed_dir)
return ExitStatus.OK
if os.path.isfile(self.failed_filename) and os.stat(self.failed_filename).st_size != 0:
if self.args.verbose:
self._error("Results of %d failed tests (out of %d):" %
(self.num_failed, self.num_tests))
cat(self.failed_filename)
else:
self._error("Results of %d failed tests (out of %d) are in %s" %
(self.num_failed, self.num_tests, self.failed_filename))
self._error("Use -V to display them after running the tests")
else:
self._error("%d (of %d) tests failed (no diff results)" %
(self.num_failed, self.num_tests))
if os.path.isfile(self.failed_filename):
shutil.rmtree(self.failed_dir)
return ExitStatus.ERROR
def find_test(self, name):
if platform.architecture()[0] == "64bit":
TESTS.extend(TESTS_64BIT)
for group in TESTS:
for test in group.tests:
if test.name == name:
return test
return None
def run(self):
""" Run test(s) as specified """
# Check for pre-existing core so we don't think it's from us
if os.path.exists("core"):
self._failed("Can't run with core already present in " + self.test_home)
return ExitStatus.OSFILE
self._print_summary()
# Zero out the error log
self.failed_file = io.open(self.failed_filename, "wt")
if self.args.run is None:
print("Performing the following tests from " + self.args.io_dir)
print()
self.run_all()
print()
self.failed_file.close()
rc = self._test_results()
else:
# Find the test we were asked to run
test = self.find_test(self.args.run)
if test is None:
print("No test named %s" % self.args.run)
return ExitStatus.INVALID_PARAM
# If no arguments were given on the command line, default to the ones
# contained in the test
if self.single_test_args:
args = self.single_test_args
else:
args = test.args
rc = self.run_one(test.name, test.desc, args)
self.failed_file.close()
if self.num_failed > 0:
print("\nFailures:\nThese have also been written to: " + self.failed_filename + "\n")
cat(self.failed_filename)
shutil.rmtree(self.failed_dir)
return rc
if __name__ == "__main__":
sys.exit(CtsScheduler().run())
# vim: set filetype=python expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=120:
diff --git a/cts/scheduler/dot/timeout-by-node.dot b/cts/scheduler/dot/timeout-by-node.dot
deleted file mode 100644
index b4c0b9727d..0000000000
--- a/cts/scheduler/dot/timeout-by-node.dot
+++ /dev/null
@@ -1,40 +0,0 @@
- digraph "g" {
-"rsc1-clone_running_0" [ style=bold color="green" fontcolor="orange"]
-"rsc1-clone_start_0" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1-clone_start_0" -> "rsc1:0_start_0 node2" [ style = bold]
-"rsc1-clone_start_0" -> "rsc1:1_start_0 node3" [ style = bold]
-"rsc1-clone_start_0" -> "rsc1:2_start_0 node4" [ style = bold]
-"rsc1-clone_start_0" -> "rsc1:3_start_0 node5" [ style = bold]
-"rsc1-clone_start_0" -> "rsc1:4_start_0 node1" [ style = bold]
-"rsc1-clone_start_0" [ style=bold color="green" fontcolor="orange"]
-"rsc1:0_monitor_0 node2" -> "rsc1-clone_start_0" [ style = bold]
-"rsc1:0_monitor_0 node2" [ style=bold color="green" fontcolor="black"]
-"rsc1:0_monitor_10000 node2" [ style=bold color="green" fontcolor="black"]
-"rsc1:0_start_0 node2" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1:0_start_0 node2" -> "rsc1:0_monitor_10000 node2" [ style = bold]
-"rsc1:0_start_0 node2" [ style=bold color="green" fontcolor="black"]
-"rsc1:1_monitor_0 node3" -> "rsc1-clone_start_0" [ style = bold]
-"rsc1:1_monitor_0 node3" [ style=bold color="green" fontcolor="black"]
-"rsc1:1_monitor_10000 node3" [ style=bold color="green" fontcolor="black"]
-"rsc1:1_start_0 node3" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1:1_start_0 node3" -> "rsc1:1_monitor_10000 node3" [ style = bold]
-"rsc1:1_start_0 node3" [ style=bold color="green" fontcolor="black"]
-"rsc1:2_monitor_0 node4" -> "rsc1-clone_start_0" [ style = bold]
-"rsc1:2_monitor_0 node4" [ style=bold color="green" fontcolor="black"]
-"rsc1:2_monitor_10000 node4" [ style=bold color="green" fontcolor="black"]
-"rsc1:2_start_0 node4" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1:2_start_0 node4" -> "rsc1:2_monitor_10000 node4" [ style = bold]
-"rsc1:2_start_0 node4" [ style=bold color="green" fontcolor="black"]
-"rsc1:3_monitor_0 node5" -> "rsc1-clone_start_0" [ style = bold]
-"rsc1:3_monitor_0 node5" [ style=bold color="green" fontcolor="black"]
-"rsc1:3_monitor_10000 node5" [ style=bold color="green" fontcolor="black"]
-"rsc1:3_start_0 node5" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1:3_start_0 node5" -> "rsc1:3_monitor_10000 node5" [ style = bold]
-"rsc1:3_start_0 node5" [ style=bold color="green" fontcolor="black"]
-"rsc1:4_monitor_0 node1" -> "rsc1-clone_start_0" [ style = bold]
-"rsc1:4_monitor_0 node1" [ style=bold color="green" fontcolor="black"]
-"rsc1:4_monitor_10000 node1" [ style=bold color="green" fontcolor="black"]
-"rsc1:4_start_0 node1" -> "rsc1-clone_running_0" [ style = bold]
-"rsc1:4_start_0 node1" -> "rsc1:4_monitor_10000 node1" [ style = bold]
-"rsc1:4_start_0 node1" [ style=bold color="green" fontcolor="black"]
-}
diff --git a/cts/scheduler/exp/timeout-by-node.exp b/cts/scheduler/exp/timeout-by-node.exp
deleted file mode 100644
index c93744e04d..0000000000
--- a/cts/scheduler/exp/timeout-by-node.exp
+++ /dev/null
@@ -1,228 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/cts/scheduler/scores/timeout-by-node.scores b/cts/scheduler/scores/timeout-by-node.scores
deleted file mode 100644
index adb96a5d21..0000000000
--- a/cts/scheduler/scores/timeout-by-node.scores
+++ /dev/null
@@ -1,61 +0,0 @@
-
-pcmk__clone_assign: rsc1-clone allocation score on node1: 0
-pcmk__clone_assign: rsc1-clone allocation score on node2: 0
-pcmk__clone_assign: rsc1-clone allocation score on node3: 0
-pcmk__clone_assign: rsc1-clone allocation score on node4: 0
-pcmk__clone_assign: rsc1-clone allocation score on node5: 0
-pcmk__clone_assign: rsc1:0 allocation score on node1: 0
-pcmk__clone_assign: rsc1:0 allocation score on node2: 0
-pcmk__clone_assign: rsc1:0 allocation score on node3: 0
-pcmk__clone_assign: rsc1:0 allocation score on node4: 0
-pcmk__clone_assign: rsc1:0 allocation score on node5: 0
-pcmk__clone_assign: rsc1:1 allocation score on node1: 0
-pcmk__clone_assign: rsc1:1 allocation score on node2: 0
-pcmk__clone_assign: rsc1:1 allocation score on node3: 0
-pcmk__clone_assign: rsc1:1 allocation score on node4: 0
-pcmk__clone_assign: rsc1:1 allocation score on node5: 0
-pcmk__clone_assign: rsc1:2 allocation score on node1: 0
-pcmk__clone_assign: rsc1:2 allocation score on node2: 0
-pcmk__clone_assign: rsc1:2 allocation score on node3: 0
-pcmk__clone_assign: rsc1:2 allocation score on node4: 0
-pcmk__clone_assign: rsc1:2 allocation score on node5: 0
-pcmk__clone_assign: rsc1:3 allocation score on node1: 0
-pcmk__clone_assign: rsc1:3 allocation score on node2: 0
-pcmk__clone_assign: rsc1:3 allocation score on node3: 0
-pcmk__clone_assign: rsc1:3 allocation score on node4: 0
-pcmk__clone_assign: rsc1:3 allocation score on node5: 0
-pcmk__clone_assign: rsc1:4 allocation score on node1: 0
-pcmk__clone_assign: rsc1:4 allocation score on node2: 0
-pcmk__clone_assign: rsc1:4 allocation score on node3: 0
-pcmk__clone_assign: rsc1:4 allocation score on node4: 0
-pcmk__clone_assign: rsc1: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:0 allocation score on node1: 0
-pcmk__primitive_assign: rsc1:0 allocation score on node2: 0
-pcmk__primitive_assign: rsc1:0 allocation score on node3: 0
-pcmk__primitive_assign: rsc1:0 allocation score on node4: 0
-pcmk__primitive_assign: rsc1:0 allocation score on node5: 0
-pcmk__primitive_assign: rsc1:1 allocation score on node1: 0
-pcmk__primitive_assign: rsc1:1 allocation score on node2: -INFINITY
-pcmk__primitive_assign: rsc1:1 allocation score on node3: 0
-pcmk__primitive_assign: rsc1:1 allocation score on node4: 0
-pcmk__primitive_assign: rsc1:1 allocation score on node5: 0
-pcmk__primitive_assign: rsc1:2 allocation score on node1: 0
-pcmk__primitive_assign: rsc1:2 allocation score on node2: -INFINITY
-pcmk__primitive_assign: rsc1:2 allocation score on node3: -INFINITY
-pcmk__primitive_assign: rsc1:2 allocation score on node4: 0
-pcmk__primitive_assign: rsc1:2 allocation score on node5: 0
-pcmk__primitive_assign: rsc1:3 allocation score on node1: 0
-pcmk__primitive_assign: rsc1:3 allocation score on node2: -INFINITY
-pcmk__primitive_assign: rsc1:3 allocation score on node3: -INFINITY
-pcmk__primitive_assign: rsc1:3 allocation score on node4: -INFINITY
-pcmk__primitive_assign: rsc1:3 allocation score on node5: 0
-pcmk__primitive_assign: rsc1:4 allocation score on node1: 0
-pcmk__primitive_assign: rsc1:4 allocation score on node2: -INFINITY
-pcmk__primitive_assign: rsc1:4 allocation score on node3: -INFINITY
-pcmk__primitive_assign: rsc1:4 allocation score on node4: -INFINITY
-pcmk__primitive_assign: rsc1:4 allocation score on node5: -INFINITY
diff --git a/cts/scheduler/summary/timeout-by-node.summary b/cts/scheduler/summary/timeout-by-node.summary
deleted file mode 100644
index 78f4fcdc8b..0000000000
--- a/cts/scheduler/summary/timeout-by-node.summary
+++ /dev/null
@@ -1,43 +0,0 @@
-Current cluster status:
- * Node List:
- * Online: [ node1 node2 node3 node4 node5 ]
-
- * Full List of Resources:
- * Fencing (stonith:fence_xvm): Started node1
- * Clone Set: rsc1-clone [rsc1]:
- * Stopped: [ node1 node2 node3 node4 node5 ]
-
-Transition Summary:
- * Start rsc1:0 ( node2 )
- * Start rsc1:1 ( node3 )
- * Start rsc1:2 ( node4 )
- * Start rsc1:3 ( node5 )
- * Start rsc1:4 ( node1 )
-
-Executing Cluster Transition:
- * Resource action: rsc1:0 monitor on node2
- * Resource action: rsc1:1 monitor on node3
- * Resource action: rsc1:2 monitor on node4
- * Resource action: rsc1:3 monitor on node5
- * Resource action: rsc1:4 monitor on node1
- * Pseudo action: rsc1-clone_start_0
- * Resource action: rsc1:0 start on node2
- * Resource action: rsc1:1 start on node3
- * Resource action: rsc1:2 start on node4
- * Resource action: rsc1:3 start on node5
- * Resource action: rsc1:4 start on node1
- * Pseudo action: rsc1-clone_running_0
- * Resource action: rsc1:0 monitor=10000 on node2
- * Resource action: rsc1:1 monitor=10000 on node3
- * Resource action: rsc1:2 monitor=10000 on node4
- * Resource action: rsc1:3 monitor=10000 on node5
- * Resource action: rsc1:4 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
- * Clone Set: rsc1-clone [rsc1]:
- * Started: [ node1 node2 node3 node4 node5 ]
diff --git a/cts/scheduler/xml/timeout-by-node.xml b/cts/scheduler/xml/timeout-by-node.xml
deleted file mode 100644
index 221885b000..0000000000
--- a/cts/scheduler/xml/timeout-by-node.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/daemons/fenced/fenced_scheduler.c b/daemons/fenced/fenced_scheduler.c
index 52961bea03..358b6264e4 100644
--- a/daemons/fenced/fenced_scheduler.c
+++ b/daemons/fenced/fenced_scheduler.c
@@ -1,258 +1,254 @@
/*
* Copyright 2009-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
static pcmk_scheduler_t *scheduler = NULL;
/*!
* \internal
* \brief Initialize scheduler data for fencer purposes
*
* \return Standard Pacemaker return code
*/
int
fenced_scheduler_init(void)
{
pcmk__output_t *logger = NULL;
int rc = pcmk__log_output_new(&logger);
if (rc != pcmk_rc_ok) {
return rc;
}
scheduler = pe_new_working_set();
if (scheduler == NULL) {
pcmk__output_free(logger);
return ENOMEM;
}
pe__register_messages(logger);
pcmk__register_lib_messages(logger);
pcmk__output_set_log_level(logger, LOG_TRACE);
scheduler->priv->out = logger;
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Set the local node name for scheduling purposes
*
* \param[in] node_name Name to set as local node name
*/
void
fenced_set_local_node(const char *node_name)
{
pcmk__assert(scheduler != NULL);
scheduler->priv->local_node_name = pcmk__str_copy(node_name);
}
/*!
* \internal
* \brief Get the local node name
*
* \return Local node name
*/
const char *
fenced_get_local_node(void)
{
if (scheduler == NULL) {
return NULL;
}
return scheduler->priv->local_node_name;
}
/*!
* \internal
* \brief Free all scheduler-related resources
*/
void
fenced_scheduler_cleanup(void)
{
if (scheduler != NULL) {
pcmk__output_t *logger = scheduler->priv->out;
if (logger != NULL) {
logger->finish(logger, CRM_EX_OK, true, NULL);
pcmk__output_free(logger);
scheduler->priv->out = NULL;
}
pe_free_working_set(scheduler);
scheduler = NULL;
}
}
/*!
* \internal
* \brief Check whether the local node is in a resource's allowed node list
*
* \param[in] rsc Resource to check
*
* \return Pointer to node if found, otherwise NULL
*/
static pcmk_node_t *
local_node_allowed_for(const pcmk_resource_t *rsc)
{
if ((rsc != NULL) && (scheduler->priv->local_node_name != NULL)) {
GHashTableIter iter;
pcmk_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (pcmk__str_eq(node->priv->name, scheduler->priv->local_node_name,
pcmk__str_casei)) {
return node;
}
}
}
return NULL;
}
/*!
* \internal
* \brief If a given resource or any of its children are fencing devices,
* register the devices
*
* \param[in,out] data Resource to check
* \param[in,out] user_data Ignored
*/
static void
register_if_fencing_device(gpointer data, gpointer user_data)
{
pcmk_resource_t *rsc = data;
const char *rsc_id = pcmk__s(rsc->priv->history_id, rsc->id);
xmlNode *xml = NULL;
GHashTableIter hash_iter;
pcmk_node_t *node = NULL;
const char *name = NULL;
const char *value = NULL;
const char *agent = NULL;
const char *rsc_provides = NULL;
stonith_key_value_t *params = NULL;
// If this is a collective resource, check children instead
if (rsc->priv->children != NULL) {
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
register_if_fencing_device(iter->data, NULL);
if (pcmk__is_clone(rsc)) {
return; // Only one instance needs to be checked for clones
}
}
return;
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
return; // Not a fencing device
}
if (pe__resource_is_disabled(rsc)) {
crm_info("Ignoring fencing device %s because it is disabled", rsc->id);
return;
}
if ((stonith_watchdog_timeout_ms <= 0) &&
pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
crm_info("Ignoring fencing device %s "
"because watchdog fencing is disabled", rsc->id);
return;
}
// Check whether local node is allowed to run resource
node = local_node_allowed_for(rsc);
if (node == NULL) {
crm_info("Ignoring fencing device %s "
"because local node is not allowed to run it", rsc->id);
return;
}
if (node->assign->score < 0) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for it",
rsc->id, pcmk_readable_score(node->assign->score));
return;
}
// If device is in a group, check whether local node is allowed for group
if (pcmk__is_group(rsc->priv->parent)) {
pcmk_node_t *group_node = local_node_allowed_for(rsc->priv->parent);
if ((group_node != NULL) && (group_node->assign->score < 0)) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for its group",
rsc->id, pcmk_readable_score(group_node->assign->score));
return;
}
}
crm_debug("Reloading configuration of fencing device %s", rsc->id);
agent = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE);
- /* @COMPAT Support for node attribute expressions in rules for resource
- * meta-attributes is deprecated. When we can break behavioral backward
- * compatibility, replace node with NULL here.
- */
- get_meta_attributes(rsc->priv->meta, rsc, node, scheduler);
+ get_meta_attributes(rsc->priv->meta, rsc, NULL, scheduler);
rsc_provides = g_hash_table_lookup(rsc->priv->meta,
PCMK_STONITH_PROVIDES);
g_hash_table_iter_init(&hash_iter, pe_rsc_params(rsc, node, scheduler));
while (g_hash_table_iter_next(&hash_iter, (gpointer *) &name,
(gpointer *) &value)) {
if ((name == NULL) || (value == NULL)) {
continue;
}
params = stonith_key_value_add(params, name, value);
}
xml = create_device_registration_xml(rsc_id, st_namespace_any, agent,
params, rsc_provides);
stonith_key_value_freeall(params, 1, 1);
pcmk__assert(stonith_device_register(xml, TRUE) == pcmk_ok);
pcmk__xml_free(xml);
}
/*!
* \internal
* \brief Run the scheduler for fencer purposes
*
* \param[in] cib Cluster's current CIB
*/
void
fenced_scheduler_run(xmlNode *cib)
{
CRM_CHECK((cib != NULL) && (scheduler != NULL), return);
if (scheduler->priv->now != NULL) {
crm_time_free(scheduler->priv->now);
scheduler->priv->now = NULL;
}
pcmk__schedule_actions(cib, pcmk__sched_location_only
|pcmk__sched_no_counts, scheduler);
g_list_foreach(scheduler->priv->resources, register_if_fencing_device,
NULL);
scheduler->input = NULL; // Wasn't a copy, so don't let API free it
pe_reset_working_set(scheduler);
}
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index 95228fd41e..08c871c3ea 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1289 +1,1290 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include "pe_status_private.h"
void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length);
static pcmk_node_t *active_node(const pcmk_resource_t *rsc,
unsigned int *count_all,
unsigned int *count_clean);
static pcmk__rsc_methods_t resource_class_functions[] = {
{
native_unpack,
native_find_rsc,
native_parameter,
native_active,
native_resource_state,
native_location,
native_free,
pe__count_common,
pe__native_is_filtered,
active_node,
pe__primitive_max_per_node,
},
{
group_unpack,
native_find_rsc,
native_parameter,
group_active,
group_resource_state,
native_location,
group_free,
pe__count_common,
pe__group_is_filtered,
active_node,
pe__group_max_per_node,
},
{
clone_unpack,
native_find_rsc,
native_parameter,
clone_active,
clone_resource_state,
native_location,
clone_free,
pe__count_common,
pe__clone_is_filtered,
active_node,
pe__clone_max_per_node,
},
{
pe__unpack_bundle,
native_find_rsc,
native_parameter,
pe__bundle_active,
pe__bundle_resource_state,
native_location,
pe__free_bundle,
pe__count_bundle,
pe__bundle_is_filtered,
pe__bundle_active_node,
pe__bundle_max_per_node,
}
};
static enum pcmk__rsc_variant
get_resource_type(const char *name)
{
if (pcmk__str_eq(name, PCMK_XE_PRIMITIVE, pcmk__str_casei)) {
return pcmk__rsc_variant_primitive;
} else if (pcmk__str_eq(name, PCMK_XE_GROUP, pcmk__str_casei)) {
return pcmk__rsc_variant_group;
} else if (pcmk__str_eq(name, PCMK_XE_CLONE, pcmk__str_casei)) {
return pcmk__rsc_variant_clone;
} else if (pcmk__str_eq(name, PCMK_XE_BUNDLE, pcmk__str_casei)) {
return pcmk__rsc_variant_bundle;
}
return pcmk__rsc_variant_unknown;
}
/*!
* \internal
* \brief Insert a meta-attribute if not already present
*
* \param[in] key Meta-attribute name
* \param[in] value Meta-attribute value to add if not already present
* \param[in,out] table Meta-attribute hash table to insert into
*
* \note This is like pcmk__insert_meta() except it won't overwrite existing
* values.
*/
static void
dup_attr(gpointer key, gpointer value, gpointer user_data)
{
GHashTable *table = user_data;
CRM_CHECK((key != NULL) && (table != NULL), return);
if (pcmk__str_eq((const char *) value, "#default", pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting meta-attributes (such as %s) to "
"the explicit value '#default' is deprecated and "
"will be removed in a future release",
(const char *) key);
} else if ((value != NULL) && (g_hash_table_lookup(table, key) == NULL)) {
pcmk__insert_dup(table, (const char *) key, (const char *) value);
}
}
static void
expand_parents_fixed_nvpairs(pcmk_resource_t *rsc,
pe_rule_eval_data_t *rule_data,
GHashTable *meta_hash, pcmk_scheduler_t *scheduler)
{
GHashTable *parent_orig_meta = pcmk__strkey_table(free, free);
pcmk_resource_t *p = rsc->priv->parent;
if (p == NULL) {
return ;
}
/* Search all parent resources, get the fixed value of
* PCMK_XE_META_ATTRIBUTES set only in the original xml, and stack it in the
* hash table. The fixed value of the lower parent resource takes precedence
* and is not overwritten.
*/
while(p != NULL) {
/* A hash table for comparison is generated, including the id-ref. */
pe__unpack_dataset_nvpairs(p->priv->xml, PCMK_XE_META_ATTRIBUTES,
rule_data, parent_orig_meta, NULL,
scheduler);
p = p->priv->parent;
}
if (parent_orig_meta != NULL) {
// This will not overwrite any values already existing for child
g_hash_table_foreach(parent_orig_meta, dup_attr, meta_hash);
}
if (parent_orig_meta != NULL) {
g_hash_table_destroy(parent_orig_meta);
}
return ;
}
+
+/*
+ * \brief Get fully evaluated resource meta-attributes
+ *
+ * \param[in,out] meta_hash Where to store evaluated meta-attributes
+ * \param[in] rsc Resource to get meta-attributes for
+ * \param[in] node Ignored
+ * \param[in,out] scheduler Scheduler data
+ */
void
get_meta_attributes(GHashTable * meta_hash, pcmk_resource_t * rsc,
pcmk_node_t *node, pcmk_scheduler_t *scheduler)
{
pe_rsc_eval_data_t rsc_rule_data = {
.standard = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS),
.provider = crm_element_value(rsc->priv->xml, PCMK_XA_PROVIDER),
.agent = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE)
};
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.now = scheduler->priv->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = NULL
};
- if (node) {
- /* @COMPAT Support for node attribute expressions in rules for
- * meta-attributes is deprecated. When we can break behavioral backward
- * compatibility, drop this block.
- */
- rule_data.node_hash = node->priv->attrs;
- }
-
for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->priv->xml);
a != NULL; a = a->next) {
if (a->children != NULL) {
dup_attr((gpointer) a->name, (gpointer) a->children->content,
meta_hash);
}
}
pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_META_ATTRIBUTES,
&rule_data, meta_hash, NULL, scheduler);
/* Set the PCMK_XE_META_ATTRIBUTES explicitly set in the parent resource to
* the hash table of the child resource. If it is already explicitly set as
* a child, it will not be overwritten.
*/
if (rsc->priv->parent != NULL) {
expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, scheduler);
}
/* check the defaults */
pe__unpack_dataset_nvpairs(scheduler->priv->rsc_defaults,
PCMK_XE_META_ATTRIBUTES, &rule_data, meta_hash,
NULL, scheduler);
/* If there is PCMK_XE_META_ATTRIBUTES that the parent resource has not
* explicitly set, set a value that is not set from PCMK_XE_RSC_DEFAULTS
* either. The values already set up to this point will not be overwritten.
*/
if (rsc->priv->parent != NULL) {
g_hash_table_foreach(rsc->priv->parent->priv->meta, dup_attr,
meta_hash);
}
}
/*!
* \brief Get final values of a resource's instance attributes
*
* \param[in,out] instance_attrs Where to store the instance attributes
* \param[in] rsc Resource to get instance attributes for
* \param[in] node If not NULL, evaluate rules for this node
* \param[in,out] scheduler Scheduler data
*/
void
get_rsc_attributes(GHashTable *instance_attrs, const pcmk_resource_t *rsc,
const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
{
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.now = NULL,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
CRM_CHECK((instance_attrs != NULL) && (rsc != NULL) && (scheduler != NULL),
return);
rule_data.now = scheduler->priv->now;
if (node != NULL) {
rule_data.node_hash = node->priv->attrs;
}
// Evaluate resource's own values, then its ancestors' values
pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_INSTANCE_ATTRIBUTES,
&rule_data, instance_attrs, NULL, scheduler);
if (rsc->priv->parent != NULL) {
get_rsc_attributes(instance_attrs, rsc->priv->parent, node, scheduler);
}
}
static char *
template_op_key(xmlNode * op)
{
const char *name = crm_element_value(op, PCMK_XA_NAME);
const char *role = crm_element_value(op, PCMK_XA_ROLE);
char *key = NULL;
if ((role == NULL)
|| pcmk__strcase_any_of(role, PCMK_ROLE_STARTED, PCMK_ROLE_UNPROMOTED,
PCMK__ROLE_UNPROMOTED_LEGACY, NULL)) {
role = PCMK__ROLE_UNKNOWN;
}
key = crm_strdup_printf("%s-%s", name, role);
return key;
}
static gboolean
unpack_template(xmlNode *xml_obj, xmlNode **expanded_xml,
pcmk_scheduler_t *scheduler)
{
xmlNode *cib_resources = NULL;
xmlNode *template = NULL;
xmlNode *new_xml = NULL;
xmlNode *child_xml = NULL;
xmlNode *rsc_ops = NULL;
xmlNode *template_ops = NULL;
const char *template_ref = NULL;
const char *id = NULL;
if (xml_obj == NULL) {
pcmk__config_err("No resource object for template unpacking");
return FALSE;
}
template_ref = crm_element_value(xml_obj, PCMK_XA_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("'%s' object must have a id", xml_obj->name);
return FALSE;
}
if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
pcmk__config_err("The resource object '%s' should not reference itself",
id);
return FALSE;
}
cib_resources = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input,
LOG_TRACE);
if (cib_resources == NULL) {
pcmk__config_err("No resources configured");
return FALSE;
}
template = pcmk__xe_first_child(cib_resources, PCMK_XE_TEMPLATE,
PCMK_XA_ID, template_ref);
if (template == NULL) {
pcmk__config_err("No template named '%s'", template_ref);
return FALSE;
}
new_xml = pcmk__xml_copy(NULL, template);
xmlNodeSetName(new_xml, xml_obj->name);
crm_xml_add(new_xml, PCMK_XA_ID, id);
crm_xml_add(new_xml, PCMK__META_CLONE,
crm_element_value(xml_obj, PCMK__META_CLONE));
template_ops = pcmk__xe_first_child(new_xml, PCMK_XE_OPERATIONS, NULL,
NULL);
for (child_xml = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
child_xml != NULL; child_xml = pcmk__xe_next(child_xml)) {
xmlNode *new_child = pcmk__xml_copy(new_xml, child_xml);
if (pcmk__xe_is(new_child, PCMK_XE_OPERATIONS)) {
rsc_ops = new_child;
}
}
if (template_ops && rsc_ops) {
xmlNode *op = NULL;
GHashTable *rsc_ops_hash = pcmk__strkey_table(free, NULL);
for (op = pcmk__xe_first_child(rsc_ops, NULL, NULL, NULL); op != NULL;
op = pcmk__xe_next(op)) {
char *key = template_op_key(op);
g_hash_table_insert(rsc_ops_hash, key, op);
}
for (op = pcmk__xe_first_child(template_ops, NULL, NULL, NULL);
op != NULL; op = pcmk__xe_next(op)) {
char *key = template_op_key(op);
if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
pcmk__xml_copy(rsc_ops, op);
}
free(key);
}
if (rsc_ops_hash) {
g_hash_table_destroy(rsc_ops_hash);
}
pcmk__xml_free(template_ops);
}
/*pcmk__xml_free(*expanded_xml); */
*expanded_xml = new_xml;
#if 0 /* Disable multi-level templates for now */
if (!unpack_template(new_xml, expanded_xml, scheduler)) {
pcmk__xml_free(*expanded_xml);
*expanded_xml = NULL;
return FALSE;
}
#endif
return TRUE;
}
static gboolean
add_template_rsc(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
{
const char *template_ref = NULL;
const char *id = NULL;
if (xml_obj == NULL) {
pcmk__config_err("No resource object for processing resource list "
"of template");
return FALSE;
}
template_ref = crm_element_value(xml_obj, PCMK_XA_TEMPLATE);
if (template_ref == NULL) {
return TRUE;
}
id = pcmk__xe_id(xml_obj);
if (id == NULL) {
pcmk__config_err("'%s' object must have a id", xml_obj->name);
return FALSE;
}
if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
pcmk__config_err("The resource object '%s' should not reference itself",
id);
return FALSE;
}
pcmk__add_idref(scheduler->priv->templates, template_ref, id);
return TRUE;
}
/*!
* \internal
* \brief Check whether a clone or instance being unpacked is globally unique
*
* \param[in] rsc Clone or clone instance to check
*
* \return \c true if \p rsc is globally unique according to its
* meta-attributes, otherwise \c false
*/
static bool
detect_unique(const pcmk_resource_t *rsc)
{
const char *value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_GLOBALLY_UNIQUE);
if (value == NULL) { // Default to true if clone-node-max > 1
value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_CLONE_NODE_MAX);
if (value != NULL) {
int node_max = 1;
if ((pcmk__scan_min_int(value, &node_max, 0) == pcmk_rc_ok)
&& (node_max > 1)) {
return true;
}
}
return false;
}
return crm_is_true(value);
}
static void
free_params_table(gpointer data)
{
g_hash_table_destroy((GHashTable *) data);
}
/*!
* \brief Get a table of resource parameters
*
* \param[in,out] rsc Resource to query
* \param[in] node Node for evaluating rules (NULL for defaults)
* \param[in,out] scheduler Scheduler data
*
* \return Hash table containing resource parameter names and values
* (or NULL if \p rsc or \p scheduler is NULL)
* \note The returned table will be destroyed when the resource is freed, so
* callers should not destroy it.
*/
GHashTable *
pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node,
pcmk_scheduler_t *scheduler)
{
GHashTable *params_on_node = NULL;
/* A NULL node is used to request the resource's default parameters
* (not evaluated for node), but we always want something non-NULL
* as a hash table key.
*/
const char *node_name = "";
// Sanity check
if ((rsc == NULL) || (scheduler == NULL)) {
return NULL;
}
if ((node != NULL) && (node->priv->name != NULL)) {
node_name = node->priv->name;
}
// Find the parameter table for given node
if (rsc->priv->parameter_cache == NULL) {
rsc->priv->parameter_cache = pcmk__strikey_table(free,
free_params_table);
} else {
params_on_node = g_hash_table_lookup(rsc->priv->parameter_cache,
node_name);
}
// If none exists yet, create one with parameters evaluated for node
if (params_on_node == NULL) {
params_on_node = pcmk__strkey_table(free, free);
get_rsc_attributes(params_on_node, rsc, node, scheduler);
g_hash_table_insert(rsc->priv->parameter_cache, strdup(node_name),
params_on_node);
}
return params_on_node;
}
/*!
* \internal
* \brief Unpack a resource's \c PCMK_META_REQUIRES meta-attribute
*
* \param[in,out] rsc Resource being unpacked
* \param[in] value Value of \c PCMK_META_REQUIRES meta-attribute
* \param[in] is_default Whether \p value was selected by default
*/
static void
unpack_requires(pcmk_resource_t *rsc, const char *value, bool is_default)
{
const pcmk_scheduler_t *scheduler = rsc->priv->scheduler;
if (pcmk__str_eq(value, PCMK_VALUE_NOTHING, pcmk__str_casei)) {
} else if (pcmk__str_eq(value, PCMK_VALUE_QUORUM, pcmk__str_casei)) {
pcmk__set_rsc_flags(rsc, pcmk__rsc_needs_quorum);
} else if (pcmk__str_eq(value, PCMK_VALUE_FENCING, pcmk__str_casei)) {
pcmk__set_rsc_flags(rsc, pcmk__rsc_needs_fencing);
if (!pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
pcmk__config_warn("%s requires fencing but fencing is disabled",
rsc->id);
}
} else if (pcmk__str_eq(value, PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
if (pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
"to \"" PCMK_VALUE_QUORUM "\" because fencing "
"devices cannot require unfencing", rsc->id);
unpack_requires(rsc, PCMK_VALUE_QUORUM, true);
return;
} else if (!pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
"to \"" PCMK_VALUE_QUORUM "\" because fencing is "
"disabled", rsc->id);
unpack_requires(rsc, PCMK_VALUE_QUORUM, true);
return;
} else {
pcmk__set_rsc_flags(rsc, pcmk__rsc_needs_fencing
|pcmk__rsc_needs_unfencing);
}
} else {
const char *orig_value = value;
if (pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
value = PCMK_VALUE_QUORUM;
} else if (pcmk__is_primitive(rsc)
&& xml_contains_remote_node(rsc->priv->xml)) {
value = PCMK_VALUE_QUORUM;
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_enable_unfencing)) {
value = PCMK_VALUE_UNFENCING;
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
value = PCMK_VALUE_FENCING;
} else if (scheduler->no_quorum_policy == pcmk_no_quorum_ignore) {
value = PCMK_VALUE_NOTHING;
} else {
value = PCMK_VALUE_QUORUM;
}
if (orig_value != NULL) {
pcmk__config_err("Resetting '" PCMK_META_REQUIRES "' for %s "
"to '%s' because '%s' is not valid",
rsc->id, value, orig_value);
}
unpack_requires(rsc, value, true);
return;
}
pcmk__rsc_trace(rsc, "\tRequired to start: %s%s", value,
(is_default? " (default)" : ""));
}
/*!
* \internal
* \brief Parse resource priority from meta-attribute
*
* \param[in,out] rsc Resource being unpacked
*/
static void
unpack_priority(pcmk_resource_t *rsc)
{
const char *value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_PRIORITY);
int rc = pcmk_parse_score(value, &(rsc->priv->priority), 0);
if (rc != pcmk_rc_ok) {
pcmk__config_warn("Using default (0) for resource %s "
PCMK_META_PRIORITY
" because '%s' is not a valid value: %s",
rsc->id, value, pcmk_rc_str(rc));
}
}
/*!
* \internal
* \brief Parse resource stickiness from meta-attribute
*
* \param[in,out] rsc Resource being unpacked
*/
static void
unpack_stickiness(pcmk_resource_t *rsc)
{
const char *value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_RESOURCE_STICKINESS);
if (pcmk__str_eq(value, PCMK_VALUE_DEFAULT, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting "
PCMK_META_RESOURCE_STICKINESS
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
} else {
int rc = pcmk_parse_score(value, &(rsc->priv->stickiness), 0);
if (rc != pcmk_rc_ok) {
pcmk__config_warn("Using default (0) for resource %s "
PCMK_META_RESOURCE_STICKINESS
" because '%s' is not a valid value: %s",
rsc->id, value, pcmk_rc_str(rc));
}
}
}
/*!
* \internal
* \brief Parse resource migration threshold from meta-attribute
*
* \param[in,out] rsc Resource being unpacked
*/
static void
unpack_migration_threshold(pcmk_resource_t *rsc)
{
const char *value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_MIGRATION_THRESHOLD);
if (pcmk__str_eq(value, PCMK_VALUE_DEFAULT, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting "
PCMK_META_MIGRATION_THRESHOLD
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
rsc->priv->ban_after_failures = PCMK_SCORE_INFINITY;
} else {
int rc = pcmk_parse_score(value, &(rsc->priv->ban_after_failures),
PCMK_SCORE_INFINITY);
if ((rc != pcmk_rc_ok) || (rsc->priv->ban_after_failures < 0)) {
pcmk__config_warn("Using default (" PCMK_VALUE_INFINITY
") for resource %s meta-attribute "
PCMK_META_MIGRATION_THRESHOLD
" because '%s' is not a valid value: %s",
rsc->id, value, pcmk_rc_str(rc));
rsc->priv->ban_after_failures = PCMK_SCORE_INFINITY;
}
}
}
/*!
* \internal
* \brief Unpack configuration XML for a given resource
*
* Unpack the XML object containing a resource's configuration into a new
* \c pcmk_resource_t object.
*
* \param[in] xml_obj XML node containing the resource's configuration
* \param[out] rsc Where to store the unpacked resource information
* \param[in] parent Resource's parent, if any
* \param[in,out] scheduler Scheduler data
*
* \return Standard Pacemaker return code
* \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and
* the caller is responsible for freeing it using its variant-specific
* free() method. Otherwise, \p *rsc is guaranteed to be NULL.
*/
int
pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc,
pcmk_resource_t *parent, pcmk_scheduler_t *scheduler)
{
xmlNode *expanded_xml = NULL;
xmlNode *ops = NULL;
const char *value = NULL;
const char *id = NULL;
bool guest_node = false;
bool remote_node = false;
pcmk__resource_private_t *rsc_private = NULL;
pe_rule_eval_data_t rule_data = {
.node_hash = NULL,
.now = NULL,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
CRM_CHECK(rsc != NULL, return EINVAL);
CRM_CHECK((xml_obj != NULL) && (scheduler != NULL),
*rsc = NULL;
return EINVAL);
rule_data.now = scheduler->priv->now;
crm_log_xml_trace(xml_obj, "[raw XML]");
id = crm_element_value(xml_obj, PCMK_XA_ID);
if (id == NULL) {
pcmk__config_err("Ignoring <%s> configuration without " PCMK_XA_ID,
xml_obj->name);
return pcmk_rc_unpack_error;
}
if (unpack_template(xml_obj, &expanded_xml, scheduler) == FALSE) {
return pcmk_rc_unpack_error;
}
*rsc = calloc(1, sizeof(pcmk_resource_t));
if (*rsc == NULL) {
pcmk__sched_err(scheduler,
"Unable to allocate memory for resource '%s'", id);
return ENOMEM;
}
(*rsc)->priv = calloc(1, sizeof(pcmk__resource_private_t));
if ((*rsc)->priv == NULL) {
pcmk__sched_err(scheduler,
"Unable to allocate memory for resource '%s'", id);
free(*rsc);
return ENOMEM;
}
rsc_private = (*rsc)->priv;
rsc_private->scheduler = scheduler;
if (expanded_xml) {
crm_log_xml_trace(expanded_xml, "[expanded XML]");
rsc_private->xml = expanded_xml;
rsc_private->orig_xml = xml_obj;
} else {
rsc_private->xml = xml_obj;
rsc_private->orig_xml = NULL;
}
/* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
rsc_private->parent = parent;
ops = pcmk__xe_first_child(rsc_private->xml, PCMK_XE_OPERATIONS, NULL,
NULL);
rsc_private->ops_xml = pcmk__xe_resolve_idref(ops, scheduler->input);
rsc_private->variant = get_resource_type((const char *)
rsc_private->xml->name);
if (rsc_private->variant == pcmk__rsc_variant_unknown) {
pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
id, rsc_private->xml->name);
common_free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
rsc_private->meta = pcmk__strkey_table(free, free);
rsc_private->utilization = pcmk__strkey_table(free, free);
rsc_private->probed_nodes = pcmk__strkey_table(NULL, free);
rsc_private->allowed_nodes = pcmk__strkey_table(NULL, free);
value = crm_element_value(rsc_private->xml, PCMK__META_CLONE);
if (value) {
(*rsc)->id = crm_strdup_printf("%s:%s", id, value);
pcmk__insert_meta(rsc_private, PCMK__META_CLONE, value);
} else {
(*rsc)->id = strdup(id);
}
rsc_private->fns = &resource_class_functions[rsc_private->variant];
get_meta_attributes(rsc_private->meta, *rsc, NULL, scheduler);
(*rsc)->flags = 0;
pcmk__set_rsc_flags(*rsc, pcmk__rsc_unassigned);
if (!pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_managed);
}
rsc_private->orig_role = pcmk_role_stopped;
rsc_private->next_role = pcmk_role_unknown;
unpack_priority(*rsc);
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_CRITICAL);
if ((value == NULL) || crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_critical);
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_NOTIFY);
if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_notify);
}
if (xml_contains_remote_node(rsc_private->xml)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_is_remote_connection);
if (g_hash_table_lookup(rsc_private->meta, PCMK__META_CONTAINER)) {
guest_node = true;
} else {
remote_node = true;
}
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_ALLOW_MIGRATE);
if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_migratable);
} else if ((value == NULL) && remote_node) {
/* By default, we want remote nodes to be able
* to float around the cluster without having to stop all the
* resources within the remote-node before moving. Allowing
* migration support enables this feature. If this ever causes
* problems, migration support can be explicitly turned off with
* PCMK_META_ALLOW_MIGRATE=false.
*/
pcmk__set_rsc_flags(*rsc, pcmk__rsc_migratable);
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_IS_MANAGED);
if (value != NULL) {
if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting " PCMK_META_IS_MANAGED
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
} else if (crm_is_true(value)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_managed);
} else {
pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
}
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_MAINTENANCE);
if (crm_is_true(value)) {
pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
pcmk__set_rsc_flags(*rsc, pcmk__rsc_maintenance);
}
if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
pcmk__set_rsc_flags(*rsc, pcmk__rsc_maintenance);
}
if (pcmk__is_clone(pe__const_top_resource(*rsc, false))) {
if (detect_unique(*rsc)) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_unique);
}
if (crm_is_true(g_hash_table_lookup((*rsc)->priv->meta,
PCMK_META_PROMOTABLE))) {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_promotable);
}
} else {
pcmk__set_rsc_flags(*rsc, pcmk__rsc_unique);
}
// @COMPAT Deprecated meta-attribute
value = g_hash_table_lookup(rsc_private->meta, PCMK__META_RESTART_TYPE);
if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
rsc_private->restart_type = pcmk__restart_restart;
pcmk__rsc_trace(*rsc, "%s dependency restart handling: restart",
(*rsc)->id);
pcmk__warn_once(pcmk__wo_restart_type,
"Support for " PCMK__META_RESTART_TYPE " is deprecated "
"and will be removed in a future release");
} else {
rsc_private->restart_type = pcmk__restart_ignore;
pcmk__rsc_trace(*rsc, "%s dependency restart handling: ignore",
(*rsc)->id);
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_MULTIPLE_ACTIVE);
if (pcmk__str_eq(value, PCMK_VALUE_STOP_ONLY, pcmk__str_casei)) {
rsc_private->multiply_active_policy = pcmk__multiply_active_stop;
pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop only",
(*rsc)->id);
} else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
rsc_private->multiply_active_policy = pcmk__multiply_active_block;
pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block",
(*rsc)->id);
} else if (pcmk__str_eq(value, PCMK_VALUE_STOP_UNEXPECTED,
pcmk__str_casei)) {
rsc_private->multiply_active_policy = pcmk__multiply_active_unexpected;
pcmk__rsc_trace(*rsc,
"%s multiple running resource recovery: "
"stop unexpected instances",
(*rsc)->id);
} else { // PCMK_VALUE_STOP_START
if (!pcmk__str_eq(value, PCMK_VALUE_STOP_START,
pcmk__str_casei|pcmk__str_null_matches)) {
pcmk__config_warn("%s is not a valid value for "
PCMK_META_MULTIPLE_ACTIVE
", using default of "
"\"" PCMK_VALUE_STOP_START "\"",
value);
}
rsc_private->multiply_active_policy = pcmk__multiply_active_restart;
pcmk__rsc_trace(*rsc,
"%s multiple running resource recovery: stop/start",
(*rsc)->id);
}
unpack_stickiness(*rsc);
unpack_migration_threshold(*rsc);
if (pcmk__str_eq(crm_element_value(rsc_private->xml, PCMK_XA_CLASS),
PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_have_fencing);
pcmk__set_rsc_flags(*rsc, pcmk__rsc_fence_device);
}
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_REQUIRES);
unpack_requires(*rsc, value, false);
value = g_hash_table_lookup(rsc_private->meta, PCMK_META_FAILURE_TIMEOUT);
if (value != NULL) {
pcmk_parse_interval_spec(value, &(rsc_private->failure_expiration_ms));
}
if (remote_node) {
GHashTable *params = pe_rsc_params(*rsc, NULL, scheduler);
/* Grabbing the value now means that any rules based on node attributes
* will evaluate to false, so such rules should not be used with
* PCMK_REMOTE_RA_RECONNECT_INTERVAL.
*
* @TODO Evaluate per node before using
*/
value = g_hash_table_lookup(params, PCMK_REMOTE_RA_RECONNECT_INTERVAL);
if (value) {
/* reconnect delay works by setting failure_timeout and preventing the
* connection from starting until the failure is cleared. */
pcmk_parse_interval_spec(value,
&(rsc_private->remote_reconnect_ms));
/* We want to override any default failure_timeout in use when remote
* PCMK_REMOTE_RA_RECONNECT_INTERVAL is in use.
*/
rsc_private->failure_expiration_ms =
rsc_private->remote_reconnect_ms;
}
}
get_target_role(*rsc, &(rsc_private->next_role));
pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id,
(rsc_private->next_role == pcmk_role_unknown)?
"default" : pcmk_role_text(rsc_private->next_role));
if (rsc_private->fns->unpack(*rsc, scheduler) == FALSE) {
rsc_private->fns->free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
if (pcmk_is_set(scheduler->flags, pcmk__sched_symmetric_cluster)) {
// This tag must stay exactly the same because it is tested elsewhere
resource_location(*rsc, NULL, 0, "symmetric_default", scheduler);
} else if (guest_node) {
/* remote resources tied to a container resource must always be allowed
* to opt-in to the cluster. Whether the connection resource is actually
* allowed to be placed on a node is dependent on the container resource */
resource_location(*rsc, NULL, 0, "remote_connection_default",
scheduler);
}
pcmk__rsc_trace(*rsc, "%s action notification: %s", (*rsc)->id,
pcmk_is_set((*rsc)->flags, pcmk__rsc_notify)? "required" : "not required");
pe__unpack_dataset_nvpairs(rsc_private->xml, PCMK_XE_UTILIZATION,
&rule_data, rsc_private->utilization, NULL,
scheduler);
if (expanded_xml) {
if (add_template_rsc(xml_obj, scheduler) == FALSE) {
rsc_private->fns->free(*rsc);
*rsc = NULL;
return pcmk_rc_unpack_error;
}
}
return pcmk_rc_ok;
}
gboolean
is_parent(pcmk_resource_t *child, pcmk_resource_t *rsc)
{
pcmk_resource_t *parent = child;
if (parent == NULL || rsc == NULL) {
return FALSE;
}
while (parent->priv->parent != NULL) {
if (parent->priv->parent == rsc) {
return TRUE;
}
parent = parent->priv->parent;
}
return FALSE;
}
pcmk_resource_t *
uber_parent(pcmk_resource_t *rsc)
{
pcmk_resource_t *parent = rsc;
if (parent == NULL) {
return NULL;
}
while ((parent->priv->parent != NULL)
&& !pcmk__is_bundle(parent->priv->parent)) {
parent = parent->priv->parent;
}
return parent;
}
/*!
* \internal
* \brief Get the topmost parent of a resource as a const pointer
*
* \param[in] rsc Resource to check
* \param[in] include_bundle If true, go all the way to bundle
*
* \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent,
* the bundle if \p rsc is bundled and \p include_bundle is true,
* otherwise the topmost parent of \p rsc up to a clone
*/
const pcmk_resource_t *
pe__const_top_resource(const pcmk_resource_t *rsc, bool include_bundle)
{
const pcmk_resource_t *parent = rsc;
if (parent == NULL) {
return NULL;
}
while (parent->priv->parent != NULL) {
if (!include_bundle && pcmk__is_bundle(parent->priv->parent)) {
break;
}
parent = parent->priv->parent;
}
return parent;
}
void
common_free(pcmk_resource_t * rsc)
{
if (rsc == NULL) {
return;
}
pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
if (rsc->priv->parameter_cache != NULL) {
g_hash_table_destroy(rsc->priv->parameter_cache);
}
if ((rsc->priv->parent == NULL)
&& pcmk_is_set(rsc->flags, pcmk__rsc_removed)) {
pcmk__xml_free(rsc->priv->xml);
rsc->priv->xml = NULL;
pcmk__xml_free(rsc->priv->orig_xml);
rsc->priv->orig_xml = NULL;
} else if (rsc->priv->orig_xml != NULL) {
// rsc->private->xml was expanded from a template
pcmk__xml_free(rsc->priv->xml);
rsc->priv->xml = NULL;
}
free(rsc->id);
free(rsc->priv->variant_opaque);
free(rsc->priv->history_id);
free(rsc->priv->pending_action);
free(rsc->priv->assigned_node);
g_list_free(rsc->priv->actions);
g_list_free(rsc->priv->active_nodes);
g_list_free(rsc->priv->launched);
g_list_free(rsc->priv->dangling_migration_sources);
g_list_free(rsc->priv->with_this_colocations);
g_list_free(rsc->priv->this_with_colocations);
g_list_free(rsc->priv->location_constraints);
g_list_free(rsc->priv->ticket_constraints);
if (rsc->priv->meta != NULL) {
g_hash_table_destroy(rsc->priv->meta);
}
if (rsc->priv->utilization != NULL) {
g_hash_table_destroy(rsc->priv->utilization);
}
if (rsc->priv->probed_nodes != NULL) {
g_hash_table_destroy(rsc->priv->probed_nodes);
}
if (rsc->priv->allowed_nodes != NULL) {
g_hash_table_destroy(rsc->priv->allowed_nodes);
}
free(rsc->priv);
free(rsc);
}
/*!
* \internal
* \brief Count a node and update most preferred to it as appropriate
*
* \param[in] rsc An active resource
* \param[in] node A node that \p rsc is active on
* \param[in,out] active This will be set to \p node if \p node is more
* preferred than the current value
* \param[in,out] count_all If not NULL, this will be incremented
* \param[in,out] count_clean If not NULL, this will be incremented if \p node
* is online and clean
*
* \return true if the count should continue, or false if sufficiently known
*/
bool
pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_node_t **active, unsigned int *count_all,
unsigned int *count_clean)
{
bool keep_looking = false;
bool is_happy = false;
CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL),
return false);
is_happy = node->details->online && !node->details->unclean;
if (count_all != NULL) {
++*count_all;
}
if ((count_clean != NULL) && is_happy) {
++*count_clean;
}
if ((count_all != NULL) || (count_clean != NULL)) {
keep_looking = true; // We're counting, so go through entire list
}
if (rsc->priv->partial_migration_source != NULL) {
if (pcmk__same_node(node, rsc->priv->partial_migration_source)) {
*active = node; // This is the migration source
} else {
keep_looking = true;
}
} else if (!pcmk_is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
if (is_happy && ((*active == NULL) || !(*active)->details->online
|| (*active)->details->unclean)) {
*active = node; // This is the first clean node
} else {
keep_looking = true;
}
}
if (*active == NULL) {
*active = node; // This is the first node checked
}
return keep_looking;
}
// Shared implementation of pcmk__rsc_methods_t:active_node()
static pcmk_node_t *
active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
unsigned int *count_clean)
{
pcmk_node_t *active = NULL;
if (count_all != NULL) {
*count_all = 0;
}
if (count_clean != NULL) {
*count_clean = 0;
}
if (rsc == NULL) {
return NULL;
}
for (GList *iter = rsc->priv->active_nodes;
iter != NULL; iter = iter->next) {
if (!pe__count_active_node(rsc, (pcmk_node_t *) iter->data, &active,
count_all, count_clean)) {
break; // Don't waste time iterating if we don't have to
}
}
return active;
}
/*!
* \brief
* \internal Find and count active nodes according to \c PCMK_META_REQUIRES
*
* \param[in] rsc Resource to check
* \param[out] count If not NULL, will be set to count of active nodes
*
* \return An active node (or NULL if resource is not active anywhere)
*
* \note This is a convenience wrapper for active_node() where the count of all
* active nodes or only clean active nodes is desired according to the
* \c PCMK_META_REQUIRES meta-attribute.
*/
pcmk_node_t *
pe__find_active_requires(const pcmk_resource_t *rsc, unsigned int *count)
{
if (rsc == NULL) {
if (count != NULL) {
*count = 0;
}
return NULL;
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
return rsc->priv->fns->active_node(rsc, count, NULL);
} else {
return rsc->priv->fns->active_node(rsc, NULL, count);
}
}
void
pe__count_common(pcmk_resource_t *rsc)
{
if (rsc->priv->children != NULL) {
for (GList *item = rsc->priv->children;
item != NULL; item = item->next) {
pcmk_resource_t *child = item->data;
child->priv->fns->count(item->data);
}
} else if (!pcmk_is_set(rsc->flags, pcmk__rsc_removed)
|| (rsc->priv->orig_role > pcmk_role_stopped)) {
rsc->priv->scheduler->priv->ninstances++;
if (pe__resource_is_disabled(rsc)) {
rsc->priv->scheduler->priv->disabled_resources++;
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_blocked)) {
rsc->priv->scheduler->priv->blocked_resources++;
}
}
}
/*!
* \internal
* \brief Update a resource's next role
*
* \param[in,out] rsc Resource to be updated
* \param[in] role Resource's new next role
* \param[in] why Human-friendly reason why role is changing (for logs)
*/
void
pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role, const char *why)
{
pcmk__assert((rsc != NULL) && (why != NULL));
if (rsc->priv->next_role != role) {
pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
rsc->id, pcmk_role_text(rsc->priv->next_role),
pcmk_role_text(role), why);
rsc->priv->next_role = role;
}
}
diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c
index 0e1ce48fc3..700bb7e646 100644
--- a/lib/pengine/pe_actions.c
+++ b/lib/pengine/pe_actions.c
@@ -1,1802 +1,1798 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "pe_status_private.h"
static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms);
static void
add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action)
{
if (scheduler->priv->singletons == NULL) {
scheduler->priv->singletons = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(scheduler->priv->singletons, action->uuid, action);
}
static pcmk_action_t *
lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid)
{
/* @TODO This is the only use of the pcmk_scheduler_t:singletons hash table.
* Compare the performance of this approach to keeping the
* pcmk_scheduler_t:actions list sorted by action key and just searching
* that instead.
*/
if (scheduler->priv->singletons == NULL) {
return NULL;
}
return g_hash_table_lookup(scheduler->priv->singletons, action_uuid);
}
/*!
* \internal
* \brief Find an existing action that matches arguments
*
* \param[in] key Action key to match
* \param[in] rsc Resource to match (if any)
* \param[in] node Node to match (if any)
* \param[in] scheduler Scheduler data
*
* \return Existing action that matches arguments (or NULL if none)
*/
static pcmk_action_t *
find_existing_action(const char *key, const pcmk_resource_t *rsc,
const pcmk_node_t *node, const pcmk_scheduler_t *scheduler)
{
/* When rsc is NULL, it would be quicker to check
* scheduler->priv->singletons, but checking all scheduler->priv->actions
* takes the node into account.
*/
GList *actions = (rsc == NULL)? scheduler->priv->actions : rsc->priv->actions;
GList *matches = find_actions(actions, key, node);
pcmk_action_t *action = NULL;
if (matches == NULL) {
return NULL;
}
CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches));
action = matches->data;
g_list_free(matches);
return action;
}
/*!
* \internal
* \brief Find the XML configuration corresponding to a specific action key
*
* \param[in] rsc Resource to find action configuration for
* \param[in] key "RSC_ACTION_INTERVAL" of action to find
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
static xmlNode *
find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
for (xmlNode *operation = pcmk__xe_first_child(rsc->priv->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
const char *config_name = NULL;
const char *interval_spec = NULL;
guint tmp_ms = 0U;
// @TODO This does not consider meta-attributes, rules, defaults, etc.
if (!include_disabled
&& (pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &tmp_ms);
if (tmp_ms != interval_ms) {
continue;
}
config_name = crm_element_value(operation, PCMK_XA_NAME);
if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) {
return operation;
}
}
return NULL;
}
/*!
* \internal
* \brief Find the XML configuration of a resource action
*
* \param[in] rsc Resource to find action configuration for
* \param[in] action_name Action name to search for
* \param[in] interval_ms Action interval (in milliseconds) to search for
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
xmlNode *
pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
xmlNode *action_config = NULL;
// Try requested action first
action_config = find_exact_action_config(rsc, action_name, interval_ms,
include_disabled);
// For migrate_to and migrate_from actions, retry with "migrate"
// @TODO This should be either documented or deprecated
if ((action_config == NULL)
&& pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
action_config = find_exact_action_config(rsc, "migrate", 0,
include_disabled);
}
return action_config;
}
/*!
* \internal
* \brief Create a new action object
*
* \param[in] key Action key
* \param[in] task Action name
* \param[in,out] rsc Resource that action is for (if any)
* \param[in] node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Newly allocated action
* \note This function takes ownership of \p key. It is the caller's
* responsibility to free the return value with pe_free_action().
*/
static pcmk_action_t *
new_action(char *key, const char *task, pcmk_resource_t *rsc,
const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = pcmk__assert_alloc(1, sizeof(pcmk_action_t));
action->rsc = rsc;
action->task = pcmk__str_copy(task);
action->uuid = key;
action->scheduler = scheduler;
if (node) {
action->node = pe__copy_node(node);
}
if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) {
// Resource history deletion for a node can be done on the DC
pcmk__set_action_flags(action, pcmk__action_on_dc);
}
pcmk__set_action_flags(action, pcmk__action_runnable);
if (optional) {
pcmk__set_action_flags(action, pcmk__action_optional);
} else {
pcmk__clear_action_flags(action, pcmk__action_optional);
}
if (rsc == NULL) {
action->meta = pcmk__strkey_table(free, free);
} else {
guint interval_ms = 0;
parse_op_key(key, NULL, NULL, &interval_ms);
action->op_entry = pcmk__find_action_config(rsc, task, interval_ms,
true);
/* If the given key is for one of the many notification pseudo-actions
* (pre_notify_promote, etc.), the actual action name is "notify"
*/
if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) {
action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY,
0, true);
}
unpack_operation(action, action->op_entry, interval_ms);
}
pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s",
(optional? "optional" : "required"),
scheduler->priv->next_action_id, key, task,
((rsc == NULL)? "no resource" : rsc->id),
pcmk__node_name(node));
action->id = scheduler->priv->next_action_id++;
scheduler->priv->actions = g_list_prepend(scheduler->priv->actions, action);
if (rsc == NULL) {
add_singleton(scheduler, action);
} else {
rsc->priv->actions = g_list_prepend(rsc->priv->actions, action);
}
return action;
}
/*!
* \internal
* \brief Unpack a resource's action-specific instance parameters
*
* \param[in] action_xml XML of action's configuration in CIB (if any)
* \param[in,out] node_attrs Table of node attributes (for rule evaluation)
* \param[in,out] scheduler Cluster working set (for rule evaluation)
*
* \return Newly allocated hash table of action-specific instance parameters
*/
GHashTable *
pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
GHashTable *node_attrs,
pcmk_scheduler_t *scheduler)
{
GHashTable *params = pcmk__strkey_table(free, free);
pe_rule_eval_data_t rule_data = {
.node_hash = node_attrs,
.now = scheduler->priv->now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe__unpack_dataset_nvpairs(action_xml, PCMK_XE_INSTANCE_ATTRIBUTES,
&rule_data, params, NULL, scheduler);
return params;
}
/*!
* \internal
* \brief Update an action's optional flag
*
* \param[in,out] action Action to update
* \param[in] optional Requested optional status
*/
static void
update_action_optional(pcmk_action_t *action, gboolean optional)
{
// Force a non-recurring action to be optional if its resource is unmanaged
if ((action->rsc != NULL) && (action->node != NULL)
&& !pcmk_is_set(action->flags, pcmk__action_pseudo)
&& !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)
&& (g_hash_table_lookup(action->meta, PCMK_META_INTERVAL) == NULL)) {
pcmk__rsc_debug(action->rsc,
"%s on %s is optional (%s is unmanaged)",
action->uuid, pcmk__node_name(action->node),
action->rsc->id);
pcmk__set_action_flags(action, pcmk__action_optional);
// We shouldn't clear runnable here because ... something
// Otherwise require the action if requested
} else if (!optional) {
pcmk__clear_action_flags(action, pcmk__action_optional);
}
}
static enum pe_quorum_policy
effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
{
enum pe_quorum_policy policy = scheduler->no_quorum_policy;
if (pcmk_is_set(scheduler->flags, pcmk__sched_quorate)) {
policy = pcmk_no_quorum_ignore;
} else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) {
switch (rsc->priv->orig_role) {
case pcmk_role_promoted:
case pcmk_role_unpromoted:
if (rsc->priv->next_role > pcmk_role_unpromoted) {
pe__set_next_role(rsc, pcmk_role_unpromoted,
PCMK_OPT_NO_QUORUM_POLICY "=demote");
}
policy = pcmk_no_quorum_ignore;
break;
default:
policy = pcmk_no_quorum_stop;
break;
}
}
return policy;
}
/*!
* \internal
* \brief Update a resource action's runnable flag
*
* \param[in,out] action Action to update
* \param[in,out] scheduler Scheduler data
*
* \note This may also schedule fencing if a stop is unrunnable.
*/
static void
update_resource_action_runnable(pcmk_action_t *action,
pcmk_scheduler_t *scheduler)
{
pcmk_resource_t *rsc = action->rsc;
if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
return;
}
if (action->node == NULL) {
pcmk__rsc_trace(rsc, "%s is unrunnable (unallocated)", action->uuid);
pcmk__clear_action_flags(action, pcmk__action_runnable);
} else if (!pcmk_is_set(action->flags, pcmk__action_on_dc)
&& !(action->node->details->online)
&& (!pcmk__is_guest_or_bundle_node(action->node)
|| pcmk_is_set(action->node->priv->flags,
pcmk__node_remote_reset))) {
pcmk__clear_action_flags(action, pcmk__action_runnable);
do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)",
action->uuid, pcmk__node_name(action->node));
if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)
&& pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)
&& !(action->node->details->unclean)) {
pe_fence_node(scheduler, action->node, "stop is unrunnable", false);
}
} else if (!pcmk_is_set(action->flags, pcmk__action_on_dc)
&& action->node->details->pending) {
pcmk__clear_action_flags(action, pcmk__action_runnable);
do_crm_log(LOG_WARNING,
"Action %s on %s is unrunnable (node is pending)",
action->uuid, pcmk__node_name(action->node));
} else if (action->needs == pcmk__requires_nothing) {
pe_action_set_reason(action, NULL, TRUE);
if (pcmk__is_guest_or_bundle_node(action->node)
&& !pe_can_fence(scheduler, action->node)) {
/* An action that requires nothing usually does not require any
* fencing in order to be runnable. However, there is an exception:
* such an action cannot be completed if it is on a guest node whose
* host is unclean and cannot be fenced.
*/
pcmk__rsc_debug(rsc,
"%s on %s is unrunnable "
"(node's host cannot be fenced)",
action->uuid, pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk__action_runnable);
} else {
pcmk__rsc_trace(rsc,
"%s on %s does not require fencing or quorum",
action->uuid, pcmk__node_name(action->node));
pcmk__set_action_flags(action, pcmk__action_runnable);
}
} else {
switch (effective_quorum_policy(rsc, scheduler)) {
case pcmk_no_quorum_stop:
pcmk__rsc_debug(rsc, "%s on %s is unrunnable (no quorum)",
action->uuid, pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk__action_runnable);
pe_action_set_reason(action, "no quorum", true);
break;
case pcmk_no_quorum_freeze:
if (!rsc->priv->fns->active(rsc, TRUE)
|| (rsc->priv->next_role > rsc->priv->orig_role)) {
pcmk__rsc_debug(rsc, "%s on %s is unrunnable (no quorum)",
action->uuid,
pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk__action_runnable);
pe_action_set_reason(action, "quorum freeze", true);
}
break;
default:
//pe_action_set_reason(action, NULL, TRUE);
pcmk__set_action_flags(action, pcmk__action_runnable);
break;
}
}
}
static bool
valid_stop_on_fail(const char *value)
{
return !pcmk__strcase_any_of(value,
PCMK_VALUE_STANDBY, PCMK_VALUE_DEMOTE,
PCMK_VALUE_STOP, NULL);
}
/*!
* \internal
* \brief Validate (and possibly reset) resource action's on_fail meta-attribute
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] action_config Action configuration XML from CIB (if any)
* \param[in,out] meta Table of action meta-attributes
*/
static void
validate_on_fail(const pcmk_resource_t *rsc, const char *action_name,
const xmlNode *action_config, GHashTable *meta)
{
const char *name = NULL;
const char *role = NULL;
const char *interval_spec = NULL;
const char *value = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
guint interval_ms = 0U;
// Stop actions can only use certain on-fail values
if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)
&& !valid_stop_on_fail(value)) {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s stop "
"action to default value because '%s' is not "
"allowed for stop", rsc->id, value);
g_hash_table_remove(meta, PCMK_META_ON_FAIL);
return;
}
/* Demote actions default on-fail to the on-fail value for the first
* recurring monitor for the promoted role (if any).
*/
if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)
&& (value == NULL)) {
/* @TODO This does not consider promote options set in a meta-attribute
* block (which may have rules that need to be evaluated) rather than
* XML properties.
*/
for (xmlNode *operation = pcmk__xe_first_child(rsc->priv->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
const char *promote_on_fail = NULL;
/* We only care about explicit on-fail (if promote uses default, so
* can demote)
*/
promote_on_fail = crm_element_value(operation, PCMK_META_ON_FAIL);
if (promote_on_fail == NULL) {
continue;
}
// We only care about recurring monitors for the promoted role
name = crm_element_value(operation, PCMK_XA_NAME);
role = crm_element_value(operation, PCMK_XA_ROLE);
if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL)) {
continue;
}
interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// We only care about enabled monitors
if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
/* Demote actions can't default to
* PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE
*/
if (pcmk__str_eq(promote_on_fail, PCMK_VALUE_DEMOTE,
pcmk__str_casei)) {
continue;
}
// Use value from first applicable promote action found
pcmk__insert_dup(meta, PCMK_META_ON_FAIL, promote_on_fail);
}
return;
}
if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none)
&& !pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) {
pcmk__insert_dup(meta, PCMK_META_ON_FAIL, PCMK_VALUE_IGNORE);
return;
}
// PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE is allowed only for certain actions
if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
name = crm_element_value(action_config, PCMK_XA_NAME);
role = crm_element_value(action_config, PCMK_XA_ROLE);
interval_spec = crm_element_value(action_config, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none)
&& ((interval_ms == 0U)
|| !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL))) {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s %s "
"action to default value because 'demote' is not "
"allowed for it", rsc->id, name);
g_hash_table_remove(meta, PCMK_META_ON_FAIL);
return;
}
}
}
static int
unpack_timeout(const char *value)
{
long long timeout_ms = crm_get_msec(value);
if (timeout_ms <= 0) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
return (int) QB_MIN(timeout_ms, INT_MAX);
}
// true if value contains valid, non-NULL interval origin for recurring op
static bool
unpack_interval_origin(const char *value, const xmlNode *xml_obj,
guint interval_ms, const crm_time_t *now,
long long *start_delay)
{
long long result = 0;
guint interval_sec = interval_ms / 1000;
crm_time_t *origin = NULL;
// Ignore unspecified values and non-recurring operations
if ((value == NULL) || (interval_ms == 0) || (now == NULL)) {
return false;
}
// Parse interval origin from text
origin = crm_time_new(value);
if (origin == NULL) {
pcmk__config_err("Ignoring '" PCMK_META_INTERVAL_ORIGIN "' for "
"operation '%s' because '%s' is not valid",
pcmk__s(pcmk__xe_id(xml_obj), "(missing ID)"), value);
return false;
}
// Get seconds since origin (negative if origin is in the future)
result = crm_time_get_seconds(now) - crm_time_get_seconds(origin);
crm_time_free(origin);
// Calculate seconds from closest interval to now
result = result % interval_sec;
// Calculate seconds remaining until next interval
result = ((result <= 0)? 0 : interval_sec) - result;
crm_info("Calculated a start delay of %llds for operation '%s'",
result, pcmk__s(pcmk__xe_id(xml_obj), "(unspecified)"));
if (start_delay != NULL) {
*start_delay = result * 1000; // milliseconds
}
return true;
}
static int
unpack_start_delay(const char *value, GHashTable *meta)
{
long long start_delay_ms = 0;
if (value == NULL) {
return 0;
}
start_delay_ms = crm_get_msec(value);
start_delay_ms = QB_MIN(start_delay_ms, INT_MAX);
if (start_delay_ms < 0) {
start_delay_ms = 0;
}
if (meta != NULL) {
g_hash_table_replace(meta, strdup(PCMK_META_START_DELAY),
pcmk__itoa(start_delay_ms));
}
return (int) start_delay_ms;
}
/*!
* \internal
* \brief Find a resource's most frequent recurring monitor
*
* \param[in] rsc Resource to check
*
* \return Operation XML configured for most frequent recurring monitor for
* \p rsc (if any)
*/
static xmlNode *
most_frequent_monitor(const pcmk_resource_t *rsc)
{
guint min_interval_ms = G_MAXUINT;
xmlNode *op = NULL;
for (xmlNode *operation = pcmk__xe_first_child(rsc->priv->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
guint interval_ms = 0U;
const char *interval_spec = crm_element_value(operation,
PCMK_META_INTERVAL);
// We only care about enabled recurring monitors
if (!pcmk__str_eq(crm_element_value(operation, PCMK_XA_NAME),
PCMK_ACTION_MONITOR, pcmk__str_none)) {
continue;
}
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// @TODO This does not consider meta-attributes, rules, defaults, etc.
if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
if (interval_ms < min_interval_ms) {
min_interval_ms = interval_ms;
op = operation;
}
}
return op;
}
/*!
* \internal
* \brief Unpack action meta-attributes
*
* \param[in,out] rsc Resource that action is for
* \param[in] node Node that action is on
* \param[in] action_name Action name
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] action_config Action XML configuration from CIB (if any)
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds) from its CIB XML
* configuration (including defaults).
*
* \return Newly allocated hash table with normalized action meta-attributes
*/
GHashTable *
pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *action_name, guint interval_ms,
const xmlNode *action_config)
{
GHashTable *meta = NULL;
const char *timeout_spec = NULL;
const char *str = NULL;
pe_rsc_eval_data_t rsc_rule_data = {
.standard = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS),
.provider = crm_element_value(rsc->priv->xml, PCMK_XA_PROVIDER),
.agent = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE),
};
pe_op_eval_data_t op_rule_data = {
.op_name = action_name,
.interval = interval_ms,
};
pe_rule_eval_data_t rule_data = {
- /* @COMPAT Support for node attribute expressions in operation
- * meta-attributes (whether in the operation configuration or operation
- * defaults) is deprecated. When we can break behavioral backward
- * compatibility, drop this line.
+ /* Node attributes are not set because node expressions are not allowed
+ * for meta-attributes
*/
- .node_hash = (node == NULL)? NULL : node->priv->attrs,
-
.now = rsc->priv->scheduler->priv->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = &op_rule_data,
};
meta = pcmk__strkey_table(free, free);
if (action_config != NULL) {
// take precedence over defaults
pe__unpack_dataset_nvpairs(action_config, PCMK_XE_META_ATTRIBUTES,
&rule_data, meta, NULL,
rsc->priv->scheduler);
/* Anything set as an XML property has highest precedence.
* This ensures we use the name and interval from the tag.
* (See below for the only exception, fence device start/probe timeout.)
*/
for (xmlAttrPtr attr = action_config->properties;
attr != NULL; attr = attr->next) {
pcmk__insert_dup(meta, (const char *) attr->name,
pcmk__xml_attr_value(attr));
}
}
// Derive default timeout for probes from recurring monitor timeouts
if (pcmk_is_probe(action_name, interval_ms)
&& (g_hash_table_lookup(meta, PCMK_META_TIMEOUT) == NULL)) {
xmlNode *min_interval_mon = most_frequent_monitor(rsc);
if (min_interval_mon != NULL) {
/* @TODO This does not consider timeouts set in
* PCMK_XE_META_ATTRIBUTES blocks (which may also have rules that
* need to be evaluated).
*/
timeout_spec = crm_element_value(min_interval_mon,
PCMK_META_TIMEOUT);
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting default timeout for %s probe to "
"most frequent monitor's timeout '%s'",
rsc->id, timeout_spec);
pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
}
}
}
// Cluster-wide
pe__unpack_dataset_nvpairs(rsc->priv->scheduler->priv->op_defaults,
PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL,
rsc->priv->scheduler);
g_hash_table_remove(meta, PCMK_XA_ID);
// Normalize interval to milliseconds
if (interval_ms > 0) {
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_INTERVAL),
crm_strdup_printf("%u", interval_ms));
} else {
g_hash_table_remove(meta, PCMK_META_INTERVAL);
}
/* Timeout order of precedence (highest to lowest):
* 1. pcmk_monitor_timeout resource parameter (only for starts and probes
* when rsc has pcmk_ra_cap_fence_params; this gets used for recurring
* monitors via the executor instead)
* 2. timeout configured in (with taking precedence over
* )
* 3. timeout configured in
* 4. PCMK_DEFAULT_ACTION_TIMEOUT_MS
*/
// Check for pcmk_monitor_timeout
if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard),
pcmk_ra_cap_fence_params)
&& (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)
|| pcmk_is_probe(action_name, interval_ms))) {
GHashTable *params = pe_rsc_params(rsc, node, rsc->priv->scheduler);
timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout");
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting timeout for %s %s to "
"pcmk_monitor_timeout (%s)",
rsc->id, action_name, timeout_spec);
pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
}
}
// Normalize timeout to positive milliseconds
timeout_spec = g_hash_table_lookup(meta, PCMK_META_TIMEOUT);
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_TIMEOUT),
pcmk__itoa(unpack_timeout(timeout_spec)));
// Ensure on-fail has a valid value
validate_on_fail(rsc, action_name, action_config, meta);
// Normalize PCMK_META_START_DELAY
str = g_hash_table_lookup(meta, PCMK_META_START_DELAY);
if (str != NULL) {
unpack_start_delay(str, meta);
} else {
long long start_delay = 0;
str = g_hash_table_lookup(meta, PCMK_META_INTERVAL_ORIGIN);
if (unpack_interval_origin(str, action_config, interval_ms,
rsc->priv->scheduler->priv->now,
&start_delay)) {
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_START_DELAY),
crm_strdup_printf("%lld", start_delay));
}
}
return meta;
}
/*!
* \internal
* \brief Determine an action's quorum and fencing dependency
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action being unpacked
*
* \return Quorum and fencing dependency appropriate to action
*/
enum pcmk__requires
pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name)
{
const char *value = NULL;
enum pcmk__requires requires = pcmk__requires_nothing;
CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires);
if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START,
PCMK_ACTION_PROMOTE, NULL)) {
value = "nothing (not start or promote)";
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
requires = pcmk__requires_fencing;
value = "fencing";
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_quorum)) {
requires = pcmk__requires_quorum;
value = "quorum";
} else {
value = "nothing";
}
pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value);
return requires;
}
/*!
* \internal
* \brief Parse action failure response from a user-provided string
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] value User-provided configuration value for on-fail
*
* \return Action failure response parsed from \p text
*/
enum pcmk__on_fail
pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, const char *value)
{
const char *desc = NULL;
bool needs_remote_reset = false;
enum pcmk__on_fail on_fail = pcmk__on_fail_ignore;
const pcmk_scheduler_t *scheduler = NULL;
// There's no enum value for unknown or invalid, so assert
pcmk__assert((rsc != NULL) && (action_name != NULL));
scheduler = rsc->priv->scheduler;
if (value == NULL) {
// Use default
} else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
on_fail = pcmk__on_fail_block;
desc = "block";
} else if (pcmk__str_eq(value, PCMK_VALUE_FENCE, pcmk__str_casei)) {
if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
on_fail = pcmk__on_fail_fence_node;
desc = "node fencing";
} else {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for "
"%s of %s to 'stop' because 'fence' is not "
"valid when fencing is disabled",
action_name, rsc->id);
on_fail = pcmk__on_fail_stop;
desc = "stop resource";
}
} else if (pcmk__str_eq(value, PCMK_VALUE_STANDBY, pcmk__str_casei)) {
on_fail = pcmk__on_fail_standby_node;
desc = "node standby";
} else if (pcmk__strcase_any_of(value,
PCMK_VALUE_IGNORE, PCMK_VALUE_NOTHING,
NULL)) {
desc = "ignore";
} else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) {
on_fail = pcmk__on_fail_ban;
desc = "force migration";
} else if (pcmk__str_eq(value, PCMK_VALUE_STOP, pcmk__str_casei)) {
on_fail = pcmk__on_fail_stop;
desc = "stop resource";
} else if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
on_fail = pcmk__on_fail_restart;
desc = "restart (and possibly migrate)";
} else if (pcmk__str_eq(value, PCMK_VALUE_RESTART_CONTAINER,
pcmk__str_casei)) {
if (rsc->priv->launcher == NULL) {
pcmk__rsc_debug(rsc,
"Using default " PCMK_META_ON_FAIL " for %s "
"of %s because it does not have a launcher",
action_name, rsc->id);
} else {
on_fail = pcmk__on_fail_restart_container;
desc = "restart container (and possibly migrate)";
}
} else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
on_fail = pcmk__on_fail_demote;
desc = "demote instance";
} else {
pcmk__config_err("Using default '" PCMK_META_ON_FAIL "' for "
"%s of %s because '%s' is not valid",
action_name, rsc->id, value);
}
/* Remote node connections are handled specially. Failures that result
* in dropping an active connection must result in fencing. The only
* failures that don't are probes and starts. The user can explicitly set
* PCMK_META_ON_FAIL=PCMK_VALUE_FENCE to fence after start failures.
*/
if (pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)
&& pcmk__is_remote_node(pcmk_find_node(scheduler, rsc->id))
&& !pcmk_is_probe(action_name, interval_ms)
&& !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
needs_remote_reset = true;
if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
desc = NULL; // Force default for unmanaged connections
}
}
if (desc != NULL) {
// Explicit value used, default not needed
} else if (rsc->priv->launcher != NULL) {
on_fail = pcmk__on_fail_restart_container;
desc = "restart container (and possibly migrate) (default)";
} else if (needs_remote_reset) {
if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
desc = "fence remote node (default)";
} else {
desc = "recover remote node connection (default)";
}
on_fail = pcmk__on_fail_reset_remote;
} else {
on_fail = pcmk__on_fail_stop;
desc = "stop unmanaged remote node (enforcing default)";
}
} else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
on_fail = pcmk__on_fail_fence_node;
desc = "resource fence (default)";
} else {
on_fail = pcmk__on_fail_block;
desc = "resource block (default)";
}
} else {
on_fail = pcmk__on_fail_restart;
desc = "restart (and possibly migrate) (default)";
}
pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s",
pcmk__readable_interval(interval_ms), action_name,
rsc->id, desc);
return on_fail;
}
/*!
* \internal
* \brief Determine a resource's role after failure of an action
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] on_fail Failure handling for action
* \param[in] meta Unpacked action meta-attributes
*
* \return Resource role that results from failure of action
*/
enum rsc_role_e
pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name,
enum pcmk__on_fail on_fail, GHashTable *meta)
{
const char *value = NULL;
enum rsc_role_e role = pcmk_role_unknown;
// Set default for role after failure specially in certain circumstances
switch (on_fail) {
case pcmk__on_fail_stop:
role = pcmk_role_stopped;
break;
case pcmk__on_fail_reset_remote:
if (rsc->priv->remote_reconnect_ms != 0U) {
role = pcmk_role_stopped;
}
break;
default:
break;
}
// @COMPAT Check for explicitly configured role (deprecated)
value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE);
if (value != NULL) {
pcmk__warn_once(pcmk__wo_role_after,
"Support for " PCMK__META_ROLE_AFTER_FAILURE " is "
"deprecated and will be removed in a future release");
if (role == pcmk_role_unknown) {
role = pcmk_parse_role(value);
if (role == pcmk_role_unknown) {
pcmk__config_err("Ignoring invalid value %s for "
PCMK__META_ROLE_AFTER_FAILURE,
value);
}
}
}
if (role == pcmk_role_unknown) {
// Use default
if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
role = pcmk_role_unpromoted;
} else {
role = pcmk_role_started;
}
}
pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s",
rsc->id, action_name, pcmk_role_text(role));
return role;
}
/*!
* \internal
* \brief Unpack action configuration
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds), requirements, and
* failure policy from its CIB XML configuration (including defaults).
*
* \param[in,out] action Resource action to unpack into
* \param[in] xml_obj Action configuration XML (NULL for defaults only)
* \param[in] interval_ms How frequently to perform the operation
*/
static void
unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms)
{
const char *value = NULL;
action->meta = pcmk__unpack_action_meta(action->rsc, action->node,
action->task, interval_ms, xml_obj);
action->needs = pcmk__action_requires(action->rsc, action->task);
value = g_hash_table_lookup(action->meta, PCMK_META_ON_FAIL);
action->on_fail = pcmk__parse_on_fail(action->rsc, action->task,
interval_ms, value);
action->fail_role = pcmk__role_after_failure(action->rsc, action->task,
action->on_fail, action->meta);
}
/*!
* \brief Create or update an action object
*
* \param[in,out] rsc Resource that action is for (if any)
* \param[in,out] key Action key (must be non-NULL)
* \param[in] task Action name (must be non-NULL)
* \param[in] on_node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Action object corresponding to arguments (guaranteed not to be
* \c NULL)
* \note This function takes ownership of (and might free) \p key, and
* \p scheduler takes ownership of the returned action (the caller should
* not free it).
*/
pcmk_action_t *
custom_action(pcmk_resource_t *rsc, char *key, const char *task,
const pcmk_node_t *on_node, gboolean optional,
pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = NULL;
pcmk__assert((key != NULL) && (task != NULL) && (scheduler != NULL));
action = find_existing_action(key, rsc, on_node, scheduler);
if (action == NULL) {
action = new_action(key, task, rsc, on_node, optional, scheduler);
} else {
free(key);
}
update_action_optional(action, optional);
if (rsc != NULL) {
/* An action can be initially created with a NULL node, and later have
* the node added via find_existing_action() (above) -> find_actions().
* That is why the extra parameters are unpacked here rather than in
* new_action().
*/
if ((action->node != NULL) && (action->op_entry != NULL)
&& !pcmk_is_set(action->flags, pcmk__action_attrs_evaluated)) {
GHashTable *attrs = action->node->priv->attrs;
if (action->extra != NULL) {
g_hash_table_destroy(action->extra);
}
action->extra = pcmk__unpack_action_rsc_params(action->op_entry,
attrs, scheduler);
pcmk__set_action_flags(action, pcmk__action_attrs_evaluated);
}
update_resource_action_runnable(action, scheduler);
}
if (action->extra == NULL) {
action->extra = pcmk__strkey_table(free, free);
}
return action;
}
pcmk_action_t *
get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *op = lookup_singleton(scheduler, name);
if (op == NULL) {
op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler);
pcmk__set_action_flags(op, pcmk__action_pseudo|pcmk__action_runnable);
}
return op;
}
static GList *
find_unfencing_devices(GList *candidates, GList *matches)
{
for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *candidate = gIter->data;
if (candidate->priv->children != NULL) {
matches = find_unfencing_devices(candidate->priv->children,
matches);
} else if (!pcmk_is_set(candidate->flags, pcmk__rsc_fence_device)) {
continue;
} else if (pcmk_is_set(candidate->flags, pcmk__rsc_needs_unfencing)) {
matches = g_list_prepend(matches, candidate);
} else if (pcmk__str_eq(g_hash_table_lookup(candidate->priv->meta,
PCMK_STONITH_PROVIDES),
PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
matches = g_list_prepend(matches, candidate);
}
}
return matches;
}
static int
node_priority_fencing_delay(const pcmk_node_t *node,
const pcmk_scheduler_t *scheduler)
{
int member_count = 0;
int online_count = 0;
int top_priority = 0;
int lowest_priority = 0;
GList *gIter = NULL;
// PCMK_OPT_PRIORITY_FENCING_DELAY is disabled
if (scheduler->priv->priority_fencing_ms == 0U) {
return 0;
}
/* No need to request a delay if the fencing target is not a normal cluster
* member, for example if it's a remote node or a guest node. */
if (node->priv->variant != pcmk__node_variant_cluster) {
return 0;
}
// No need to request a delay if the fencing target is in our partition
if (node->details->online) {
return 0;
}
for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *n = gIter->data;
if (n->priv->variant != pcmk__node_variant_cluster) {
continue;
}
member_count ++;
if (n->details->online) {
online_count++;
}
if (member_count == 1
|| n->priv->priority > top_priority) {
top_priority = n->priv->priority;
}
if (member_count == 1
|| n->priv->priority < lowest_priority) {
lowest_priority = n->priv->priority;
}
}
// No need to delay if we have more than half of the cluster members
if (online_count > member_count / 2) {
return 0;
}
/* All the nodes have equal priority.
* Any configured corresponding `pcmk_delay_base/max` will be applied. */
if (lowest_priority == top_priority) {
return 0;
}
if (node->priv->priority < top_priority) {
return 0;
}
return (int) (scheduler->priv->priority_fencing_ms / 1000U);
}
pcmk_action_t *
pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
const char *reason, bool priority_delay,
pcmk_scheduler_t *scheduler)
{
char *op_key = NULL;
pcmk_action_t *stonith_op = NULL;
if(op == NULL) {
op = scheduler->priv->fence_action;
}
op_key = crm_strdup_printf("%s-%s-%s",
PCMK_ACTION_STONITH, node->priv->name, op);
stonith_op = lookup_singleton(scheduler, op_key);
if(stonith_op == NULL) {
stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node,
TRUE, scheduler);
pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE, node->priv->name);
pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE_UUID,
node->priv->id);
pcmk__insert_meta(stonith_op, PCMK__META_STONITH_ACTION, op);
if (pcmk_is_set(scheduler->flags, pcmk__sched_enable_unfencing)) {
/* Extra work to detect device changes
*/
GString *digests_all = g_string_sized_new(1024);
GString *digests_secure = g_string_sized_new(1024);
GList *matches = find_unfencing_devices(scheduler->priv->resources,
NULL);
for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *match = gIter->data;
const char *agent = g_hash_table_lookup(match->priv->meta,
PCMK_XA_TYPE);
pcmk__op_digest_t *data = NULL;
data = pe__compare_fencing_digest(match, agent, node,
scheduler);
if (data->rc == pcmk__digest_mismatch) {
optional = FALSE;
crm_notice("Unfencing node %s because the definition of "
"%s changed", pcmk__node_name(node), match->id);
if (!pcmk__is_daemon && (scheduler->priv->out != NULL)) {
pcmk__output_t *out = scheduler->priv->out;
out->info(out,
"notice: Unfencing node %s because the "
"definition of %s changed",
pcmk__node_name(node), match->id);
}
}
pcmk__g_strcat(digests_all,
match->id, ":", agent, ":",
data->digest_all_calc, ",", NULL);
pcmk__g_strcat(digests_secure,
match->id, ":", agent, ":",
data->digest_secure_calc, ",", NULL);
}
pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_ALL,
digests_all->str);
g_string_free(digests_all, TRUE);
pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_SECURE,
digests_secure->str);
g_string_free(digests_secure, TRUE);
g_list_free(matches);
}
} else {
free(op_key);
}
if ((scheduler->priv->priority_fencing_ms > 0U)
/* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY
* applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as
* an indicator.
*/
&& (priority_delay
/* The priority delay needs to be recalculated if this function has
* been called by schedule_fencing_and_shutdowns() after node
* priority has already been calculated by native_add_running().
*/
|| g_hash_table_lookup(stonith_op->meta,
PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) {
/* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if
* it's 0 for the targeting node. So that it takes precedence over
* any possible `pcmk_delay_base/max`.
*/
char *delay_s = pcmk__itoa(node_priority_fencing_delay(node,
scheduler));
g_hash_table_insert(stonith_op->meta,
strdup(PCMK_OPT_PRIORITY_FENCING_DELAY),
delay_s);
}
if(optional == FALSE && pe_can_fence(scheduler, node)) {
pcmk__clear_action_flags(stonith_op, pcmk__action_optional);
pe_action_set_reason(stonith_op, reason, false);
} else if(reason && stonith_op->reason == NULL) {
stonith_op->reason = strdup(reason);
}
return stonith_op;
}
void
pe_free_action(pcmk_action_t *action)
{
if (action == NULL) {
return;
}
g_list_free_full(action->actions_before, free);
g_list_free_full(action->actions_after, free);
if (action->extra) {
g_hash_table_destroy(action->extra);
}
if (action->meta) {
g_hash_table_destroy(action->meta);
}
free(action->cancel_task);
free(action->reason);
free(action->task);
free(action->uuid);
free(action->node);
free(action);
}
enum pcmk__action_type
get_complex_task(const pcmk_resource_t *rsc, const char *name)
{
enum pcmk__action_type task = pcmk__parse_action(name);
if (pcmk__is_primitive(rsc)) {
switch (task) {
case pcmk__action_stopped:
case pcmk__action_started:
case pcmk__action_demoted:
case pcmk__action_promoted:
crm_trace("Folding %s back into its atomic counterpart for %s",
name, rsc->id);
--task;
break;
default:
break;
}
}
return task;
}
/*!
* \internal
* \brief Find first matching action in a list
*
* \param[in] input List of actions to search
* \param[in] uuid If not NULL, action must have this UUID
* \param[in] task If not NULL, action must have this action name
* \param[in] on_node If not NULL, action must be on this node
*
* \return First action in list that matches criteria, or NULL if none
*/
pcmk_action_t *
find_first_action(const GList *input, const char *uuid, const char *task,
const pcmk_node_t *on_node)
{
CRM_CHECK(uuid || task, return NULL);
for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) {
continue;
} else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
return action;
} else if (action->node == NULL) {
continue;
} else if (pcmk__same_node(on_node, action->node)) {
return action;
}
}
return NULL;
}
GList *
find_actions(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *gIter = input;
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
crm_trace("Action %s matches (ignoring node)", key);
result = g_list_prepend(result, action);
} else if (action->node == NULL) {
crm_trace("Action %s matches (unallocated, assigning to %s)",
key, pcmk__node_name(on_node));
action->node = pe__copy_node(on_node);
result = g_list_prepend(result, action);
} else if (pcmk__same_node(on_node, action->node)) {
crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
GList *
find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
if (on_node == NULL) {
return NULL;
}
for (GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if ((action->node != NULL)
&& pcmk__str_eq(key, action->uuid, pcmk__str_casei)
&& pcmk__same_node(on_node, action->node)) {
crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
/*!
* \brief Find all actions of given type for a resource
*
* \param[in] rsc Resource to search
* \param[in] node Find only actions scheduled on this node
* \param[in] task Action name to search for
* \param[in] require_node If TRUE, NULL node or action node will not match
*
* \return List of actions found (or NULL if none)
* \note If node is not NULL and require_node is FALSE, matching actions
* without a node will be assigned to node.
*/
GList *
pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *task, bool require_node)
{
GList *result = NULL;
char *key = pcmk__op_key(rsc->id, task, 0);
if (require_node) {
result = find_actions_exact(rsc->priv->actions, key, node);
} else {
result = find_actions(rsc->priv->actions, key, node);
}
free(key);
return result;
}
/*!
* \internal
* \brief Create an action reason string based on the action itself
*
* \param[in] action Action to create reason string for
* \param[in] flag Action flag that was cleared
*
* \return Newly allocated string suitable for use as action reason
* \note It is the caller's responsibility to free() the result.
*/
char *
pe__action2reason(const pcmk_action_t *action, enum pcmk__action_flags flag)
{
const char *change = NULL;
switch (flag) {
case pcmk__action_runnable:
change = "unrunnable";
break;
case pcmk__action_migratable:
change = "unmigrateable";
break;
case pcmk__action_optional:
change = "required";
break;
default:
// Bug: caller passed unsupported flag
CRM_CHECK(change != NULL, change = "");
break;
}
return crm_strdup_printf("%s%s%s %s", change,
(action->rsc == NULL)? "" : " ",
(action->rsc == NULL)? "" : action->rsc->id,
action->task);
}
void pe_action_set_reason(pcmk_action_t *action, const char *reason,
bool overwrite)
{
if (action->reason != NULL && overwrite) {
pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'",
action->uuid, action->reason,
pcmk__s(reason, "(none)"));
} else if (action->reason == NULL) {
pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'",
action->uuid, pcmk__s(reason, "(none)"));
} else {
// crm_assert(action->reason != NULL && !overwrite);
return;
}
pcmk__str_update(&action->reason, reason);
}
/*!
* \internal
* \brief Create an action to clear a resource's history from CIB
*
* \param[in,out] rsc Resource to clear
* \param[in] node Node to clear history on
*/
void
pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node)
{
pcmk__assert((rsc != NULL) && (node != NULL));
custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->priv->scheduler);
}
#define sort_return(an_int, why) do { \
free(a_uuid); \
free(b_uuid); \
crm_trace("%s (%d) %c %s (%d) : %s", \
a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \
b_xml_id, b_call_id, why); \
return an_int; \
} while(0)
int
pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b)
{
int a_call_id = -1;
int b_call_id = -1;
char *a_uuid = NULL;
char *b_uuid = NULL;
const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID);
const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID);
const char *a_node = crm_element_value(xml_a, PCMK__META_ON_NODE);
const char *b_node = crm_element_value(xml_b, PCMK__META_ON_NODE);
bool same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei);
if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) {
/* We have duplicate PCMK__XE_LRM_RSC_OP entries in the status
* section which is unlikely to be a good thing
* - we can handle it easily enough, but we need to get
* to the bottom of why it's happening.
*/
pcmk__config_err("Duplicate " PCMK__XE_LRM_RSC_OP " entries named %s",
a_xml_id);
sort_return(0, "duplicate");
}
crm_element_value_int(xml_a, PCMK__XA_CALL_ID, &a_call_id);
crm_element_value_int(xml_b, PCMK__XA_CALL_ID, &b_call_id);
if (a_call_id == -1 && b_call_id == -1) {
/* both are pending ops so it doesn't matter since
* stops are never pending
*/
sort_return(0, "pending");
} else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) {
sort_return(-1, "call id");
} else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) {
sort_return(1, "call id");
} else if (a_call_id >= 0 && b_call_id >= 0
&& (!same_node || a_call_id == b_call_id)) {
/* The op and last_failed_op are the same. Order on
* PCMK_XA_LAST_RC_CHANGE.
*/
time_t last_a = -1;
time_t last_b = -1;
crm_element_value_epoch(xml_a, PCMK_XA_LAST_RC_CHANGE, &last_a);
crm_element_value_epoch(xml_b, PCMK_XA_LAST_RC_CHANGE, &last_b);
crm_trace("rc-change: %lld vs %lld",
(long long) last_a, (long long) last_b);
if (last_a >= 0 && last_a < last_b) {
sort_return(-1, "rc-change");
} else if (last_b >= 0 && last_a > last_b) {
sort_return(1, "rc-change");
}
sort_return(0, "rc-change");
} else {
/* One of the inputs is a pending operation.
* Attempt to use PCMK__XA_TRANSITION_MAGIC to determine its age relative
* to the other.
*/
int a_id = -1;
int b_id = -1;
const char *a_magic = crm_element_value(xml_a,
PCMK__XA_TRANSITION_MAGIC);
const char *b_magic = crm_element_value(xml_b,
PCMK__XA_TRANSITION_MAGIC);
CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic a");
}
if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic b");
}
/* try to determine the relative age of the operation...
* some pending operations (e.g. a start) may have been superseded
* by a subsequent stop
*
* [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
*/
if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) {
/*
* some of the logic in here may be redundant...
*
* if the UUID from the TE doesn't match then one better
* be a pending operation.
* pending operations don't survive between elections and joins
* because we query the LRM directly
*/
if (b_call_id == -1) {
sort_return(-1, "transition + call");
} else if (a_call_id == -1) {
sort_return(1, "transition + call");
}
} else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
sort_return(-1, "transition");
} else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
sort_return(1, "transition");
}
}
/* we should never end up here */
CRM_CHECK(FALSE, sort_return(0, "default"));
}
gint
sort_op_by_callid(gconstpointer a, gconstpointer b)
{
return pe__is_newer_op((const xmlNode *) a, (const xmlNode *) b);
}
/*!
* \internal
* \brief Create a new pseudo-action for a resource
*
* \param[in,out] rsc Resource to create action for
* \param[in] task Action name
* \param[in] optional Whether action should be considered optional
* \param[in] runnable Whethe action should be considered runnable
*
* \return New action object corresponding to arguments
*/
pcmk_action_t *
pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional,
bool runnable)
{
pcmk_action_t *action = NULL;
pcmk__assert((rsc != NULL) && (task != NULL));
action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL,
optional, rsc->priv->scheduler);
pcmk__set_action_flags(action, pcmk__action_pseudo);
if (runnable) {
pcmk__set_action_flags(action, pcmk__action_runnable);
}
return action;
}
/*!
* \internal
* \brief Add the expected result to an action
*
* \param[in,out] action Action to add expected result to
* \param[in] expected_result Expected result to add
*
* \note This is more efficient than calling pcmk__insert_meta().
*/
void
pe__add_action_expected_result(pcmk_action_t *action, int expected_result)
{
pcmk__assert((action != NULL) && (action->meta != NULL));
g_hash_table_insert(action->meta, pcmk__str_copy(PCMK__META_OP_TARGET_RC),
pcmk__itoa(expected_result));
}
diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c
index 8c4beb0230..d955f1d3de 100644
--- a/lib/pengine/utils.c
+++ b/lib/pengine/utils.c
@@ -1,911 +1,925 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include "pe_status_private.h"
extern bool pcmk__is_daemon;
gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data);
/*!
* \internal
* \brief Check whether we can fence a particular node
*
* \param[in] scheduler Scheduler data
* \param[in] node Name of node to check
*
* \return true if node can be fenced, false otherwise
*/
bool
pe_can_fence(const pcmk_scheduler_t *scheduler, const pcmk_node_t *node)
{
if (pcmk__is_guest_or_bundle_node(node)) {
/* A guest or bundle node is fenced by stopping its launcher, which is
* possible if the launcher's host is either online or fenceable.
*/
pcmk_resource_t *rsc = node->priv->remote->priv->launcher;
for (GList *n = rsc->priv->active_nodes; n != NULL; n = n->next) {
pcmk_node_t *launcher_node = n->data;
if (!launcher_node->details->online
&& !pe_can_fence(scheduler, launcher_node)) {
return false;
}
}
return true;
} else if (!pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
return false; /* Turned off */
} else if (!pcmk_is_set(scheduler->flags, pcmk__sched_have_fencing)) {
return false; /* No devices */
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_quorate)) {
return true;
} else if (scheduler->no_quorum_policy == pcmk_no_quorum_ignore) {
return true;
} else if(node == NULL) {
return false;
} else if(node->details->online) {
crm_notice("We can fence %s without quorum because they're in our membership",
pcmk__node_name(node));
return true;
}
crm_trace("Cannot fence %s", pcmk__node_name(node));
return false;
}
/*!
* \internal
* \brief Copy a node object
*
* \param[in] this_node Node object to copy
*
* \return Newly allocated shallow copy of this_node
* \note This function asserts on errors and is guaranteed to return non-NULL.
*/
pcmk_node_t *
pe__copy_node(const pcmk_node_t *this_node)
{
pcmk_node_t *new_node = NULL;
pcmk__assert(this_node != NULL);
new_node = pcmk__assert_alloc(1, sizeof(pcmk_node_t));
new_node->assign = pcmk__assert_alloc(1,
sizeof(struct pcmk__node_assignment));
new_node->assign->probe_mode = this_node->assign->probe_mode;
new_node->assign->score = this_node->assign->score;
new_node->assign->count = this_node->assign->count;
new_node->details = this_node->details;
new_node->priv = this_node->priv;
return new_node;
}
/*!
* \internal
* \brief Create a node hash table from a node list
*
* \param[in] list Node list
*
* \return Hash table equivalent of node list
*/
GHashTable *
pe__node_list2table(const GList *list)
{
GHashTable *result = NULL;
result = pcmk__strkey_table(NULL, free);
for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *new_node = NULL;
new_node = pe__copy_node((const pcmk_node_t *) gIter->data);
g_hash_table_insert(result, (gpointer) new_node->priv->id, new_node);
}
return result;
}
/*!
* \internal
* \brief Compare two nodes by name, with numeric portions sorted numerically
*
* Sort two node names case-insensitively like strcasecmp(), but with any
* numeric portions of the name sorted numerically. For example, "node10" will
* sort higher than "node9" but lower than "remotenode9".
*
* \param[in] a First node to compare (can be \c NULL)
* \param[in] b Second node to compare (can be \c NULL)
*
* \retval -1 \c a comes before \c b (or \c a is \c NULL and \c b is not)
* \retval 0 \c a and \c b are equal (or both are \c NULL)
* \retval 1 \c a comes after \c b (or \c b is \c NULL and \c a is not)
*/
gint
pe__cmp_node_name(gconstpointer a, gconstpointer b)
{
const pcmk_node_t *node1 = (const pcmk_node_t *) a;
const pcmk_node_t *node2 = (const pcmk_node_t *) b;
if ((node1 == NULL) && (node2 == NULL)) {
return 0;
}
if (node1 == NULL) {
return -1;
}
if (node2 == NULL) {
return 1;
}
return pcmk__numeric_strcasecmp(node1->priv->name, node2->priv->name);
}
/*!
* \internal
* \brief Output node weights to stdout
*
* \param[in] rsc Use allowed nodes for this resource
* \param[in] comment Text description to prefix lines with
* \param[in] nodes If rsc is not specified, use these nodes
* \param[in,out] scheduler Scheduler data
*/
static void
pe__output_node_weights(const pcmk_resource_t *rsc, const char *comment,
GHashTable *nodes, pcmk_scheduler_t *scheduler)
{
pcmk__output_t *out = scheduler->priv->out;
// Sort the nodes so the output is consistent for regression tests
GList *list = g_list_sort(g_hash_table_get_values(nodes),
pe__cmp_node_name);
for (const GList *gIter = list; gIter != NULL; gIter = gIter->next) {
const pcmk_node_t *node = (const pcmk_node_t *) gIter->data;
out->message(out, "node-weight", rsc, comment, node->priv->name,
pcmk_readable_score(node->assign->score));
}
g_list_free(list);
}
/*!
* \internal
* \brief Log node weights at trace level
*
* \param[in] file Caller's filename
* \param[in] function Caller's function name
* \param[in] line Caller's line number
* \param[in] rsc If not NULL, include this resource's ID in logs
* \param[in] comment Text description to prefix lines with
* \param[in] nodes Nodes whose scores should be logged
*/
static void
pe__log_node_weights(const char *file, const char *function, int line,
const pcmk_resource_t *rsc, const char *comment,
GHashTable *nodes)
{
GHashTableIter iter;
pcmk_node_t *node = NULL;
// Don't waste time if we're not tracing at this point
pcmk__if_tracing({}, return);
g_hash_table_iter_init(&iter, nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (rsc) {
qb_log_from_external_source(function, file,
"%s: %s allocation score on %s: %s",
LOG_TRACE, line, 0,
comment, rsc->id,
pcmk__node_name(node),
pcmk_readable_score(node->assign->score));
} else {
qb_log_from_external_source(function, file, "%s: %s = %s",
LOG_TRACE, line, 0,
comment, pcmk__node_name(node),
pcmk_readable_score(node->assign->score));
}
}
}
/*!
* \internal
* \brief Log or output node weights
*
* \param[in] file Caller's filename
* \param[in] function Caller's function name
* \param[in] line Caller's line number
* \param[in] to_log Log if true, otherwise output
* \param[in] rsc If not NULL, use this resource's ID in logs,
* and show scores recursively for any children
* \param[in] comment Text description to prefix lines with
* \param[in] nodes Nodes whose scores should be shown
* \param[in,out] scheduler Scheduler data
*/
void
pe__show_node_scores_as(const char *file, const char *function, int line,
bool to_log, const pcmk_resource_t *rsc,
const char *comment, GHashTable *nodes,
pcmk_scheduler_t *scheduler)
{
if ((rsc != NULL) && pcmk_is_set(rsc->flags, pcmk__rsc_removed)) {
// Don't show allocation scores for orphans
return;
}
if (nodes == NULL) {
// Nothing to show
return;
}
if (to_log) {
pe__log_node_weights(file, function, line, rsc, comment, nodes);
} else {
pe__output_node_weights(rsc, comment, nodes, scheduler);
}
if (rsc == NULL) {
return;
}
// If this resource has children, repeat recursively for each
for (GList *gIter = rsc->priv->children;
gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child = (pcmk_resource_t *) gIter->data;
pe__show_node_scores_as(file, function, line, to_log, child, comment,
child->priv->allowed_nodes, scheduler);
}
}
/*!
* \internal
* \brief Compare two resources by priority
*
* \param[in] a First resource to compare (can be \c NULL)
* \param[in] b Second resource to compare (can be \c NULL)
*
* \retval -1 a's priority > b's priority (or \c b is \c NULL and \c a is not)
* \retval 0 a's priority == b's priority (or both \c a and \c b are \c NULL)
* \retval 1 a's priority < b's priority (or \c a is \c NULL and \c b is not)
*/
gint
pe__cmp_rsc_priority(gconstpointer a, gconstpointer b)
{
const pcmk_resource_t *resource1 = (const pcmk_resource_t *)a;
const pcmk_resource_t *resource2 = (const pcmk_resource_t *)b;
if (a == NULL && b == NULL) {
return 0;
}
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (resource1->priv->priority > resource2->priv->priority) {
return -1;
}
if (resource1->priv->priority < resource2->priv->priority) {
return 1;
}
return 0;
}
static void
resource_node_score(pcmk_resource_t *rsc, const pcmk_node_t *node, int score,
const char *tag)
{
pcmk_node_t *match = NULL;
if ((pcmk_is_set(rsc->flags, pcmk__rsc_exclusive_probes)
|| (node->assign->probe_mode == pcmk__probe_never))
&& pcmk__str_eq(tag, "symmetric_default", pcmk__str_casei)) {
/* This string comparision may be fragile, but exclusive resources and
* exclusive nodes should not have the symmetric_default constraint
* applied to them.
*/
return;
} else {
for (GList *gIter = rsc->priv->children;
gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
resource_node_score(child_rsc, node, score, tag);
}
}
match = g_hash_table_lookup(rsc->priv->allowed_nodes, node->priv->id);
if (match == NULL) {
match = pe__copy_node(node);
g_hash_table_insert(rsc->priv->allowed_nodes,
(gpointer) match->priv->id, match);
}
match->assign->score = pcmk__add_scores(match->assign->score, score);
pcmk__rsc_trace(rsc,
"Enabling %s preference (%s) for %s on %s (now %s)",
tag, pcmk_readable_score(score), rsc->id,
pcmk__node_name(node),
pcmk_readable_score(match->assign->score));
}
void
resource_location(pcmk_resource_t *rsc, const pcmk_node_t *node, int score,
const char *tag, pcmk_scheduler_t *scheduler)
{
if (node != NULL) {
resource_node_score(rsc, node, score, tag);
} else if (scheduler != NULL) {
GList *gIter = scheduler->nodes;
for (; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *node_iter = (pcmk_node_t *) gIter->data;
resource_node_score(rsc, node_iter, score, tag);
}
} else {
GHashTableIter iter;
pcmk_node_t *node_iter = NULL;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node_iter)) {
resource_node_score(rsc, node_iter, score, tag);
}
}
if ((node == NULL) && (score == -PCMK_SCORE_INFINITY)
&& (rsc->priv->assigned_node != NULL)) {
// @TODO Should this be more like pcmk__unassign_resource()?
crm_info("Unassigning %s from %s",
rsc->id, pcmk__node_name(rsc->priv->assigned_node));
free(rsc->priv->assigned_node);
rsc->priv->assigned_node = NULL;
}
}
time_t
get_effective_time(pcmk_scheduler_t *scheduler)
{
if(scheduler) {
if (scheduler->priv->now == NULL) {
crm_trace("Recording a new 'now'");
scheduler->priv->now = crm_time_new(NULL);
}
return crm_time_get_seconds_since_epoch(scheduler->priv->now);
}
crm_trace("Defaulting to 'now'");
return time(NULL);
}
gboolean
get_target_role(const pcmk_resource_t *rsc, enum rsc_role_e *role)
{
enum rsc_role_e local_role = pcmk_role_unknown;
const char *value = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_TARGET_ROLE);
CRM_CHECK(role != NULL, return FALSE);
if (pcmk__str_eq(value, PCMK_ROLE_STARTED,
pcmk__str_null_matches|pcmk__str_casei)) {
return FALSE;
}
if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
// @COMPAT Deprecated since 2.1.8
pcmk__config_warn("Support for setting " PCMK_META_TARGET_ROLE
" to the explicit value '" PCMK_VALUE_DEFAULT
"' is deprecated and will be removed in a "
"future release (just leave it unset)");
return FALSE;
}
local_role = pcmk_parse_role(value);
if (local_role == pcmk_role_unknown) {
pcmk__config_err("Ignoring '" PCMK_META_TARGET_ROLE "' for %s "
"because '%s' is not valid", rsc->id, value);
return FALSE;
} else if (local_role > pcmk_role_started) {
if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
pcmk__rsc_promotable)) {
if (local_role > pcmk_role_unpromoted) {
/* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */
return FALSE;
}
} else {
pcmk__config_err("Ignoring '" PCMK_META_TARGET_ROLE "' for %s "
"because '%s' only makes sense for promotable "
"clones", rsc->id, value);
return FALSE;
}
}
*role = local_role;
return TRUE;
}
gboolean
order_actions(pcmk_action_t *first, pcmk_action_t *then, uint32_t flags)
{
GList *gIter = NULL;
pcmk__related_action_t *wrapper = NULL;
GList *list = NULL;
if (flags == pcmk__ar_none) {
return FALSE;
}
if ((first == NULL) || (then == NULL)) {
return FALSE;
}
crm_trace("Creating action wrappers for ordering: %s then %s",
first->uuid, then->uuid);
/* Ensure we never create a dependency on ourselves... it's happened */
pcmk__assert(first != then);
/* Filter dups, otherwise update_action_states() has too much work to do */
gIter = first->actions_after;
for (; gIter != NULL; gIter = gIter->next) {
pcmk__related_action_t *after = gIter->data;
if ((after->action == then)
&& pcmk_any_flags_set(after->flags, flags)) {
return FALSE;
}
}
wrapper = pcmk__assert_alloc(1, sizeof(pcmk__related_action_t));
wrapper->action = then;
wrapper->flags = flags;
list = first->actions_after;
list = g_list_prepend(list, wrapper);
first->actions_after = list;
wrapper = pcmk__assert_alloc(1, sizeof(pcmk__related_action_t));
wrapper->action = first;
wrapper->flags = flags;
list = then->actions_before;
list = g_list_prepend(list, wrapper);
then->actions_before = list;
return TRUE;
}
void
destroy_ticket(gpointer data)
{
pcmk__ticket_t *ticket = data;
if (ticket->state) {
g_hash_table_destroy(ticket->state);
}
free(ticket->id);
free(ticket);
}
pcmk__ticket_t *
ticket_new(const char *ticket_id, pcmk_scheduler_t *scheduler)
{
pcmk__ticket_t *ticket = NULL;
if (pcmk__str_empty(ticket_id)) {
return NULL;
}
if (scheduler->priv->ticket_constraints == NULL) {
scheduler->priv->ticket_constraints =
pcmk__strkey_table(free, destroy_ticket);
}
ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints,
ticket_id);
if (ticket == NULL) {
ticket = calloc(1, sizeof(pcmk__ticket_t));
if (ticket == NULL) {
pcmk__sched_err(scheduler, "Cannot allocate ticket '%s'",
ticket_id);
return NULL;
}
crm_trace("Creating ticket entry for %s", ticket_id);
ticket->id = strdup(ticket_id);
ticket->last_granted = -1;
ticket->state = pcmk__strkey_table(free, free);
g_hash_table_insert(scheduler->priv->ticket_constraints,
pcmk__str_copy(ticket->id), ticket);
}
return ticket;
}
const char *
rsc_printable_id(const pcmk_resource_t *rsc)
{
if (pcmk_is_set(rsc->flags, pcmk__rsc_unique)) {
return rsc->id;
}
return pcmk__xe_id(rsc->priv->xml);
}
void
pe__clear_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags)
{
pcmk__clear_rsc_flags(rsc, flags);
for (GList *gIter = rsc->priv->children;
gIter != NULL; gIter = gIter->next) {
pe__clear_resource_flags_recursive((pcmk_resource_t *) gIter->data,
flags);
}
}
void
pe__clear_resource_flags_on_all(pcmk_scheduler_t *scheduler, uint64_t flag)
{
for (GList *lpc = scheduler->priv->resources;
lpc != NULL; lpc = lpc->next) {
pcmk_resource_t *r = (pcmk_resource_t *) lpc->data;
pe__clear_resource_flags_recursive(r, flag);
}
}
void
pe__set_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags)
{
pcmk__set_rsc_flags(rsc, flags);
for (GList *gIter = rsc->priv->children;
gIter != NULL; gIter = gIter->next) {
pe__set_resource_flags_recursive((pcmk_resource_t *) gIter->data,
flags);
}
}
void
trigger_unfencing(pcmk_resource_t *rsc, pcmk_node_t *node, const char *reason,
pcmk_action_t *dependency, pcmk_scheduler_t *scheduler)
{
if (!pcmk_is_set(scheduler->flags, pcmk__sched_enable_unfencing)) {
/* No resources require it */
return;
} else if ((rsc != NULL)
&& !pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
/* Wasn't a stonith device */
return;
} else if(node
&& node->details->online
&& node->details->unclean == FALSE
&& node->details->shutdown == FALSE) {
pcmk_action_t *unfence = pe_fence_op(node, PCMK_ACTION_ON, FALSE,
reason, FALSE, scheduler);
if(dependency) {
order_actions(unfence, dependency, pcmk__ar_ordered);
}
} else if(rsc) {
GHashTableIter iter;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if(node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) {
trigger_unfencing(rsc, node, reason, dependency, scheduler);
}
}
}
}
/*!
* \internal
* \brief Check whether shutdown has been requested for a node
*
* \param[in] node Node to check
*
* \return TRUE if node has shutdown attribute set and nonzero, FALSE otherwise
* \note This differs from simply using node->details->shutdown in that it can
* be used before that has been determined (and in fact to determine it),
* and it can also be used to distinguish requested shutdown from implicit
* shutdown of remote nodes by virtue of their connection stopping.
*/
bool
pe__shutdown_requested(const pcmk_node_t *node)
{
const char *shutdown = pcmk__node_attr(node, PCMK__NODE_ATTR_SHUTDOWN, NULL,
pcmk__rsc_node_current);
return !pcmk__str_eq(shutdown, "0", pcmk__str_null_matches);
}
/*!
* \internal
* \brief Update "recheck by" time in scheduler data
*
* \param[in] recheck Epoch time when recheck should happen
* \param[in,out] scheduler Scheduler data
* \param[in] reason What time is being updated for (for logs)
*/
void
pe__update_recheck_time(time_t recheck, pcmk_scheduler_t *scheduler,
const char *reason)
{
if ((recheck > get_effective_time(scheduler))
&& ((scheduler->priv->recheck_by == 0)
|| (scheduler->priv->recheck_by > recheck))) {
scheduler->priv->recheck_by = recheck;
crm_debug("Updated next scheduler recheck to %s for %s",
pcmk__trim(ctime(&recheck)), reason);
}
}
/*!
* \internal
* \brief Extract nvpair blocks contained by a CIB XML element into a hash table
*
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name If not NULL, only use blocks of this element
* \param[in] rule_data Matching parameters to use when unpacking
+ * (node_hash member must be NULL if \p set_name is
+ * PCMK_XE_META_ATTRIBUTES)
* \param[out] hash Where to store extracted name/value pairs
* \param[in] always_first If not NULL, process block with this ID first
* \param[in,out] scheduler Scheduler data containing \p xml_obj
*/
void
pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name,
const pe_rule_eval_data_t *rule_data,
GHashTable *hash, const char *always_first,
pcmk_scheduler_t *scheduler)
{
- crm_time_t *next_change = crm_time_new_undefined();
+ crm_time_t *next_change = NULL;
+ CRM_CHECK((set_name != NULL) && (rule_data != NULL) && (hash != NULL)
+ && (scheduler != NULL), return);
+
+ // Node attribute expressions are not allowed for meta-attributes
+ CRM_CHECK((rule_data->node_hash == NULL)
+ || (strcmp(set_name, PCMK_XE_META_ATTRIBUTES) != 0), return);
+
+ if (xml_obj == NULL) {
+ return;
+ }
+
+ next_change = crm_time_new_undefined();
pe_eval_nvpairs(scheduler->input, xml_obj, set_name, rule_data, hash,
always_first, FALSE, next_change);
if (crm_time_is_defined(next_change)) {
time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change);
pe__update_recheck_time(recheck, scheduler, "rule evaluation");
}
crm_time_free(next_change);
}
bool
pe__resource_is_disabled(const pcmk_resource_t *rsc)
{
const char *target_role = NULL;
CRM_CHECK(rsc != NULL, return false);
target_role = g_hash_table_lookup(rsc->priv->meta,
PCMK_META_TARGET_ROLE);
if (target_role) {
// If invalid, we've already logged an error when unpacking
enum rsc_role_e target_role_e = pcmk_parse_role(target_role);
if ((target_role_e == pcmk_role_stopped)
|| ((target_role_e == pcmk_role_unpromoted)
&& pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
pcmk__rsc_promotable))) {
return true;
}
}
return false;
}
/*!
* \internal
* \brief Check whether a resource is running only on given node
*
* \param[in] rsc Resource to check
* \param[in] node Node to check
*
* \return true if \p rsc is running only on \p node, otherwise false
*/
bool
pe__rsc_running_on_only(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
return (rsc != NULL) && pcmk__list_of_1(rsc->priv->active_nodes)
&& pcmk__same_node((const pcmk_node_t *)
rsc->priv->active_nodes->data, node);
}
bool
pe__rsc_running_on_any(pcmk_resource_t *rsc, GList *node_list)
{
if (rsc != NULL) {
for (GList *ele = rsc->priv->active_nodes; ele; ele = ele->next) {
pcmk_node_t *node = (pcmk_node_t *) ele->data;
if (pcmk__str_in_list(node->priv->name, node_list,
pcmk__str_star_matches|pcmk__str_casei)) {
return true;
}
}
}
return false;
}
bool
pcmk__rsc_filtered_by_node(pcmk_resource_t *rsc, GList *only_node)
{
return rsc->priv->fns->active(rsc, FALSE)
&& !pe__rsc_running_on_any(rsc, only_node);
}
GList *
pe__filter_rsc_list(GList *rscs, GList *filter)
{
GList *retval = NULL;
for (GList *gIter = rscs; gIter; gIter = gIter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
/* I think the second condition is safe here for all callers of this
* function. If not, it needs to move into pe__node_text.
*/
if (pcmk__str_in_list(rsc_printable_id(rsc), filter, pcmk__str_star_matches) ||
((rsc->priv->parent != NULL)
&& pcmk__str_in_list(rsc_printable_id(rsc->priv->parent),
filter, pcmk__str_star_matches))) {
retval = g_list_prepend(retval, rsc);
}
}
return retval;
}
GList *
pe__build_node_name_list(pcmk_scheduler_t *scheduler, const char *s)
{
GList *nodes = NULL;
if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) {
/* Nothing was given so return a list of all node names. Or, '*' was
* given. This would normally fall into the pe__unames_with_tag branch
* where it will return an empty list. Catch it here instead.
*/
nodes = g_list_prepend(nodes, strdup("*"));
} else {
pcmk_node_t *node = pcmk_find_node(scheduler, s);
if (node) {
/* The given string was a valid uname for a node. Return a
* singleton list containing just that uname.
*/
nodes = g_list_prepend(nodes, strdup(s));
} else {
/* The given string was not a valid uname. It's either a tag or
* it's a typo or something. In the first case, we'll return a
* list of all the unames of the nodes with the given tag. In the
* second case, we'll return a NULL pointer and nothing will
* get displayed.
*/
nodes = pe__unames_with_tag(scheduler, s);
}
}
return nodes;
}
GList *
pe__build_rsc_list(pcmk_scheduler_t *scheduler, const char *s)
{
GList *resources = NULL;
if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) {
resources = g_list_prepend(resources, strdup("*"));
} else {
const uint32_t flags = pcmk_rsc_match_history|pcmk_rsc_match_basename;
pcmk_resource_t *rsc =
pe_find_resource_with_flags(scheduler->priv->resources, s, flags);
if (rsc) {
/* A colon in the name we were given means we're being asked to filter
* on a specific instance of a cloned resource. Put that exact string
* into the filter list. Otherwise, use the printable ID of whatever
* resource was found that matches what was asked for.
*/
if (strstr(s, ":") != NULL) {
resources = g_list_prepend(resources, strdup(rsc->id));
} else {
resources = g_list_prepend(resources, strdup(rsc_printable_id(rsc)));
}
} else {
/* The given string was not a valid resource name. It's a tag or a
* typo or something. See pe__build_node_name_list() for more
* detail.
*/
resources = pe__rscs_with_tag(scheduler, s);
}
}
return resources;
}
xmlNode *
pe__failed_probe_for_rsc(const pcmk_resource_t *rsc, const char *name)
{
const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
const char *rsc_id = rsc->id;
const pcmk_scheduler_t *scheduler = rsc->priv->scheduler;
if (pcmk__is_clone(parent)) {
rsc_id = pe__clone_child_id(parent);
}
for (xmlNode *xml_op = pcmk__xe_first_child(scheduler->priv->failed,
NULL, NULL, NULL);
xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) {
const char *value = NULL;
char *op_id = NULL;
/* This resource operation is not a failed probe. */
if (!pcmk_xe_mask_probe_failure(xml_op)) {
continue;
}
/* This resource operation was not run on the given node. Note that if name is
* NULL, this will always succeed.
*/
value = crm_element_value(xml_op, PCMK__META_ON_NODE);
if (value == NULL || !pcmk__str_eq(value, name, pcmk__str_casei|pcmk__str_null_matches)) {
continue;
}
if (!parse_op_key(pcmk__xe_history_key(xml_op), &op_id, NULL, NULL)) {
continue; // This history entry is missing an operation key
}
/* This resource operation's ID does not match the rsc_id we are looking for. */
if (!pcmk__str_eq(op_id, rsc_id, pcmk__str_none)) {
free(op_id);
continue;
}
free(op_id);
return xml_op;
}
return NULL;
}