diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c
index 1ea2dccb2b..6f23306b78 100644
--- a/daemons/attrd/attrd_utils.c
+++ b/daemons/attrd/attrd_utils.c
@@ -1,325 +1,325 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <stdbool.h>
 #include <errno.h>
 #include <glib.h>
 #include <regex.h>
 #include <sys/types.h>
 
 #include <crm/crm.h>
 #include <crm/common/ipc_internal.h>
 #include <crm/common/mainloop.h>
 #include <crm/common/xml.h>
 
 #include "pacemaker-attrd.h"
 
 cib_t *the_cib = NULL;
 
 static bool requesting_shutdown = false;
 static bool shutting_down = false;
 static GMainLoop *mloop = NULL;
 
 /* A hash table storing information on the protocol version of each peer attrd.
  * The key is the peer's uname, and the value is the protocol version number.
  */
 GHashTable *peer_protocol_vers = NULL;
 
 /*!
  * \internal
  * \brief  Set requesting_shutdown state
  */
 void
 attrd_set_requesting_shutdown(void)
 {
     requesting_shutdown = true;
 }
 
 /*!
  * \internal
  * \brief  Clear requesting_shutdown state
  */
 void
 attrd_clear_requesting_shutdown(void)
 {
     requesting_shutdown = false;
 }
 
 /*!
  * \internal
  * \brief Check whether local attribute manager is shutting down
  *
  * \param[in] if_requested  If \c true, also consider presence of
  *                          \c PCMK__NODE_ATTR_SHUTDOWN attribute
  *
  * \return \c true if local attribute manager has begun shutdown sequence
  *         or (if \p if_requested is \c true) whether local node has a nonzero
  *         \c PCMK__NODE_ATTR_SHUTDOWN attribute set, otherwise \c false
  * \note Most callers should pass \c false for \p if_requested, because the
  *       attribute manager needs to continue performing while the controller is
  *       shutting down, and even needs to be eligible for election in case all
  *       nodes are shutting down.
  */
 bool
 attrd_shutting_down(bool if_requested)
 {
     return shutting_down || (if_requested && requesting_shutdown);
 }
 
 /*!
  * \internal
  * \brief  Exit (using mainloop or not, as appropriate)
  *
  * \param[in] nsig  Ignored
  */
 void
 attrd_shutdown(int nsig)
 {
     // Tell various functions not to do anthing
     shutting_down = true;
 
     // Don't respond to signals while shutting down
     mainloop_destroy_signal(SIGTERM);
     mainloop_destroy_signal(SIGCHLD);
     mainloop_destroy_signal(SIGPIPE);
     mainloop_destroy_signal(SIGUSR1);
     mainloop_destroy_signal(SIGUSR2);
     mainloop_destroy_signal(SIGTRAP);
 
     attrd_free_waitlist();
     attrd_free_confirmations();
 
     if (peer_protocol_vers != NULL) {
         g_hash_table_destroy(peer_protocol_vers);
         peer_protocol_vers = NULL;
     }
 
     if ((mloop == NULL) || !g_main_loop_is_running(mloop)) {
         /* If there's no main loop active, just exit. This should be possible
          * only if we get SIGTERM in brief windows at start-up and shutdown.
          */
         crm_exit(CRM_EX_OK);
     } else {
         g_main_loop_quit(mloop);
         g_main_loop_unref(mloop);
     }
 }
 
 /*!
  * \internal
  * \brief Create a main loop for attrd
  */
 void
 attrd_init_mainloop(void)
 {
     mloop = g_main_loop_new(NULL, FALSE);
 }
 
 /*!
  * \internal
  * \brief Run attrd main loop
  */
 void
 attrd_run_mainloop(void)
 {
     g_main_loop_run(mloop);
 }
 
 /* strlen("value") */
 #define plus_plus_len (5)
 
 /*!
  * \internal
  * \brief  Check whether an attribute value should be expanded
  *
  * \param[in] value  Attribute value to check
  *
  * \return true if value needs expansion, false otherwise
  */
 bool
 attrd_value_needs_expansion(const char *value)
 {
     return ((strlen(value) >= (plus_plus_len + 2))
            && (value[plus_plus_len] == '+')
            && ((value[plus_plus_len + 1] == '+')
                || (value[plus_plus_len + 1] == '=')));
 }
 
 /*!
  * \internal
  * \brief Expand an increment expression into an integer
  *
  * \param[in] value      Attribute increment expression to expand
  * \param[in] old_value  Previous value of attribute
  *
  * \return Expanded value
  */
 int
 attrd_expand_value(const char *value, const char *old_value)
 {
     int offset = 1;
     int int_value = char2score(old_value);
 
     if (value[plus_plus_len + 1] != '+') {
         const char *offset_s = value + (plus_plus_len + 2);
 
         offset = char2score(offset_s);
     }
     int_value += offset;
 
-    if (int_value > INFINITY) {
-        int_value = INFINITY;
+    if (int_value > PCMK_SCORE_INFINITY) {
+        int_value = PCMK_SCORE_INFINITY;
     }
     return int_value;
 }
 
 /*!
  * \internal
  * \brief Create regular expression matching failure-related attributes
  *
  * \param[out] regex  Where to store created regular expression
  * \param[in]  rsc    Name of resource to clear (or NULL for all)
  * \param[in]  op     Operation to clear if rsc is specified (or NULL for all)
  * \param[in]  interval_ms  Interval of operation to clear if op is specified
  *
  * \return pcmk_ok on success, -EINVAL if arguments are invalid
  *
  * \note The caller is responsible for freeing the result with regfree().
  */
 int
 attrd_failure_regex(regex_t *regex, const char *rsc, const char *op,
                     guint interval_ms)
 {
     char *pattern = NULL;
     int rc;
 
     /* Create a pattern that matches desired attributes */
 
     if (rsc == NULL) {
         pattern = strdup(ATTRD_RE_CLEAR_ALL);
     } else if (op == NULL) {
         pattern = crm_strdup_printf(ATTRD_RE_CLEAR_ONE, rsc);
     } else {
         pattern = crm_strdup_printf(ATTRD_RE_CLEAR_OP, rsc, op, interval_ms);
     }
 
     /* Compile pattern into regular expression */
     crm_trace("Clearing attributes matching %s", pattern);
     rc = regcomp(regex, pattern, REG_EXTENDED|REG_NOSUB);
     free(pattern);
 
     return (rc == 0)? pcmk_ok : -EINVAL;
 }
 
 void
 attrd_free_attribute_value(gpointer data)
 {
     attribute_value_t *v = data;
 
     free(v->nodename);
     free(v->current);
     free(v->requested);
     free(v);
 }
 
 void
 attrd_free_attribute(gpointer data)
 {
     attribute_t *a = data;
     if(a) {
         free(a->id);
         free(a->set_id);
         free(a->set_type);
         free(a->user);
 
         mainloop_timer_del(a->timer);
         g_hash_table_destroy(a->values);
 
         free(a);
     }
 }
 
 /*!
  * \internal
  * \brief When a peer node leaves the cluster, stop tracking its protocol version.
  *
  * \param[in] host  The peer node's uname to be removed
  */
 void
 attrd_remove_peer_protocol_ver(const char *host)
 {
     if (peer_protocol_vers != NULL) {
         g_hash_table_remove(peer_protocol_vers, host);
     }
 }
 
 /*!
  * \internal
  * \brief When a peer node broadcasts a message with its protocol version, keep
  *        track of that information.
  *
  * We keep track of each peer's protocol version so we know which peers to
  * expect confirmation messages from when handling cluster-wide sync points.
  * We additionally keep track of the lowest protocol version supported by all
  * peers so we know when we can send IPC messages containing more than one
  * request.
  *
  * \param[in] host  The peer node's uname to be tracked
  * \param[in] value The peer node's protocol version
  */
 void
 attrd_update_minimum_protocol_ver(const char *host, const char *value)
 {
     int ver;
 
     if (peer_protocol_vers == NULL) {
         peer_protocol_vers = pcmk__strkey_table(free, NULL);
     }
 
     pcmk__scan_min_int(value, &ver, 0);
 
     if (ver > 0) {
         char *host_name = strdup(host);
 
         /* Record the peer attrd's protocol version. */
         CRM_ASSERT(host_name != NULL);
         g_hash_table_insert(peer_protocol_vers, host_name, GINT_TO_POINTER(ver));
 
         /* If the protocol version is a new minimum, record it as such. */
         if (minimum_protocol_version == -1 || ver < minimum_protocol_version) {
             minimum_protocol_version = ver;
             crm_trace("Set minimum attrd protocol version to %d",
                       minimum_protocol_version);
         }
     }
 }
 
 void
 attrd_copy_xml_attributes(xmlNode *src, xmlNode *dest)
 {
     /* Copy attributes from the wrapper parent node into the child node.
      * We can't just use copy_in_properties because we want to skip any
      * attributes that are already set on the child.  For instance, if
      * we were told to use a specific node, there will already be a node
      * attribute on the child.  Copying the parent's node attribute over
      * could result in the wrong value.
      */
     for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
         const char *p_name = (const char *) a->name;
         const char *p_value = ((a == NULL) || (a->children == NULL)) ? NULL :
                               (const char *) a->children->content;
 
         if (crm_element_value(dest, p_name) == NULL) {
             crm_xml_add(dest, p_name, p_value);
         }
     }
 }
diff --git a/daemons/controld/controld_callbacks.c b/daemons/controld/controld_callbacks.c
index fdd44c555b..81f5b13137 100644
--- a/daemons/controld/controld_callbacks.c
+++ b/daemons/controld/controld_callbacks.c
@@ -1,381 +1,381 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <string.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/cluster.h>
 #include <crm/cib.h>
 
 #include <pacemaker-controld.h>
 
 /* From join_dc... */
 extern gboolean check_join_state(enum crmd_fsa_state cur_state, const char *source);
 
 void
 crmd_ha_msg_filter(xmlNode * msg)
 {
     if (AM_I_DC) {
         const char *sys_from = crm_element_value(msg, PCMK__XA_CRM_SYS_FROM);
 
         if (pcmk__str_eq(sys_from, CRM_SYSTEM_DC, pcmk__str_casei)) {
             const char *from = crm_element_value(msg, PCMK__XA_SRC);
 
             if (!pcmk__str_eq(from, controld_globals.our_nodename,
                               pcmk__str_casei)) {
                 int level = LOG_INFO;
                 const char *op = crm_element_value(msg, PCMK__XA_CRM_TASK);
 
                 /* make sure the election happens NOW */
                 if (controld_globals.fsa_state != S_ELECTION) {
                     ha_msg_input_t new_input;
 
                     level = LOG_WARNING;
                     new_input.msg = msg;
                     register_fsa_error_adv(C_FSA_INTERNAL, I_ELECTION, NULL, &new_input,
                                            __func__);
                 }
 
                 do_crm_log(level, "Another DC detected: %s (op=%s)", from, op);
                 goto done;
             }
         }
 
     } else {
         const char *sys_to = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
 
         if (pcmk__str_eq(sys_to, CRM_SYSTEM_DC, pcmk__str_casei)) {
             return;
         }
     }
 
     /* crm_log_xml_trace(msg, "HA[inbound]"); */
     route_message(C_HA_MESSAGE, msg);
 
   done:
     controld_trigger_fsa();
 }
 
 /*!
  * \internal
  * \brief Check whether a node is online
  *
  * \param[in] node  Node to check
  *
  * \retval -1 if completely dead
  * \retval  0 if partially alive
  * \retval  1 if completely alive
  */
 static int
 node_alive(const crm_node_t *node)
 {
     if (pcmk_is_set(node->flags, crm_remote_node)) {
         // Pacemaker Remote nodes can't be partially alive
         return pcmk__str_eq(node->state, CRM_NODE_MEMBER, pcmk__str_casei) ? 1: -1;
 
     } else if (crm_is_peer_active(node)) {
         // Completely up cluster node: both cluster member and peer
         return 1;
 
     } else if (!pcmk_is_set(node->processes, crm_get_cluster_proc())
                && !pcmk__str_eq(node->state, CRM_NODE_MEMBER, pcmk__str_casei)) {
         // Completely down cluster node: neither cluster member nor peer
         return -1;
     }
 
     // Partially up cluster node: only cluster member or only peer
     return 0;
 }
 
 #define state_text(state) ((state)? (const char *)(state) : "in unknown state")
 
 void
 peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data)
 {
     uint32_t old = 0;
     bool appeared = FALSE;
     bool is_remote = pcmk_is_set(node->flags, crm_remote_node);
 
     controld_node_pending_timer(node);
 
     /* The controller waits to receive some information from the membership
      * layer before declaring itself operational. If this is being called for a
      * cluster node, indicate that we have it.
      */
     if (!is_remote) {
         controld_set_fsa_input_flags(R_PEER_DATA);
     }
 
     if (type == crm_status_processes
         && pcmk_is_set(node->processes, crm_get_cluster_proc())
         && !AM_I_DC
         && !is_remote) {
         /*
          * This is a hack until we can send to a nodeid and/or we fix node name lookups
          * These messages are ignored in crmd_ha_msg_filter()
          */
         xmlNode *query = create_request(CRM_OP_HELLO, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
 
         crm_debug("Sending hello to node %u so that it learns our node name", node->id);
         send_cluster_message(node, crm_msg_crmd, query, FALSE);
 
         free_xml(query);
     }
 
     if (node->uname == NULL) {
         return;
     }
 
     switch (type) {
         case crm_status_uname:
             /* If we've never seen the node, then it also won't be in the status section */
             crm_info("%s node %s is now %s",
                      (is_remote? "Remote" : "Cluster"),
                      node->uname, state_text(node->state));
             return;
 
         case crm_status_nstate:
             /* This callback should not be called unless the state actually
              * changed, but here's a failsafe just in case.
              */
             CRM_CHECK(!pcmk__str_eq(data, node->state, pcmk__str_casei),
                       return);
 
             crm_info("%s node %s is now %s (was %s)",
                      (is_remote? "Remote" : "Cluster"),
                      node->uname, state_text(node->state), state_text(data));
 
             if (pcmk__str_eq(CRM_NODE_MEMBER, node->state, pcmk__str_casei)) {
                 appeared = TRUE;
                 if (!is_remote) {
                     remove_stonith_cleanup(node->uname);
                 }
             } else {
                 controld_remove_failed_sync_node(node->uname);
                 controld_remove_voter(node->uname);
             }
 
             crmd_alert_node_event(node);
             break;
 
         case crm_status_processes:
             CRM_CHECK(data != NULL, return);
             old = *(const uint32_t *)data;
             appeared = pcmk_is_set(node->processes, crm_get_cluster_proc());
 
             {
                 const char *dc_s = controld_globals.dc_name;
 
                 if ((dc_s == NULL) && AM_I_DC) {
                     dc_s = PCMK_VALUE_TRUE;
                 }
 
                 crm_info("Node %s is %s a peer " CRM_XS
                          " DC=%s old=%#07x new=%#07x",
                          node->uname, (appeared? "now" : "no longer"),
                          pcmk__s(dc_s, "<none>"), old, node->processes);
             }
 
             if (!pcmk_is_set((node->processes ^ old), crm_get_cluster_proc())) {
                 /* Peer status did not change. This should not be possible,
                  * since we don't track process flags other than peer status.
                  */
                 crm_trace("Process flag %#7x did not change from %#7x to %#7x",
                           crm_get_cluster_proc(), old, node->processes);
                 return;
 
             }
 
             if (!appeared) {
                 node->peer_lost = time(NULL);
                 controld_remove_failed_sync_node(node->uname);
                 controld_remove_voter(node->uname);
             }
 
             if (!pcmk_is_set(controld_globals.fsa_input_register,
                              R_CIB_CONNECTED)) {
                 crm_trace("Ignoring peer status change because not connected to CIB");
                 return;
 
             } else if (controld_globals.fsa_state == S_STOPPING) {
                 crm_trace("Ignoring peer status change because stopping");
                 return;
             }
 
             if (!appeared
                 && pcmk__str_eq(node->uname, controld_globals.our_nodename,
                                 pcmk__str_casei)) {
                 /* Did we get evicted? */
                 crm_notice("Our peer connection failed");
                 register_fsa_input(C_CRMD_STATUS_CALLBACK, I_ERROR, NULL);
 
             } else if (pcmk__str_eq(node->uname, controld_globals.dc_name,
                                     pcmk__str_casei)
                        && !crm_is_peer_active(node)) {
                 /* Did the DC leave us? */
                 crm_notice("Our peer on the DC (%s) is dead",
                            controld_globals.dc_name);
                 register_fsa_input(C_CRMD_STATUS_CALLBACK, I_ELECTION, NULL);
 
                 /* @COMPAT DC < 1.1.13: If a DC shuts down normally, we don't
                  * want to fence it. Newer DCs will send their shutdown request
                  * to all peers, who will update the DC's expected state to
                  * down, thus avoiding fencing. We can safely erase the DC's
                  * transient attributes when it leaves in that case. However,
                  * the only way to avoid fencing older DCs is to leave the
                  * transient attributes intact until it rejoins.
                  */
                 if (compare_version(controld_globals.dc_version, "3.0.9") > 0) {
                     controld_delete_node_state(node->uname,
                                                controld_section_attrs,
                                                cib_scope_local);
                 }
 
             } else if (AM_I_DC
                        || pcmk_is_set(controld_globals.flags, controld_dc_left)
                        || (controld_globals.dc_name == NULL)) {
                 /* This only needs to be done once, so normally the DC should do
                  * it. However if there is no DC, every node must do it, since
                  * there is no other way to ensure some one node does it.
                  */
                 if (appeared) {
                     te_trigger_stonith_history_sync(FALSE);
                 } else {
                     controld_delete_node_state(node->uname,
                                                controld_section_attrs,
                                                cib_scope_local);
                 }
             }
             break;
     }
 
     if (AM_I_DC) {
         xmlNode *update = NULL;
         int flags = node_update_peer;
         int alive = node_alive(node);
         pcmk__graph_action_t *down = match_down_event(node->uuid);
 
         crm_trace("Alive=%d, appeared=%d, down=%d",
                   alive, appeared, (down? down->id : -1));
 
         if (appeared && (alive > 0) && !is_remote) {
             register_fsa_input_before(C_FSA_INTERNAL, I_NODE_JOIN, NULL);
         }
 
         if (down) {
             const char *task = crm_element_value(down->xml, PCMK_XA_OPERATION);
 
             if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_casei)) {
 
                 /* tengine_stonith_callback() confirms fence actions */
                 crm_trace("Updating CIB %s fencer reported fencing of %s complete",
                           (pcmk_is_set(down->flags, pcmk__graph_action_confirmed)? "after" : "before"), node->uname);
 
             } else if (!appeared && pcmk__str_eq(task, PCMK_ACTION_DO_SHUTDOWN,
                                                  pcmk__str_casei)) {
 
                 // Shutdown actions are immediately confirmed (i.e. no_wait)
                 if (!is_remote) {
                     flags |= node_update_join | node_update_expected;
                     crmd_peer_down(node, FALSE);
                     check_join_state(controld_globals.fsa_state, __func__);
                 }
                 if (alive >= 0) {
                     crm_info("%s of peer %s is in progress " CRM_XS " action=%d",
                              task, node->uname, down->id);
                 } else {
                     crm_notice("%s of peer %s is complete " CRM_XS " action=%d",
                                task, node->uname, down->id);
                     pcmk__update_graph(controld_globals.transition_graph, down);
                     trigger_graph();
                 }
 
             } else {
                 crm_trace("Node %s is %s, was expected to %s (op %d)",
                           node->uname,
                           ((alive > 0)? "alive" :
                            ((alive < 0)? "dead" : "partially alive")),
                           task, down->id);
             }
 
         } else if (appeared == FALSE) {
             if ((controld_globals.transition_graph == NULL)
                 || (controld_globals.transition_graph->id == -1)) {
                 crm_info("Stonith/shutdown of node %s is unknown to the "
                          "current DC", node->uname);
             } else {
                 crm_warn("Stonith/shutdown of node %s was not expected",
                          node->uname);
             }
             if (!is_remote) {
                 crm_update_peer_join(__func__, node, crm_join_none);
                 check_join_state(controld_globals.fsa_state, __func__);
             }
-            abort_transition(INFINITY, pcmk__graph_restart, "Node failure",
-                             NULL);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Node failure", NULL);
             fail_incompletable_actions(controld_globals.transition_graph,
                                        node->uuid);
 
         } else {
             crm_trace("Node %s came up, was not expected to be down",
                       node->uname);
         }
 
         if (is_remote) {
             /* A pacemaker_remote node won't have its cluster status updated
              * in the CIB by membership-layer callbacks, so do it here.
              */
             flags |= node_update_cluster;
 
             /* Trigger resource placement on newly integrated nodes */
             if (appeared) {
-                abort_transition(INFINITY, pcmk__graph_restart,
+                abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                                  "Pacemaker Remote node integrated", NULL);
             }
         }
 
         if (!appeared && (type == crm_status_processes)
             && (node->when_member > 1)) {
             /* The node left CPG but is still a cluster member. Set its
              * membership time to 1 to record it in the cluster state as a
              * boolean, so we don't fence it due to
              * PCMK_OPT_NODE_PENDING_TIMEOUT.
              */
             node->when_member = 1;
             flags |= node_update_cluster;
             controld_node_pending_timer(node);
         }
 
         /* Update the CIB node state */
         update = create_node_state_update(node, flags, NULL, __func__);
         if (update == NULL) {
             crm_debug("Node state update not yet possible for %s", node->uname);
         } else {
             fsa_cib_anon_update(PCMK_XE_STATUS, update);
         }
         free_xml(update);
     }
 
     controld_trigger_fsa();
 }
 
 gboolean
 crm_fsa_trigger(gpointer user_data)
 {
     crm_trace("Invoked (queue len: %d)",
               g_list_length(controld_globals.fsa_message_queue));
     s_crmd_fsa(C_FSA_INTERNAL);
     crm_trace("Exited  (queue len: %d)",
               g_list_length(controld_globals.fsa_message_queue));
     return TRUE;
 }
diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c
index e27ebdb236..6b743e28f6 100644
--- a/daemons/controld/controld_fencing.c
+++ b/daemons/controld/controld_fencing.c
@@ -1,1116 +1,1117 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/stonith-ng.h>
 #include <crm/fencing/internal.h>
 
 #include <pacemaker-controld.h>
 
 static void
 tengine_stonith_history_synced(stonith_t *st, stonith_event_t *st_event);
 
 /*
  * stonith failure counting
  *
  * We don't want to get stuck in a permanent fencing loop. Keep track of the
  * number of fencing failures for each target node, and the most we'll restart a
  * transition for.
  */
 
 struct st_fail_rec {
     int count;
 };
 
 static bool fence_reaction_panic = false;
 static unsigned long int stonith_max_attempts = 10;
 static GHashTable *stonith_failures = NULL;
 
 /*!
  * \internal
  * \brief Update max fencing attempts before giving up
  *
  * \param[in] value  New max fencing attempts
  */
 static void
 update_stonith_max_attempts(const char *value)
 {
     stonith_max_attempts = char2score(value);
     if (stonith_max_attempts < 1UL) {
         stonith_max_attempts = 10UL;
     }
 }
 
 /*!
  * \internal
  * \brief Configure reaction to notification of local node being fenced
  *
  * \param[in] reaction_s  Reaction type
  */
 static void
 set_fence_reaction(const char *reaction_s)
 {
     if (pcmk__str_eq(reaction_s, "panic", pcmk__str_casei)) {
         fence_reaction_panic = true;
 
     } else {
         if (!pcmk__str_eq(reaction_s, PCMK_VALUE_STOP, pcmk__str_casei)) {
             crm_warn("Invalid value '%s' for %s, using 'stop'",
                      reaction_s, PCMK_OPT_FENCE_REACTION);
         }
         fence_reaction_panic = false;
     }
 }
 
 /*!
  * \internal
  * \brief Configure fencing options based on the CIB
  *
  * \param[in,out] options  Name/value pairs for configured options
  */
 void
 controld_configure_fencing(GHashTable *options)
 {
     const char *value = NULL;
 
     value = g_hash_table_lookup(options, PCMK_OPT_FENCE_REACTION);
     set_fence_reaction(value);
 
     value = g_hash_table_lookup(options, PCMK_OPT_STONITH_MAX_ATTEMPTS);
     update_stonith_max_attempts(value);
 }
 
 static gboolean
 too_many_st_failures(const char *target)
 {
     GHashTableIter iter;
     const char *key = NULL;
     struct st_fail_rec *value = NULL;
 
     if (stonith_failures == NULL) {
         return FALSE;
     }
 
     if (target == NULL) {
         g_hash_table_iter_init(&iter, stonith_failures);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key,
                (gpointer *) &value)) {
 
             if (value->count >= stonith_max_attempts) {
                 target = (const char*)key;
                 goto too_many;
             }
         }
     } else {
         value = g_hash_table_lookup(stonith_failures, target);
         if ((value != NULL) && (value->count >= stonith_max_attempts)) {
             goto too_many;
         }
     }
     return FALSE;
 
 too_many:
     crm_warn("Too many failures (%d) to fence %s, giving up",
              value->count, target);
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Reset a stonith fail count
  *
  * \param[in] target  Name of node to reset, or NULL for all
  */
 void
 st_fail_count_reset(const char *target)
 {
     if (stonith_failures == NULL) {
         return;
     }
 
     if (target) {
         struct st_fail_rec *rec = NULL;
 
         rec = g_hash_table_lookup(stonith_failures, target);
         if (rec) {
             rec->count = 0;
         }
     } else {
         GHashTableIter iter;
         const char *key = NULL;
         struct st_fail_rec *rec = NULL;
 
         g_hash_table_iter_init(&iter, stonith_failures);
         while (g_hash_table_iter_next(&iter, (gpointer *) &key,
                                       (gpointer *) &rec)) {
             rec->count = 0;
         }
     }
 }
 
 static void
 st_fail_count_increment(const char *target)
 {
     struct st_fail_rec *rec = NULL;
 
     if (stonith_failures == NULL) {
         stonith_failures = pcmk__strkey_table(free, free);
     }
 
     rec = g_hash_table_lookup(stonith_failures, target);
     if (rec) {
         rec->count++;
     } else {
         rec = malloc(sizeof(struct st_fail_rec));
         if(rec == NULL) {
             return;
         }
 
         rec->count = 1;
         g_hash_table_insert(stonith_failures, strdup(target), rec);
     }
 }
 
 /* end stonith fail count functions */
 
 
 static void
 cib_fencing_updated(xmlNode *msg, int call_id, int rc, xmlNode *output,
                     void *user_data)
 {
     if (rc < pcmk_ok) {
         crm_err("Fencing update %d for %s: failed - %s (%d)",
                 call_id, (char *)user_data, pcmk_strerror(rc), rc);
         crm_log_xml_warn(msg, "Failed update");
-        abort_transition(INFINITY, pcmk__graph_shutdown, "CIB update failed",
-                         NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_shutdown,
+                         "CIB update failed", NULL);
 
     } else {
         crm_info("Fencing update %d for %s: complete", call_id, (char *)user_data);
     }
 }
 
 static void
 send_stonith_update(pcmk__graph_action_t *action, const char *target,
                     const char *uuid)
 {
     int rc = pcmk_ok;
     crm_node_t *peer = NULL;
 
     /* We (usually) rely on the membership layer to do node_update_cluster,
      * and the peer status callback to do node_update_peer, because the node
      * might have already rejoined before we get the stonith result here.
      */
     int flags = node_update_join | node_update_expected;
 
     /* zero out the node-status & remove all LRM status info */
     xmlNode *node_state = NULL;
 
     CRM_CHECK(target != NULL, return);
     CRM_CHECK(uuid != NULL, return);
 
     /* Make sure the membership and join caches are accurate.
      * Try getting any existing node cache entry also by node uuid in case it
      * doesn't have an uname yet.
      */
     peer = pcmk__get_node(0, target, uuid, pcmk__node_search_any);
 
     CRM_CHECK(peer != NULL, return);
 
     if (peer->state == NULL) {
         /* Usually, we rely on the membership layer to update the cluster state
          * in the CIB. However, if the node has never been seen, do it here, so
          * the node is not considered unclean.
          */
         flags |= node_update_cluster;
     }
 
     if (peer->uuid == NULL) {
         crm_info("Recording uuid '%s' for node '%s'", uuid, target);
         peer->uuid = strdup(uuid);
     }
 
     crmd_peer_down(peer, TRUE);
 
     /* Generate a node state update for the CIB */
     node_state = create_node_state_update(peer, flags, NULL, __func__);
 
     /* we have to mark whether or not remote nodes have already been fenced */
     if (peer->flags & crm_remote_node) {
         char *now_s = pcmk__ttoa(time(NULL));
 
         crm_xml_add(node_state, PCMK__XA_NODE_FENCED, now_s);
         free(now_s);
     }
 
     /* Force our known ID */
     crm_xml_add(node_state, PCMK_XA_ID, uuid);
 
     rc = controld_globals.cib_conn->cmds->modify(controld_globals.cib_conn,
                                                  PCMK_XE_STATUS, node_state,
                                                  cib_scope_local
                                                  |cib_can_create);
 
     /* Delay processing the trigger until the update completes */
     crm_debug("Sending fencing update %d for %s", rc, target);
     fsa_register_cib_callback(rc, strdup(target), cib_fencing_updated);
 
     // Make sure it sticks
     /* controld_globals.cib_conn->cmds->bump_epoch(controld_globals.cib_conn,
      *                                             cib_scope_local);
      */
 
     controld_delete_node_state(peer->uname, controld_section_all,
                                cib_scope_local);
     free_xml(node_state);
     return;
 }
 
 /*!
  * \internal
  * \brief Abort transition due to stonith failure
  *
  * \param[in] abort_action  Whether to restart or stop transition
  * \param[in] target  Don't restart if this (NULL for any) has too many failures
  * \param[in] reason  Log this stonith action XML as abort reason (or NULL)
  */
 static void
 abort_for_stonith_failure(enum pcmk__graph_next abort_action,
                           const char *target, const xmlNode *reason)
 {
     /* If stonith repeatedly fails, we eventually give up on starting a new
      * transition for that reason.
      */
     if ((abort_action != pcmk__graph_wait) && too_many_st_failures(target)) {
         abort_action = pcmk__graph_wait;
     }
-    abort_transition(INFINITY, abort_action, "Stonith failed", reason);
+    abort_transition(PCMK_SCORE_INFINITY, abort_action, "Stonith failed",
+                     reason);
 }
 
 
 /*
  * stonith cleanup list
  *
  * If the DC is shot, proper notifications might not go out.
  * The stonith cleanup list allows the cluster to (re-)send
  * notifications once a new DC is elected.
  */
 
 static GList *stonith_cleanup_list = NULL;
 
 /*!
  * \internal
  * \brief Add a node to the stonith cleanup list
  *
  * \param[in] target  Name of node to add
  */
 void
 add_stonith_cleanup(const char *target) {
     stonith_cleanup_list = g_list_append(stonith_cleanup_list, strdup(target));
 }
 
 /*!
  * \internal
  * \brief Remove a node from the stonith cleanup list
  *
  * \param[in] Name of node to remove
  */
 void
 remove_stonith_cleanup(const char *target)
 {
     GList *iter = stonith_cleanup_list;
 
     while (iter != NULL) {
         GList *tmp = iter;
         char *iter_name = tmp->data;
 
         iter = iter->next;
         if (pcmk__str_eq(target, iter_name, pcmk__str_casei)) {
             crm_trace("Removing %s from the cleanup list", iter_name);
             stonith_cleanup_list = g_list_delete_link(stonith_cleanup_list, tmp);
             free(iter_name);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Purge all entries from the stonith cleanup list
  */
 void
 purge_stonith_cleanup(void)
 {
     if (stonith_cleanup_list) {
         GList *iter = NULL;
 
         for (iter = stonith_cleanup_list; iter != NULL; iter = iter->next) {
             char *target = iter->data;
 
             crm_info("Purging %s from stonith cleanup list", target);
             free(target);
         }
         g_list_free(stonith_cleanup_list);
         stonith_cleanup_list = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Send stonith updates for all entries in cleanup list, then purge it
  */
 void
 execute_stonith_cleanup(void)
 {
     GList *iter;
 
     for (iter = stonith_cleanup_list; iter != NULL; iter = iter->next) {
         char *target = iter->data;
         crm_node_t *target_node = pcmk__get_node(0, target, NULL,
                                                  pcmk__node_search_cluster);
         const char *uuid = crm_peer_uuid(target_node);
 
         crm_notice("Marking %s, target of a previous stonith action, as clean", target);
         send_stonith_update(NULL, target, uuid);
         free(target);
     }
     g_list_free(stonith_cleanup_list);
     stonith_cleanup_list = NULL;
 }
 
 /* end stonith cleanup list functions */
 
 
 /* stonith API client
  *
  * Functions that need to interact directly with the fencer via its API
  */
 
 static stonith_t *stonith_api = NULL;
 static mainloop_timer_t *controld_fencer_connect_timer = NULL;
 static char *te_client_id = NULL;
 
 static gboolean
 fail_incompletable_stonith(pcmk__graph_t *graph)
 {
     GList *lpc = NULL;
     const char *task = NULL;
     xmlNode *last_action = NULL;
 
     if (graph == NULL) {
         return FALSE;
     }
 
     for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         GList *lpc2 = NULL;
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
             continue;
         }
 
         for (lpc2 = synapse->actions; lpc2 != NULL; lpc2 = lpc2->next) {
             pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc2->data;
 
             if ((action->type != pcmk__cluster_graph_action)
                 || pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
                 continue;
             }
 
             task = crm_element_value(action->xml, PCMK_XA_OPERATION);
             if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_casei)) {
                 pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
                 last_action = action->xml;
                 pcmk__update_graph(graph, action);
                 crm_notice("Failing action %d (%s): fencer terminated",
                            action->id, pcmk__xe_id(action->xml));
             }
         }
     }
 
     if (last_action != NULL) {
         crm_warn("Fencer failure resulted in unrunnable actions");
         abort_for_stonith_failure(pcmk__graph_restart, NULL, last_action);
         return TRUE;
     }
 
     return FALSE;
 }
 
 static void
 tengine_stonith_connection_destroy(stonith_t *st, stonith_event_t *e)
 {
     te_cleanup_stonith_history_sync(st, FALSE);
 
     if (pcmk_is_set(controld_globals.fsa_input_register, R_ST_REQUIRED)) {
         crm_err("Lost fencer connection (will attempt to reconnect)");
         if (!mainloop_timer_running(controld_fencer_connect_timer)) {
             mainloop_timer_start(controld_fencer_connect_timer);
         }
     } else {
         crm_info("Disconnected from fencer");
     }
 
     if (stonith_api) {
         /* the client API won't properly reconnect notifications
          * if they are still in the table - so remove them
          */
         if (stonith_api->state != stonith_disconnected) {
             stonith_api->cmds->disconnect(st);
         }
         stonith_api->cmds->remove_notification(stonith_api, NULL);
     }
 
     if (AM_I_DC) {
         fail_incompletable_stonith(controld_globals.transition_graph);
         trigger_graph();
     }
 }
 
 /*!
  * \internal
  * \brief Handle an event notification from the fencing API
  *
  * \param[in] st     Fencing API connection (ignored)
  * \param[in] event  Fencing API event notification
  */
 static void
 handle_fence_notification(stonith_t *st, stonith_event_t *event)
 {
     bool succeeded = true;
     const char *executioner = "the cluster";
     const char *client = "a client";
     const char *reason = NULL;
     int exec_status;
 
     if (te_client_id == NULL) {
         te_client_id = crm_strdup_printf("%s.%lu", crm_system_name,
                                          (unsigned long) getpid());
     }
 
     if (event == NULL) {
         crm_err("Notify data not found");
         return;
     }
 
     if (event->executioner != NULL) {
         executioner = event->executioner;
     }
     if (event->client_origin != NULL) {
         client = event->client_origin;
     }
 
     exec_status = stonith__event_execution_status(event);
     if ((stonith__event_exit_status(event) != CRM_EX_OK)
         || (exec_status != PCMK_EXEC_DONE)) {
         succeeded = false;
         if (exec_status == PCMK_EXEC_DONE) {
             exec_status = PCMK_EXEC_ERROR;
         }
     }
     reason = stonith__event_exit_reason(event);
 
     crmd_alert_fencing_op(event);
 
     if (pcmk__str_eq(PCMK_ACTION_ON, event->action, pcmk__str_none)) {
         // Unfencing doesn't need special handling, just a log message
         if (succeeded) {
             crm_notice("%s was unfenced by %s at the request of %s@%s",
                        event->target, executioner, client, event->origin);
         } else {
             crm_err("Unfencing of %s by %s failed (%s%s%s) with exit status %d",
                     event->target, executioner,
                     pcmk_exec_status_str(exec_status),
                     ((reason == NULL)? "" : ": "),
                     ((reason == NULL)? "" : reason),
                     stonith__event_exit_status(event));
         }
         return;
     }
 
     if (succeeded
         && pcmk__str_eq(event->target, controld_globals.our_nodename,
                         pcmk__str_casei)) {
         /* We were notified of our own fencing. Most likely, either fencing was
          * misconfigured, or fabric fencing that doesn't cut cluster
          * communication is in use.
          *
          * Either way, shutting down the local host is a good idea, to require
          * administrator intervention. Also, other nodes would otherwise likely
          * set our status to lost because of the fencing callback and discard
          * our subsequent election votes as "not part of our cluster".
          */
         crm_crit("We were allegedly just fenced by %s for %s!",
                  executioner, event->origin); // Dumps blackbox if enabled
         if (fence_reaction_panic) {
             pcmk__panic(__func__);
         } else {
             crm_exit(CRM_EX_FATAL);
         }
         return; // Should never get here
     }
 
     /* Update the count of fencing failures for this target, in case we become
      * DC later. The current DC has already updated its fail count in
      * tengine_stonith_callback().
      */
     if (!AM_I_DC) {
         if (succeeded) {
             st_fail_count_reset(event->target);
         } else {
             st_fail_count_increment(event->target);
         }
     }
 
     crm_notice("Peer %s was%s terminated (%s) by %s on behalf of %s@%s: "
                "%s%s%s%s " CRM_XS " event=%s",
                event->target, (succeeded? "" : " not"),
                event->action, executioner, client, event->origin,
                (succeeded? "OK" : pcmk_exec_status_str(exec_status)),
                ((reason == NULL)? "" : " ("),
                ((reason == NULL)? "" : reason),
                ((reason == NULL)? "" : ")"),
                event->id);
 
     if (succeeded) {
         crm_node_t *peer = pcmk__search_node_caches(0, event->target,
                                                     pcmk__node_search_any
                                                     |pcmk__node_search_known);
         const char *uuid = NULL;
 
         if (peer == NULL) {
             return;
         }
 
         uuid = crm_peer_uuid(peer);
 
         if (AM_I_DC) {
             /* The DC always sends updates */
             send_stonith_update(NULL, event->target, uuid);
 
             /* @TODO Ideally, at this point, we'd check whether the fenced node
              * hosted any guest nodes, and call remote_node_down() for them.
              * Unfortunately, the controller doesn't have a simple, reliable way
              * to map hosts to guests. It might be possible to track this in the
              * peer cache via crm_remote_peer_cache_refresh(). For now, we rely
              * on the scheduler creating fence pseudo-events for the guests.
              */
 
             if (!pcmk__str_eq(client, te_client_id, pcmk__str_casei)) {
                 /* Abort the current transition if it wasn't the cluster that
                  * initiated fencing.
                  */
                 crm_info("External fencing operation from %s fenced %s",
                          client, event->target);
-                abort_transition(INFINITY, pcmk__graph_restart,
+                abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                                  "External Fencing Operation", NULL);
             }
 
         } else if (pcmk__str_eq(controld_globals.dc_name, event->target,
                                 pcmk__str_null_matches|pcmk__str_casei)
                    && !pcmk_is_set(peer->flags, crm_remote_node)) {
             // Assume the target was our DC if we don't currently have one
 
             if (controld_globals.dc_name != NULL) {
                 crm_notice("Fencing target %s was our DC", event->target);
             } else {
                 crm_notice("Fencing target %s may have been our DC",
                            event->target);
             }
 
             /* Given the CIB resyncing that occurs around elections,
              * have one node update the CIB now and, if the new DC is different,
              * have them do so too after the election
              */
             if (pcmk__str_eq(event->executioner, controld_globals.our_nodename,
                              pcmk__str_casei)) {
                 send_stonith_update(NULL, event->target, uuid);
             }
             add_stonith_cleanup(event->target);
         }
 
         /* If the target is a remote node, and we host its connection,
          * immediately fail all monitors so it can be recovered quickly.
          * The connection won't necessarily drop when a remote node is fenced,
          * so the failure might not otherwise be detected until the next poke.
          */
         if (pcmk_is_set(peer->flags, crm_remote_node)) {
             remote_ra_fail(event->target);
         }
 
         crmd_peer_down(peer, TRUE);
      }
 }
 
 /*!
  * \brief Connect to fencer
  *
  * \param[in] user_data  If NULL, retry failures now, otherwise retry in mainloop timer
  *
  * \return G_SOURCE_REMOVE on success, G_SOURCE_CONTINUE to retry
  * \note If user_data is NULL, this will wait 2s between attempts, for up to
  *       30 attempts, meaning the controller could be blocked as long as 58s.
  */
 gboolean
 controld_timer_fencer_connect(gpointer user_data)
 {
     int rc = pcmk_ok;
 
     if (stonith_api == NULL) {
         stonith_api = stonith_api_new();
         if (stonith_api == NULL) {
             crm_err("Could not connect to fencer: API memory allocation failed");
             return G_SOURCE_REMOVE;
         }
     }
 
     if (stonith_api->state != stonith_disconnected) {
         crm_trace("Already connected to fencer, no need to retry");
         return G_SOURCE_REMOVE;
     }
 
     if (user_data == NULL) {
         // Blocking (retry failures now until successful)
         rc = stonith_api_connect_retry(stonith_api, crm_system_name, 30);
         if (rc != pcmk_ok) {
             crm_err("Could not connect to fencer in 30 attempts: %s "
                     CRM_XS " rc=%d", pcmk_strerror(rc), rc);
         }
     } else {
         // Non-blocking (retry failures later in main loop)
         rc = stonith_api->cmds->connect(stonith_api, crm_system_name, NULL);
 
         if (controld_fencer_connect_timer == NULL) {
             controld_fencer_connect_timer =
                 mainloop_timer_add("controld_fencer_connect", 1000,
                                    TRUE, controld_timer_fencer_connect,
                                    GINT_TO_POINTER(TRUE));
         }
 
         if (rc != pcmk_ok) {
             if (pcmk_is_set(controld_globals.fsa_input_register,
                             R_ST_REQUIRED)) {
                 crm_notice("Fencer connection failed (will retry): %s "
                            CRM_XS " rc=%d", pcmk_strerror(rc), rc);
 
                 if (!mainloop_timer_running(controld_fencer_connect_timer)) {
                     mainloop_timer_start(controld_fencer_connect_timer);
                 }
 
                 return G_SOURCE_CONTINUE;
             } else {
                 crm_info("Fencer connection failed (ignoring because no longer required): %s "
                          CRM_XS " rc=%d", pcmk_strerror(rc), rc);
             }
             return G_SOURCE_REMOVE;
         }
     }
 
     if (rc == pcmk_ok) {
         stonith_api->cmds->register_notification(stonith_api,
                                                  T_STONITH_NOTIFY_DISCONNECT,
                                                  tengine_stonith_connection_destroy);
         stonith_api->cmds->register_notification(stonith_api,
                                                  T_STONITH_NOTIFY_FENCE,
                                                  handle_fence_notification);
         stonith_api->cmds->register_notification(stonith_api,
                                                  T_STONITH_NOTIFY_HISTORY_SYNCED,
                                                  tengine_stonith_history_synced);
         te_trigger_stonith_history_sync(TRUE);
         crm_notice("Fencer successfully connected");
     }
 
     return G_SOURCE_REMOVE;
 }
 
 void
 controld_disconnect_fencer(bool destroy)
 {
     if (stonith_api) {
         // Prevent fencer connection from coming up again
         controld_clear_fsa_input_flags(R_ST_REQUIRED);
 
         if (stonith_api->state != stonith_disconnected) {
             stonith_api->cmds->disconnect(stonith_api);
         }
         stonith_api->cmds->remove_notification(stonith_api, NULL);
     }
     if (destroy) {
         if (stonith_api) {
             stonith_api->cmds->free(stonith_api);
             stonith_api = NULL;
         }
         if (controld_fencer_connect_timer) {
             mainloop_timer_del(controld_fencer_connect_timer);
             controld_fencer_connect_timer = NULL;
         }
         if (te_client_id) {
             free(te_client_id);
             te_client_id = NULL;
         }
     }
 }
 
 static gboolean
 do_stonith_history_sync(gpointer user_data)
 {
     if (stonith_api && (stonith_api->state != stonith_disconnected)) {
         stonith_history_t *history = NULL;
 
         te_cleanup_stonith_history_sync(stonith_api, FALSE);
         stonith_api->cmds->history(stonith_api,
                                    st_opt_sync_call | st_opt_broadcast,
                                    NULL, &history, 5);
         stonith_history_free(history);
         return TRUE;
     } else {
         crm_info("Skip triggering stonith history-sync as stonith is disconnected");
         return FALSE;
     }
 }
 
 static void
 tengine_stonith_callback(stonith_t *stonith, stonith_callback_data_t *data)
 {
     char *uuid = NULL;
     int stonith_id = -1;
     int transition_id = -1;
     pcmk__graph_action_t *action = NULL;
     const char *target = NULL;
 
     if ((data == NULL) || (data->userdata == NULL)) {
         crm_err("Ignoring fence operation %d result: "
                 "No transition key given (bug?)",
                 ((data == NULL)? -1 : data->call_id));
         return;
     }
 
     if (!AM_I_DC) {
         const char *reason = stonith__exit_reason(data);
 
         if (reason == NULL) {
            reason = pcmk_exec_status_str(stonith__execution_status(data));
         }
         crm_notice("Result of fence operation %d: %d (%s) " CRM_XS " key=%s",
                    data->call_id, stonith__exit_status(data), reason,
                    (const char *) data->userdata);
         return;
     }
 
     CRM_CHECK(decode_transition_key(data->userdata, &uuid, &transition_id,
                                     &stonith_id, NULL),
               goto bail);
 
     if (controld_globals.transition_graph->complete || (stonith_id < 0)
         || !pcmk__str_eq(uuid, controld_globals.te_uuid, pcmk__str_none)
         || (controld_globals.transition_graph->id != transition_id)) {
         crm_info("Ignoring fence operation %d result: "
                  "Not from current transition " CRM_XS
                  " complete=%s action=%d uuid=%s (vs %s) transition=%d (vs %d)",
                  data->call_id,
                  pcmk__btoa(controld_globals.transition_graph->complete),
                  stonith_id, uuid, controld_globals.te_uuid, transition_id,
                  controld_globals.transition_graph->id);
         goto bail;
     }
 
     action = controld_get_action(stonith_id);
     if (action == NULL) {
         crm_err("Ignoring fence operation %d result: "
                 "Action %d not found in transition graph (bug?) "
                 CRM_XS " uuid=%s transition=%d",
                 data->call_id, stonith_id, uuid, transition_id);
         goto bail;
     }
 
     target = crm_element_value(action->xml, PCMK__META_ON_NODE);
     if (target == NULL) {
         crm_err("Ignoring fence operation %d result: No target given (bug?)",
                 data->call_id);
         goto bail;
     }
 
     stop_te_timer(action);
     if (stonith__exit_status(data) == CRM_EX_OK) {
         const char *uuid = crm_element_value(action->xml,
                                              PCMK__META_ON_NODE_UUID);
         const char *op = crm_meta_value(action->params,
                                         PCMK__META_STONITH_ACTION);
 
         crm_info("Fence operation %d for %s succeeded", data->call_id, target);
         if (!(pcmk_is_set(action->flags, pcmk__graph_action_confirmed))) {
             te_action_confirmed(action, NULL);
             if (pcmk__str_eq(PCMK_ACTION_ON, op, pcmk__str_casei)) {
                 const char *value = NULL;
                 char *now = pcmk__ttoa(time(NULL));
                 gboolean is_remote_node = FALSE;
 
                 /* This check is not 100% reliable, since this node is not
                  * guaranteed to have the remote node cached. However, it
                  * doesn't have to be reliable, since the attribute manager can
                  * learn a node's "remoteness" by other means sooner or later.
                  * This allows it to learn more quickly if this node does have
                  * the information.
                  */
                 if (g_hash_table_lookup(crm_remote_peer_cache, uuid) != NULL) {
                     is_remote_node = TRUE;
                 }
 
                 update_attrd(target, CRM_ATTR_UNFENCED, now, NULL,
                              is_remote_node);
                 free(now);
 
                 value = crm_meta_value(action->params, PCMK__META_DIGESTS_ALL);
                 update_attrd(target, CRM_ATTR_DIGESTS_ALL, value, NULL,
                              is_remote_node);
 
                 value = crm_meta_value(action->params,
                                        PCMK__META_DIGESTS_SECURE);
                 update_attrd(target, CRM_ATTR_DIGESTS_SECURE, value, NULL,
                              is_remote_node);
 
             } else if (!(pcmk_is_set(action->flags, pcmk__graph_action_sent_update))) {
                 send_stonith_update(action, target, uuid);
                 pcmk__set_graph_action_flags(action,
                                              pcmk__graph_action_sent_update);
             }
         }
         st_fail_count_reset(target);
 
     } else {
         enum pcmk__graph_next abort_action = pcmk__graph_restart;
         int status = stonith__execution_status(data);
         const char *reason = stonith__exit_reason(data);
 
         if (reason == NULL) {
             if (status == PCMK_EXEC_DONE) {
                 reason = "Agent returned error";
             } else {
                 reason = pcmk_exec_status_str(status);
             }
         }
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
 
         /* If no fence devices were available, there's no use in immediately
          * checking again, so don't start a new transition in that case.
          */
         if (status == PCMK_EXEC_NO_FENCE_DEVICE) {
             crm_warn("Fence operation %d for %s failed: %s "
                      "(aborting transition and giving up for now)",
                      data->call_id, target, reason);
             abort_action = pcmk__graph_wait;
         } else {
             crm_notice("Fence operation %d for %s failed: %s "
                        "(aborting transition)", data->call_id, target, reason);
         }
 
         /* Increment the fail count now, so abort_for_stonith_failure() can
          * check it. Non-DC nodes will increment it in
          * handle_fence_notification().
          */
         st_fail_count_increment(target);
         abort_for_stonith_failure(abort_action, target, NULL);
     }
 
     pcmk__update_graph(controld_globals.transition_graph, action);
     trigger_graph();
 
   bail:
     free(data->userdata);
     free(uuid);
     return;
 }
 
 static int
 fence_with_delay(const char *target, const char *type, int delay)
 {
     uint32_t options = st_opt_none; // Group of enum stonith_call_options
     int timeout_sec = (int) (controld_globals.transition_graph->stonith_timeout
                              / 1000);
 
     if (crmd_join_phase_count(crm_join_confirmed) == 1) {
         stonith__set_call_options(options, target, st_opt_allow_suicide);
     }
     return stonith_api->cmds->fence_with_delay(stonith_api, options, target,
                                                type, timeout_sec, 0, delay);
 }
 
 /*!
  * \internal
  * \brief Execute a fencing action from a transition graph
  *
  * \param[in] graph   Transition graph being executed (ignored)
  * \param[in] action  Fencing action to execute
  *
  * \return Standard Pacemaker return code
  */
 int
 controld_execute_fence_action(pcmk__graph_t *graph,
                               pcmk__graph_action_t *action)
 {
     int rc = 0;
     const char *id = pcmk__xe_id(action->xml);
     const char *uuid = crm_element_value(action->xml, PCMK__META_ON_NODE_UUID);
     const char *target = crm_element_value(action->xml, PCMK__META_ON_NODE);
     const char *type = crm_meta_value(action->params,
                                       PCMK__META_STONITH_ACTION);
     char *transition_key = NULL;
     const char *priority_delay = NULL;
     int delay_i = 0;
     gboolean invalid_action = FALSE;
     int stonith_timeout = (int) (controld_globals.transition_graph->stonith_timeout
                                  / 1000);
 
     CRM_CHECK(id != NULL, invalid_action = TRUE);
     CRM_CHECK(uuid != NULL, invalid_action = TRUE);
     CRM_CHECK(type != NULL, invalid_action = TRUE);
     CRM_CHECK(target != NULL, invalid_action = TRUE);
 
     if (invalid_action) {
         crm_log_xml_warn(action->xml, "BadAction");
         return EPROTO;
     }
 
     priority_delay = crm_meta_value(action->params,
                                     PCMK_OPT_PRIORITY_FENCING_DELAY);
 
     crm_notice("Requesting fencing (%s) targeting node %s "
                CRM_XS " action=%s timeout=%i%s%s",
                type, target, id, stonith_timeout,
                priority_delay ? " priority_delay=" : "",
                priority_delay ? priority_delay : "");
 
     /* Passing NULL means block until we can connect... */
     controld_timer_fencer_connect(NULL);
 
     pcmk__scan_min_int(priority_delay, &delay_i, 0);
     rc = fence_with_delay(target, type, delay_i);
     transition_key = pcmk__transition_key(controld_globals.transition_graph->id,
                                           action->id, 0,
                                           controld_globals.te_uuid),
     stonith_api->cmds->register_callback(stonith_api, rc,
                                          (stonith_timeout
                                           + (delay_i > 0 ? delay_i : 0)),
                                          st_opt_timeout_updates, transition_key,
                                          "tengine_stonith_callback",
                                          tengine_stonith_callback);
     return pcmk_rc_ok;
 }
 
 bool
 controld_verify_stonith_watchdog_timeout(const char *value)
 {
     long long st_timeout = (value != NULL)? crm_get_msec(value) : 0;
     const char *our_nodename = controld_globals.our_nodename;
 
     if (st_timeout == 0
         || (stonith_api && (stonith_api->state != stonith_disconnected) &&
             stonith__watchdog_fencing_enabled_for_node_api(stonith_api,
                                                            our_nodename))) {
         return pcmk__valid_stonith_watchdog_timeout(value);
     }
     return true;
 }
 
 /* end stonith API client functions */
 
 
 /*
  * stonith history synchronization
  *
  * Each node's fencer keeps track of a cluster-wide fencing history. When a node
  * joins or leaves, we need to synchronize the history across all nodes.
  */
 
 static crm_trigger_t *stonith_history_sync_trigger = NULL;
 static mainloop_timer_t *stonith_history_sync_timer_short = NULL;
 static mainloop_timer_t *stonith_history_sync_timer_long = NULL;
 
 void
 te_cleanup_stonith_history_sync(stonith_t *st, bool free_timers)
 {
     if (free_timers) {
         mainloop_timer_del(stonith_history_sync_timer_short);
         stonith_history_sync_timer_short = NULL;
         mainloop_timer_del(stonith_history_sync_timer_long);
         stonith_history_sync_timer_long = NULL;
     } else {
         mainloop_timer_stop(stonith_history_sync_timer_short);
         mainloop_timer_stop(stonith_history_sync_timer_long);
     }
 
     if (st) {
         st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY_SYNCED);
     }
 }
 
 static void
 tengine_stonith_history_synced(stonith_t *st, stonith_event_t *st_event)
 {
     te_cleanup_stonith_history_sync(st, FALSE);
     crm_debug("Fence-history synced - cancel all timers");
 }
 
 static gboolean
 stonith_history_sync_set_trigger(gpointer user_data)
 {
     mainloop_set_trigger(stonith_history_sync_trigger);
     return FALSE;
 }
 
 void
 te_trigger_stonith_history_sync(bool long_timeout)
 {
     /* trigger a sync in 5s to give more nodes the
      * chance to show up so that we don't create
      * unnecessary stonith-history-sync traffic
      *
      * the long timeout of 30s is there as a fallback
      * so that after a successful connection to fenced
      * we will wait for 30s for the DC to trigger a
      * history-sync
      * if this doesn't happen we trigger a sync locally
      * (e.g. fenced segfaults and is restarted by pacemakerd)
      */
 
     /* as we are finally checking the stonith-connection
      * in do_stonith_history_sync we should be fine
      * leaving stonith_history_sync_time & stonith_history_sync_trigger
      * around
      */
     if (stonith_history_sync_trigger == NULL) {
         stonith_history_sync_trigger =
             mainloop_add_trigger(G_PRIORITY_LOW,
                                  do_stonith_history_sync, NULL);
     }
 
     if (long_timeout) {
         if(stonith_history_sync_timer_long == NULL) {
             stonith_history_sync_timer_long =
                 mainloop_timer_add("history_sync_long", 30000,
                                    FALSE, stonith_history_sync_set_trigger,
                                    NULL);
         }
         crm_info("Fence history will be synchronized cluster-wide within 30 seconds");
         mainloop_timer_start(stonith_history_sync_timer_long);
     } else {
         if(stonith_history_sync_timer_short == NULL) {
             stonith_history_sync_timer_short =
                 mainloop_timer_add("history_sync_short", 5000,
                                    FALSE, stonith_history_sync_set_trigger,
                                    NULL);
         }
         crm_info("Fence history will be synchronized cluster-wide within 5 seconds");
         mainloop_timer_start(stonith_history_sync_timer_short);
     }
 
 }
 
 /* end stonith history synchronization functions */
diff --git a/daemons/controld/controld_join_dc.c b/daemons/controld/controld_join_dc.c
index 596edbb53b..1fab3883b0 100644
--- a/daemons/controld/controld_join_dc.c
+++ b/daemons/controld/controld_join_dc.c
@@ -1,1043 +1,1044 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/crm.h>
 
 #include <crm/common/xml.h>
 #include <crm/cluster.h>
 
 #include <pacemaker-controld.h>
 
 static char *max_generation_from = NULL;
 static xmlNodePtr max_generation_xml = NULL;
 
 /*!
  * \internal
  * \brief Nodes from which a CIB sync has failed since the peer joined
  *
  * This table is of the form (<tt>node_name -> join_id</tt>). \p node_name is
  * the name of a client node from which a CIB \p sync_from() call has failed in
  * \p do_dc_join_finalize() since the client joined the cluster as a peer.
  * \p join_id is the ID of the join round in which the \p sync_from() failed,
  * and is intended for use in nack log messages.
  */
 static GHashTable *failed_sync_nodes = NULL;
 
 void finalize_join_for(gpointer key, gpointer value, gpointer user_data);
 void finalize_sync_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data);
 gboolean check_join_state(enum crmd_fsa_state cur_state, const char *source);
 
 /* Numeric counter used to identify join rounds (an unsigned int would be
  * appropriate, except we get and set it in XML as int)
  */
 static int current_join_id = 0;
 
 /*!
  * \internal
  * \brief Destroy the hash table containing failed sync nodes
  */
 void
 controld_destroy_failed_sync_table(void)
 {
     if (failed_sync_nodes != NULL) {
         g_hash_table_destroy(failed_sync_nodes);
         failed_sync_nodes = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Remove a node from the failed sync nodes table if present
  *
  * \param[in] node_name  Node name to remove
  */
 void
 controld_remove_failed_sync_node(const char *node_name)
 {
     if (failed_sync_nodes != NULL) {
         g_hash_table_remove(failed_sync_nodes, (gchar *) node_name);
     }
 }
 
 /*!
  * \internal
  * \brief Add to a hash table a node whose CIB failed to sync
  *
  * \param[in] node_name  Name of node whose CIB failed to sync
  * \param[in] join_id    Join round when the failure occurred
  */
 static void
 record_failed_sync_node(const char *node_name, gint join_id)
 {
     if (failed_sync_nodes == NULL) {
         failed_sync_nodes = pcmk__strikey_table(g_free, NULL);
     }
 
     /* If the node is already in the table then we failed to nack it during the
      * filter offer step
      */
     CRM_LOG_ASSERT(g_hash_table_insert(failed_sync_nodes, g_strdup(node_name),
                                        GINT_TO_POINTER(join_id)));
 }
 
 /*!
  * \internal
  * \brief Look up a node name in the failed sync table
  *
  * \param[in]  node_name  Name of node to look up
  * \param[out] join_id    Where to store the join ID of when the sync failed
  *
  * \return Standard Pacemaker return code. Specifically, \p pcmk_rc_ok if the
  *         node name was found, or \p pcmk_rc_node_unknown otherwise.
  * \note \p *join_id is set to -1 if the node is not found.
  */
 static int
 lookup_failed_sync_node(const char *node_name, gint *join_id)
 {
     *join_id = -1;
 
     if (failed_sync_nodes != NULL) {
         gpointer result = g_hash_table_lookup(failed_sync_nodes,
                                               (gchar *) node_name);
         if (result != NULL) {
             *join_id = GPOINTER_TO_INT(result);
             return pcmk_rc_ok;
         }
     }
     return pcmk_rc_node_unknown;
 }
 
 void
 crm_update_peer_join(const char *source, crm_node_t * node, enum crm_join_phase phase)
 {
     enum crm_join_phase last = 0;
 
     CRM_CHECK(node != NULL, return);
 
     /* Remote nodes do not participate in joins */
     if (pcmk_is_set(node->flags, crm_remote_node)) {
         return;
     }
 
     last = node->join;
 
     if(phase == last) {
         crm_trace("Node %s join-%d phase is still %s "
                   CRM_XS " nodeid=%u source=%s",
                   node->uname, current_join_id, crm_join_phase_str(last),
                   node->id, source);
 
     } else if ((phase <= crm_join_none) || (phase == (last + 1))) {
         node->join = phase;
         crm_trace("Node %s join-%d phase is now %s (was %s) "
                   CRM_XS " nodeid=%u source=%s",
                  node->uname, current_join_id, crm_join_phase_str(phase),
                  crm_join_phase_str(last), node->id, source);
 
     } else {
         crm_warn("Rejecting join-%d phase update for node %s because "
                  "can't go from %s to %s " CRM_XS " nodeid=%u source=%s",
                  current_join_id, node->uname, crm_join_phase_str(last),
                  crm_join_phase_str(phase), node->id, source);
     }
 }
 
 static void
 start_join_round(void)
 {
     GHashTableIter iter;
     crm_node_t *peer = NULL;
 
     crm_debug("Starting new join round join-%d", current_join_id);
 
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
         crm_update_peer_join(__func__, peer, crm_join_none);
     }
     if (max_generation_from != NULL) {
         free(max_generation_from);
         max_generation_from = NULL;
     }
     if (max_generation_xml != NULL) {
         free_xml(max_generation_xml);
         max_generation_xml = NULL;
     }
     controld_clear_fsa_input_flags(R_HAVE_CIB);
 }
 
 /*!
  * \internal
  * \brief Create a join message from the DC
  *
  * \param[in] join_op  Join operation name
  * \param[in] host_to  Recipient of message
  */
 static xmlNode *
 create_dc_message(const char *join_op, const char *host_to)
 {
     xmlNode *msg = create_request(join_op, NULL, host_to, CRM_SYSTEM_CRMD,
                                   CRM_SYSTEM_DC, NULL);
 
     /* Identify which election this is a part of */
     crm_xml_add_int(msg, PCMK__XA_JOIN_ID, current_join_id);
 
     /* Add a field specifying whether the DC is shutting down. This keeps the
      * joining node from fencing the old DC if it becomes the new DC.
      */
     pcmk__xe_set_bool_attr(msg, PCMK__XA_DC_LEAVING,
                            pcmk_is_set(controld_globals.fsa_input_register,
                                        R_SHUTDOWN));
     return msg;
 }
 
 static void
 join_make_offer(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNode *offer = NULL;
     crm_node_t *member = (crm_node_t *)value;
 
     CRM_ASSERT(member != NULL);
     if (crm_is_peer_active(member) == FALSE) {
         crm_info("Not making join-%d offer to inactive node %s",
                  current_join_id,
                  (member->uname? member->uname : "with unknown name"));
         if(member->expected == NULL && pcmk__str_eq(member->state, CRM_NODE_LOST, pcmk__str_casei)) {
             /* You would think this unsafe, but in fact this plus an
              * active resource is what causes it to be fenced.
              *
              * Yes, this does mean that any node that dies at the same
              * time as the old DC and is not running resource (still)
              * won't be fenced.
              *
              * I'm not happy about this either.
              */
             pcmk__update_peer_expected(__func__, member, CRMD_JOINSTATE_DOWN);
         }
         return;
     }
 
     if (member->uname == NULL) {
         crm_info("Not making join-%d offer to node uuid %s with unknown name",
                  current_join_id, member->uuid);
         return;
     }
 
     if (controld_globals.membership_id != crm_peer_seq) {
         controld_globals.membership_id = crm_peer_seq;
         crm_info("Making join-%d offers based on membership event %llu",
                  current_join_id, crm_peer_seq);
     }
 
     if(user_data && member->join > crm_join_none) {
         crm_info("Not making join-%d offer to already known node %s (%s)",
                  current_join_id, member->uname,
                  crm_join_phase_str(member->join));
         return;
     }
 
     crm_update_peer_join(__func__, (crm_node_t*)member, crm_join_none);
 
     offer = create_dc_message(CRM_OP_JOIN_OFFER, member->uname);
 
     // Advertise our feature set so the joining node can bail if not compatible
     crm_xml_add(offer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
 
     crm_info("Sending join-%d offer to %s", current_join_id, member->uname);
     send_cluster_message(member, crm_msg_crmd, offer, TRUE);
     free_xml(offer);
 
     crm_update_peer_join(__func__, member, crm_join_welcomed);
 }
 
 /*	 A_DC_JOIN_OFFER_ALL	*/
 void
 do_dc_join_offer_all(long long action,
                      enum crmd_fsa_cause cause,
                      enum crmd_fsa_state cur_state,
                      enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     int count;
 
     /* Reset everyone's status back to down or in_ccm in the CIB.
      * Any nodes that are active in the CIB but not in the cluster membership
      * will be seen as offline by the scheduler anyway.
      */
     current_join_id++;
     start_join_round();
 
     update_dc(NULL);
     if (cause == C_HA_MESSAGE && current_input == I_NODE_JOIN) {
         crm_info("A new node joined the cluster");
     }
     g_hash_table_foreach(crm_peer_cache, join_make_offer, NULL);
 
     count = crmd_join_phase_count(crm_join_welcomed);
     crm_info("Waiting on join-%d requests from %d outstanding node%s",
              current_join_id, count, pcmk__plural_s(count));
 
     // Don't waste time by invoking the scheduler yet
 }
 
 /*	 A_DC_JOIN_OFFER_ONE	*/
 void
 do_dc_join_offer_one(long long action,
                      enum crmd_fsa_cause cause,
                      enum crmd_fsa_state cur_state,
                      enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     crm_node_t *member;
     ha_msg_input_t *welcome = NULL;
     int count;
     const char *join_to = NULL;
 
     if (msg_data->data == NULL) {
         crm_info("Making join-%d offers to any unconfirmed nodes "
                  "because an unknown node joined", current_join_id);
         g_hash_table_foreach(crm_peer_cache, join_make_offer, &member);
         check_join_state(cur_state, __func__);
         return;
     }
 
     welcome = fsa_typed_data(fsa_dt_ha_msg);
     if (welcome == NULL) {
         // fsa_typed_data() already logged an error
         return;
     }
 
     join_to = crm_element_value(welcome->msg, PCMK__XA_SRC);
     if (join_to == NULL) {
         crm_err("Can't make join-%d offer to unknown node", current_join_id);
         return;
     }
     member = pcmk__get_node(0, join_to, NULL, pcmk__node_search_cluster);
 
     /* It is possible that a node will have been sick or starting up when the
      * original offer was made. However, it will either re-announce itself in
      * due course, or we can re-store the original offer on the client.
      */
 
     crm_update_peer_join(__func__, member, crm_join_none);
     join_make_offer(NULL, member, NULL);
 
     /* If the offer isn't to the local node, make an offer to the local node as
      * well, to ensure the correct value for max_generation_from.
      */
     if (strcasecmp(join_to, controld_globals.our_nodename) != 0) {
         member = pcmk__get_node(0, controld_globals.our_nodename, NULL,
                                 pcmk__node_search_cluster);
         join_make_offer(NULL, member, NULL);
     }
 
     /* This was a genuine join request; cancel any existing transition and
      * invoke the scheduler.
      */
-    abort_transition(INFINITY, pcmk__graph_restart, "Node join", NULL);
+    abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, "Node join",
+                     NULL);
 
     count = crmd_join_phase_count(crm_join_welcomed);
     crm_info("Waiting on join-%d requests from %d outstanding node%s",
              current_join_id, count, pcmk__plural_s(count));
 
     // Don't waste time by invoking the scheduler yet
 }
 
 static int
 compare_int_fields(xmlNode * left, xmlNode * right, const char *field)
 {
     const char *elem_l = crm_element_value(left, field);
     const char *elem_r = crm_element_value(right, field);
 
     long long int_elem_l;
     long long int_elem_r;
 
     pcmk__scan_ll(elem_l, &int_elem_l, -1LL);
     pcmk__scan_ll(elem_r, &int_elem_r, -1LL);
 
     if (int_elem_l < int_elem_r) {
         return -1;
 
     } else if (int_elem_l > int_elem_r) {
         return 1;
     }
 
     return 0;
 }
 
 /*	 A_DC_JOIN_PROCESS_REQ	*/
 void
 do_dc_join_filter_offer(long long action,
                         enum crmd_fsa_cause cause,
                         enum crmd_fsa_state cur_state,
                         enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     xmlNode *generation = NULL;
 
     int cmp = 0;
     int join_id = -1;
     int count = 0;
     gint value = 0;
     gboolean ack_nack_bool = TRUE;
     ha_msg_input_t *join_ack = fsa_typed_data(fsa_dt_ha_msg);
 
     const char *join_from = crm_element_value(join_ack->msg, PCMK__XA_SRC);
     const char *ref = crm_element_value(join_ack->msg, PCMK_XA_REFERENCE);
     const char *join_version = crm_element_value(join_ack->msg,
                                                  PCMK_XA_CRM_FEATURE_SET);
     crm_node_t *join_node = NULL;
 
     if (join_from == NULL) {
         crm_err("Ignoring invalid join request without node name");
         return;
     }
     join_node = pcmk__get_node(0, join_from, NULL, pcmk__node_search_cluster);
 
     crm_element_value_int(join_ack->msg, PCMK__XA_JOIN_ID, &join_id);
     if (join_id != current_join_id) {
         crm_debug("Ignoring join-%d request from %s because we are on join-%d",
                   join_id, join_from, current_join_id);
         check_join_state(cur_state, __func__);
         return;
     }
 
     generation = join_ack->xml;
     if (max_generation_xml != NULL && generation != NULL) {
         int lpc = 0;
 
         const char *attributes[] = {
             PCMK_XA_ADMIN_EPOCH,
             PCMK_XA_EPOCH,
             PCMK_XA_NUM_UPDATES,
         };
 
         /* It's not obvious that join_ack->xml is the PCMK__XE_GENERATION_TUPLE
          * element from the join client. The "if" guard is for clarity.
          */
         if (pcmk__xe_is(generation, PCMK__XE_GENERATION_TUPLE)) {
             for (lpc = 0; cmp == 0 && lpc < PCMK__NELEM(attributes); lpc++) {
                 cmp = compare_int_fields(max_generation_xml, generation,
                                          attributes[lpc]);
             }
 
         } else {    // Should always be PCMK__XE_GENERATION_TUPLE
             CRM_LOG_ASSERT(false);
         }
     }
 
     if (ref == NULL) {
         ref = "none"; // for logging only
     }
 
     if (lookup_failed_sync_node(join_from, &value) == pcmk_rc_ok) {
         crm_err("Rejecting join-%d request from node %s because we failed to "
                 "sync its CIB in join-%d " CRM_XS " ref=%s",
                 join_id, join_from, value, ref);
         ack_nack_bool = FALSE;
 
     } else if (!crm_is_peer_active(join_node)) {
         if (match_down_event(join_from) != NULL) {
             /* The join request was received after the node was fenced or
              * otherwise shutdown in a way that we're aware of. No need to log
              * an error in this rare occurrence; we know the client was recently
              * shut down, and receiving a lingering in-flight request is not
              * cause for alarm.
              */
             crm_debug("Rejecting join-%d request from inactive node %s "
                       CRM_XS " ref=%s", join_id, join_from, ref);
         } else {
             crm_err("Rejecting join-%d request from inactive node %s "
                     CRM_XS " ref=%s", join_id, join_from, ref);
         }
         ack_nack_bool = FALSE;
 
     } else if (generation == NULL) {
         crm_err("Rejecting invalid join-%d request from node %s "
                 "missing CIB generation " CRM_XS " ref=%s",
                 join_id, join_from, ref);
         ack_nack_bool = FALSE;
 
     } else if ((join_version == NULL)
                || !feature_set_compatible(CRM_FEATURE_SET, join_version)) {
         crm_err("Rejecting join-%d request from node %s because feature set %s"
                 " is incompatible with ours (%s) " CRM_XS " ref=%s",
                 join_id, join_from, (join_version? join_version : "pre-3.1.0"),
                 CRM_FEATURE_SET, ref);
         ack_nack_bool = FALSE;
 
     } else if (max_generation_xml == NULL) {
         const char *validation = crm_element_value(generation,
                                                    PCMK_XA_VALIDATE_WITH);
 
         if (get_schema_version(validation) < 0) {
             crm_err("Rejecting join-%d request from %s (with first CIB "
                     "generation) due to unknown schema version %s "
                     CRM_XS " ref=%s",
                     join_id, join_from, validation, ref);
             ack_nack_bool = FALSE;
 
         } else {
             crm_debug("Accepting join-%d request from %s (with first CIB "
                       "generation) " CRM_XS " ref=%s",
                       join_id, join_from, ref);
             max_generation_xml = copy_xml(generation);
             pcmk__str_update(&max_generation_from, join_from);
         }
 
     } else if ((cmp < 0)
                || ((cmp == 0)
                    && pcmk__str_eq(join_from, controld_globals.our_nodename,
                                    pcmk__str_casei))) {
         const char *validation = crm_element_value(generation,
                                                    PCMK_XA_VALIDATE_WITH);
 
         if (get_schema_version(validation) < 0) {
             crm_err("Rejecting join-%d request from %s (with better CIB "
                     "generation than current best from %s) due to unknown "
                     "schema version %s " CRM_XS " ref=%s",
                     join_id, join_from, max_generation_from, validation, ref);
             ack_nack_bool = FALSE;
 
         } else {
             crm_debug("Accepting join-%d request from %s (with better CIB "
                       "generation than current best from %s) " CRM_XS " ref=%s",
                       join_id, join_from, max_generation_from, ref);
             crm_log_xml_debug(max_generation_xml, "Old max generation");
             crm_log_xml_debug(generation, "New max generation");
 
             free_xml(max_generation_xml);
             max_generation_xml = copy_xml(join_ack->xml);
             pcmk__str_update(&max_generation_from, join_from);
         }
 
     } else {
         crm_debug("Accepting join-%d request from %s " CRM_XS " ref=%s",
                   join_id, join_from, ref);
     }
 
     if (!ack_nack_bool) {
         if (compare_version(join_version, "3.17.0") < 0) {
             /* Clients with CRM_FEATURE_SET < 3.17.0 may respawn infinitely
              * after a nack message, don't send one
              */
             crm_update_peer_join(__func__, join_node, crm_join_nack_quiet);
         } else {
             crm_update_peer_join(__func__, join_node, crm_join_nack);
         }
         pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_NACK);
 
     } else {
         crm_update_peer_join(__func__, join_node, crm_join_integrated);
         pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_MEMBER);
     }
 
     count = crmd_join_phase_count(crm_join_integrated);
     crm_debug("%d node%s currently integrated in join-%d",
               count, pcmk__plural_s(count), join_id);
 
     if (check_join_state(cur_state, __func__) == FALSE) {
         // Don't waste time by invoking the scheduler yet
         count = crmd_join_phase_count(crm_join_welcomed);
         crm_debug("Waiting on join-%d requests from %d outstanding node%s",
                   join_id, count, pcmk__plural_s(count));
     }
 }
 
 /*	A_DC_JOIN_FINALIZE	*/
 void
 do_dc_join_finalize(long long action,
                     enum crmd_fsa_cause cause,
                     enum crmd_fsa_state cur_state,
                     enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     char *sync_from = NULL;
     int rc = pcmk_ok;
     int count_welcomed = crmd_join_phase_count(crm_join_welcomed);
     int count_finalizable = crmd_join_phase_count(crm_join_integrated)
                             + crmd_join_phase_count(crm_join_nack)
                             + crmd_join_phase_count(crm_join_nack_quiet);
 
     /* This we can do straight away and avoid clients timing us out
      *  while we compute the latest CIB
      */
     if (count_welcomed != 0) {
         crm_debug("Waiting on join-%d requests from %d outstanding node%s "
                   "before finalizing join", current_join_id, count_welcomed,
                   pcmk__plural_s(count_welcomed));
         crmd_join_phase_log(LOG_DEBUG);
         /* crmd_fsa_stall(FALSE); Needed? */
         return;
 
     } else if (count_finalizable == 0) {
         crm_debug("Finalization not needed for join-%d at the current time",
                   current_join_id);
         crmd_join_phase_log(LOG_DEBUG);
         check_join_state(controld_globals.fsa_state, __func__);
         return;
     }
 
     controld_clear_fsa_input_flags(R_HAVE_CIB);
     if (pcmk__str_eq(max_generation_from, controld_globals.our_nodename,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         controld_set_fsa_input_flags(R_HAVE_CIB);
     }
 
     if (!controld_globals.transition_graph->complete) {
         crm_warn("Delaying join-%d finalization while transition in progress",
                  current_join_id);
         crmd_join_phase_log(LOG_DEBUG);
         crmd_fsa_stall(FALSE);
         return;
     }
 
     if (pcmk_is_set(controld_globals.fsa_input_register, R_HAVE_CIB)) {
         // Send our CIB out to everyone
         pcmk__str_update(&sync_from, controld_globals.our_nodename);
         crm_debug("Finalizing join-%d for %d node%s (sync'ing from local CIB)",
                   current_join_id, count_finalizable,
                   pcmk__plural_s(count_finalizable));
         crm_log_xml_debug(max_generation_xml, "Requested CIB version");
 
     } else {
         // Ask for the agreed best CIB
         pcmk__str_update(&sync_from, max_generation_from);
         crm_notice("Finalizing join-%d for %d node%s (sync'ing CIB from %s)",
                    current_join_id, count_finalizable,
                    pcmk__plural_s(count_finalizable), sync_from);
         crm_log_xml_notice(max_generation_xml, "Requested CIB version");
     }
     crmd_join_phase_log(LOG_DEBUG);
 
     rc = controld_globals.cib_conn->cmds->sync_from(controld_globals.cib_conn,
                                                     sync_from, NULL, cib_none);
     fsa_register_cib_callback(rc, sync_from, finalize_sync_callback);
 }
 
 void
 free_max_generation(void)
 {
     free(max_generation_from);
     max_generation_from = NULL;
 
     free_xml(max_generation_xml);
     max_generation_xml = NULL;
 }
 
 void
 finalize_sync_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     CRM_LOG_ASSERT(-EPERM != rc);
 
     if (rc != pcmk_ok) {
         const char *sync_from = (const char *) user_data;
 
         do_crm_log(((rc == -pcmk_err_old_data)? LOG_WARNING : LOG_ERR),
                    "Could not sync CIB from %s in join-%d: %s",
                    sync_from, current_join_id, pcmk_strerror(rc));
 
         if (rc != -pcmk_err_old_data) {
             record_failed_sync_node(sync_from, current_join_id);
         }
 
         /* restart the whole join process */
         register_fsa_error_adv(C_FSA_INTERNAL, I_ELECTION_DC, NULL, NULL,
                                __func__);
 
     } else if (!AM_I_DC) {
         crm_debug("Sync'ed CIB for join-%d but no longer DC", current_join_id);
 
     } else if (controld_globals.fsa_state != S_FINALIZE_JOIN) {
         crm_debug("Sync'ed CIB for join-%d but no longer in S_FINALIZE_JOIN "
                   "(%s)", current_join_id,
                   fsa_state2string(controld_globals.fsa_state));
 
     } else {
         controld_set_fsa_input_flags(R_HAVE_CIB);
 
         /* make sure dc_uuid is re-set to us */
         if (!check_join_state(controld_globals.fsa_state, __func__)) {
             int count_finalizable = 0;
 
             count_finalizable = crmd_join_phase_count(crm_join_integrated)
                                 + crmd_join_phase_count(crm_join_nack)
                                 + crmd_join_phase_count(crm_join_nack_quiet);
 
             crm_debug("Notifying %d node%s of join-%d results",
                       count_finalizable, pcmk__plural_s(count_finalizable),
                       current_join_id);
             g_hash_table_foreach(crm_peer_cache, finalize_join_for, NULL);
         }
     }
 }
 
 static void
 join_node_state_commit_callback(xmlNode *msg, int call_id, int rc,
                                 xmlNode *output, void *user_data)
 {
     const char *node = user_data;
 
     if (rc != pcmk_ok) {
         fsa_data_t *msg_data = NULL;    // for register_fsa_error() macro
 
         crm_crit("join-%d node history update (via CIB call %d) for node %s "
                  "failed: %s",
                  current_join_id, call_id, node, pcmk_strerror(rc));
         crm_log_xml_debug(msg, "failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 
     crm_debug("join-%d node history update (via CIB call %d) for node %s "
               "complete",
               current_join_id, call_id, node);
     check_join_state(controld_globals.fsa_state, __func__);
 }
 
 /*	A_DC_JOIN_PROCESS_ACK	*/
 void
 do_dc_join_ack(long long action,
                enum crmd_fsa_cause cause,
                enum crmd_fsa_state cur_state,
                enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     int join_id = -1;
     ha_msg_input_t *join_ack = fsa_typed_data(fsa_dt_ha_msg);
 
     const char *op = crm_element_value(join_ack->msg, PCMK__XA_CRM_TASK);
     char *join_from = crm_element_value_copy(join_ack->msg, PCMK__XA_SRC);
     crm_node_t *peer = NULL;
 
     enum controld_section_e section = controld_section_lrm;
     char *xpath = NULL;
     xmlNode *state = join_ack->xml;
     xmlNode *execd_state = NULL;
 
     cib_t *cib = controld_globals.cib_conn;
     int rc = pcmk_ok;
 
     // Sanity checks
     if (join_from == NULL) {
         crm_warn("Ignoring message received without node identification");
         goto done;
     }
     if (op == NULL) {
         crm_warn("Ignoring message received from %s without task", join_from);
         goto done;
     }
 
     if (strcmp(op, CRM_OP_JOIN_CONFIRM)) {
         crm_debug("Ignoring '%s' message from %s while waiting for '%s'",
                   op, join_from, CRM_OP_JOIN_CONFIRM);
         goto done;
     }
 
     if (crm_element_value_int(join_ack->msg, PCMK__XA_JOIN_ID, &join_id) != 0) {
         crm_warn("Ignoring join confirmation from %s without valid join ID",
                  join_from);
         goto done;
     }
 
     peer = pcmk__get_node(0, join_from, NULL, pcmk__node_search_cluster);
     if (peer->join != crm_join_finalized) {
         crm_info("Ignoring out-of-sequence join-%d confirmation from %s "
                  "(currently %s not %s)",
                  join_id, join_from, crm_join_phase_str(peer->join),
                  crm_join_phase_str(crm_join_finalized));
         goto done;
     }
 
     if (join_id != current_join_id) {
         crm_err("Rejecting join-%d confirmation from %s "
                 "because currently on join-%d",
                 join_id, join_from, current_join_id);
         crm_update_peer_join(__func__, peer, crm_join_nack);
         goto done;
     }
 
     crm_update_peer_join(__func__, peer, crm_join_confirmed);
 
     /* Update CIB with node's current executor state. A new transition will be
      * triggered later, when the CIB manager notifies us of the change.
      *
      * The delete and modify requests are part of an atomic transaction.
      */
     rc = cib->cmds->init_transaction(cib);
     if (rc != pcmk_ok) {
         goto done;
     }
 
     // Delete relevant parts of node's current executor state from CIB
     if (pcmk_is_set(controld_globals.flags, controld_shutdown_lock_enabled)) {
         section = controld_section_lrm_unlocked;
     }
     controld_node_state_deletion_strings(join_from, section, &xpath, NULL);
 
     rc = cib->cmds->remove(cib, xpath, NULL,
                            cib_scope_local
                            |cib_xpath
                            |cib_multiple
                            |cib_transaction);
     if (rc != pcmk_ok) {
         goto done;
     }
 
     // Update CIB with node's latest known executor state
     if (pcmk__str_eq(join_from, controld_globals.our_nodename,
                      pcmk__str_casei)) {
 
         // Use the latest possible state if processing our own join ack
         execd_state = controld_query_executor_state();
 
         if (execd_state != NULL) {
             crm_debug("Updating local node history for join-%d from query "
                       "result",
                       current_join_id);
             state = execd_state;
 
         } else {
             crm_warn("Updating local node history from join-%d confirmation "
                      "because query failed",
                      current_join_id);
         }
 
     } else {
         crm_debug("Updating node history for %s from join-%d confirmation",
                   join_from, current_join_id);
     }
 
     rc = cib->cmds->modify(cib, PCMK_XE_STATUS, state,
                            cib_scope_local|cib_can_create|cib_transaction);
     free_xml(execd_state);
     if (rc != pcmk_ok) {
         goto done;
     }
 
     // Commit the transaction
     rc = cib->cmds->end_transaction(cib, true, cib_scope_local);
     fsa_register_cib_callback(rc, join_from, join_node_state_commit_callback);
 
     if (rc > 0) {
         // join_from will be freed after callback
         join_from = NULL;
         rc = pcmk_ok;
     }
 
 done:
     if (rc != pcmk_ok) {
         crm_crit("join-%d node history update for node %s failed: %s",
                  current_join_id, join_from, pcmk_strerror(rc));
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
     free(join_from);
     free(xpath);
 }
 
 void
 finalize_join_for(gpointer key, gpointer value, gpointer user_data)
 {
     xmlNode *acknak = NULL;
     xmlNode *tmp1 = NULL;
     crm_node_t *join_node = value;
     const char *join_to = join_node->uname;
     bool integrated = false;
 
     switch (join_node->join) {
         case crm_join_integrated:
             integrated = true;
             break;
         case crm_join_nack:
         case crm_join_nack_quiet:
             break;
         default:
             crm_trace("Not updating non-integrated and non-nacked node %s (%s) "
                       "for join-%d", join_to,
                       crm_join_phase_str(join_node->join), current_join_id);
             return;
     }
 
     /* Update the <node> element with the node's name and UUID, in case they
      * weren't known before
      */
     crm_trace("Updating node name and UUID in CIB for %s", join_to);
     tmp1 = create_xml_node(NULL, PCMK_XE_NODE);
     crm_xml_add(tmp1, PCMK_XA_ID, crm_peer_uuid(join_node));
     crm_xml_add(tmp1, PCMK_XA_UNAME, join_to);
     fsa_cib_anon_update(PCMK_XE_NODES, tmp1);
     free_xml(tmp1);
 
     if (join_node->join == crm_join_nack_quiet) {
         crm_trace("Not sending nack message to node %s with feature set older "
                   "than 3.17.0", join_to);
         return;
     }
 
     join_node = pcmk__get_node(0, join_to, NULL, pcmk__node_search_cluster);
     if (!crm_is_peer_active(join_node)) {
         /*
          * NACK'ing nodes that the membership layer doesn't know about yet
          * simply creates more churn
          *
          * Better to leave them waiting and let the join restart when
          * the new membership event comes in
          *
          * All other NACKs (due to versions etc) should still be processed
          */
         pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_PENDING);
         return;
     }
 
     // Acknowledge or nack node's join request
     crm_debug("%sing join-%d request from %s",
               integrated? "Acknowledg" : "Nack", current_join_id, join_to);
     acknak = create_dc_message(CRM_OP_JOIN_ACKNAK, join_to);
     pcmk__xe_set_bool_attr(acknak, CRM_OP_JOIN_ACKNAK, integrated);
 
     if (integrated) {
         // No change needed for a nacked node
         crm_update_peer_join(__func__, join_node, crm_join_finalized);
         pcmk__update_peer_expected(__func__, join_node, CRMD_JOINSTATE_MEMBER);
 
         /* Iterate through the remote peer cache and add information on which
          * node hosts each to the ACK message.  This keeps new controllers in
          * sync with what has already happened.
          */
         if (crm_remote_peer_cache_size() != 0) {
             GHashTableIter iter;
             crm_node_t *node = NULL;
             xmlNode *remotes = create_xml_node(acknak, PCMK_XE_NODES);
 
             g_hash_table_iter_init(&iter, crm_remote_peer_cache);
             while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
                 xmlNode *remote = NULL;
 
                 if (!node->conn_host) {
                     continue;
                 }
 
                 remote = create_xml_node(remotes, PCMK_XE_NODE);
                 pcmk__xe_set_props(remote,
                                    PCMK_XA_ID, node->uname,
                                    PCMK__XA_NODE_STATE, node->state,
                                    PCMK__XA_CONNECTION_HOST, node->conn_host,
                                    NULL);
             }
         }
     }
     send_cluster_message(join_node, crm_msg_crmd, acknak, TRUE);
     free_xml(acknak);
     return;
 }
 
 gboolean
 check_join_state(enum crmd_fsa_state cur_state, const char *source)
 {
     static unsigned long long highest_seq = 0;
 
     if (controld_globals.membership_id != crm_peer_seq) {
         crm_debug("join-%d: Membership changed from %llu to %llu "
                   CRM_XS " highest=%llu state=%s for=%s",
                   current_join_id, controld_globals.membership_id, crm_peer_seq,
                   highest_seq, fsa_state2string(cur_state), source);
         if(highest_seq < crm_peer_seq) {
             /* Don't spam the FSA with duplicates */
             highest_seq = crm_peer_seq;
             register_fsa_input_before(C_FSA_INTERNAL, I_NODE_JOIN, NULL);
         }
 
     } else if (cur_state == S_INTEGRATION) {
         if (crmd_join_phase_count(crm_join_welcomed) == 0) {
             int count = crmd_join_phase_count(crm_join_integrated);
 
             crm_debug("join-%d: Integration of %d peer%s complete "
                       CRM_XS " state=%s for=%s",
                       current_join_id, count, pcmk__plural_s(count),
                       fsa_state2string(cur_state), source);
             register_fsa_input_before(C_FSA_INTERNAL, I_INTEGRATED, NULL);
             return TRUE;
         }
 
     } else if (cur_state == S_FINALIZE_JOIN) {
         if (!pcmk_is_set(controld_globals.fsa_input_register, R_HAVE_CIB)) {
             crm_debug("join-%d: Delaying finalization until we have CIB "
                       CRM_XS " state=%s for=%s",
                       current_join_id, fsa_state2string(cur_state), source);
             return TRUE;
 
         } else if (crmd_join_phase_count(crm_join_welcomed) != 0) {
             int count = crmd_join_phase_count(crm_join_welcomed);
 
             crm_debug("join-%d: Still waiting on %d welcomed node%s "
                       CRM_XS " state=%s for=%s",
                       current_join_id, count, pcmk__plural_s(count),
                       fsa_state2string(cur_state), source);
             crmd_join_phase_log(LOG_DEBUG);
 
         } else if (crmd_join_phase_count(crm_join_integrated) != 0) {
             int count = crmd_join_phase_count(crm_join_integrated);
 
             crm_debug("join-%d: Still waiting on %d integrated node%s "
                       CRM_XS " state=%s for=%s",
                       current_join_id, count, pcmk__plural_s(count),
                       fsa_state2string(cur_state), source);
             crmd_join_phase_log(LOG_DEBUG);
 
         } else if (crmd_join_phase_count(crm_join_finalized) != 0) {
             int count = crmd_join_phase_count(crm_join_finalized);
 
             crm_debug("join-%d: Still waiting on %d finalized node%s "
                       CRM_XS " state=%s for=%s",
                       current_join_id, count, pcmk__plural_s(count),
                       fsa_state2string(cur_state), source);
             crmd_join_phase_log(LOG_DEBUG);
 
         } else {
             crm_debug("join-%d: Complete " CRM_XS " state=%s for=%s",
                       current_join_id, fsa_state2string(cur_state), source);
             register_fsa_input_later(C_FSA_INTERNAL, I_FINALIZED, NULL);
             return TRUE;
         }
     }
 
     return FALSE;
 }
 
 void
 do_dc_join_final(long long action,
                  enum crmd_fsa_cause cause,
                  enum crmd_fsa_state cur_state,
                  enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     crm_debug("Ensuring DC, quorum and node attributes are up-to-date");
     crm_update_quorum(crm_have_quorum, TRUE);
 }
 
 int crmd_join_phase_count(enum crm_join_phase phase)
 {
     int count = 0;
     crm_node_t *peer;
     GHashTableIter iter;
 
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
         if(peer->join == phase) {
             count++;
         }
     }
     return count;
 }
 
 void crmd_join_phase_log(int level)
 {
     crm_node_t *peer;
     GHashTableIter iter;
 
     g_hash_table_iter_init(&iter, crm_peer_cache);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &peer)) {
         do_crm_log(level, "join-%d: %s=%s", current_join_id, peer->uname,
                    crm_join_phase_str(peer->join));
     }
 }
diff --git a/daemons/controld/controld_membership.c b/daemons/controld/controld_membership.c
index 8e5e7f62a7..02b61327cb 100644
--- a/daemons/controld/controld_membership.c
+++ b/daemons/controld/controld_membership.c
@@ -1,467 +1,467 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 /* put these first so that uuid_t is defined without conflicts */
 #include <crm_internal.h>
 
 #include <string.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/cluster/internal.h>
 
 #include <pacemaker-controld.h>
 
 void post_cache_update(int instance);
 
 extern gboolean check_join_state(enum crmd_fsa_state cur_state, const char *source);
 
 static void
 reap_dead_nodes(gpointer key, gpointer value, gpointer user_data)
 {
     crm_node_t *node = value;
 
     if (crm_is_peer_active(node) == FALSE) {
         crm_update_peer_join(__func__, node, crm_join_none);
 
         if(node && node->uname) {
             if (pcmk__str_eq(controld_globals.our_nodename, node->uname,
                              pcmk__str_casei)) {
                 crm_err("We're not part of the cluster anymore");
                 register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
 
             } else if (!AM_I_DC
                        && pcmk__str_eq(node->uname, controld_globals.dc_name,
                                        pcmk__str_casei)) {
                 crm_warn("Our DC node (%s) left the cluster", node->uname);
                 register_fsa_input(C_FSA_INTERNAL, I_ELECTION, NULL);
             }
         }
 
         if ((controld_globals.fsa_state == S_INTEGRATION)
             || (controld_globals.fsa_state == S_FINALIZE_JOIN)) {
             check_join_state(controld_globals.fsa_state, __func__);
         }
         if ((node != NULL) && (node->uuid != NULL)) {
             fail_incompletable_actions(controld_globals.transition_graph,
                                        node->uuid);
         }
     }
 }
 
 void
 post_cache_update(int instance)
 {
     xmlNode *no_op = NULL;
 
     crm_peer_seq = instance;
     crm_debug("Updated cache after membership event %d.", instance);
 
     g_hash_table_foreach(crm_peer_cache, reap_dead_nodes, NULL);
     controld_set_fsa_input_flags(R_MEMBERSHIP);
 
     if (AM_I_DC) {
         populate_cib_nodes(node_update_quick | node_update_cluster | node_update_peer |
                            node_update_expected, __func__);
     }
 
     /*
      * If we lost nodes, we should re-check the election status
      * Safe to call outside of an election
      */
     controld_set_fsa_action_flags(A_ELECTION_CHECK);
     controld_trigger_fsa();
 
     /* Membership changed, remind everyone we're here.
      * This will aid detection of duplicate DCs
      */
     no_op = create_request(CRM_OP_NOOP, NULL, NULL, CRM_SYSTEM_CRMD,
                            AM_I_DC ? CRM_SYSTEM_DC : CRM_SYSTEM_CRMD, NULL);
     send_cluster_message(NULL, crm_msg_crmd, no_op, FALSE);
     free_xml(no_op);
 }
 
 static void
 crmd_node_update_complete(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     fsa_data_t *msg_data = NULL;
 
     if (rc == pcmk_ok) {
         crm_trace("Node update %d complete", call_id);
 
     } else if(call_id < pcmk_ok) {
         crm_err("Node update failed: %s (%d)", pcmk_strerror(call_id), call_id);
         crm_log_xml_debug(msg, "failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
 
     } else {
         crm_err("Node update %d failed: %s (%d)", call_id, pcmk_strerror(rc), rc);
         crm_log_xml_debug(msg, "failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 }
 
 /*!
  * \internal
  * \brief Create an XML node state tag with updates
  *
  * \param[in,out] node    Node whose state will be used for update
  * \param[in]     flags   Bitmask of node_update_flags indicating what to update
  * \param[in,out] parent  XML node to contain update (or NULL)
  * \param[in]     source  Who requested the update (only used for logging)
  *
  * \return Pointer to created node state tag
  */
 xmlNode *
 create_node_state_update(crm_node_t *node, int flags, xmlNode *parent,
                          const char *source)
 {
     const char *value = NULL;
     xmlNode *node_state;
 
     if (!node->state) {
         crm_info("Node update for %s cancelled: no state, not seen yet", node->uname);
        return NULL;
     }
 
     node_state = create_xml_node(parent, PCMK__XE_NODE_STATE);
 
     if (pcmk_is_set(node->flags, crm_remote_node)) {
         pcmk__xe_set_bool_attr(node_state, PCMK_XA_REMOTE_NODE, true);
     }
 
     if (crm_xml_add(node_state, PCMK_XA_ID, crm_peer_uuid(node)) == NULL) {
         crm_info("Node update for %s cancelled: no ID", node->uname);
         free_xml(node_state);
         return NULL;
     }
 
     crm_xml_add(node_state, PCMK_XA_UNAME, node->uname);
 
     if ((flags & node_update_cluster) && node->state) {
         if (compare_version(controld_globals.dc_version, "3.18.0") >= 0) {
             // A value 0 means the node is not a cluster member.
             crm_xml_add_ll(node_state, PCMK__XA_IN_CCM, node->when_member);
 
         } else {
             pcmk__xe_set_bool_attr(node_state, PCMK__XA_IN_CCM,
                                    pcmk__str_eq(node->state, CRM_NODE_MEMBER,
                                                 pcmk__str_casei));
         }
     }
 
     if (!pcmk_is_set(node->flags, crm_remote_node)) {
         if (flags & node_update_peer) {
             if (compare_version(controld_globals.dc_version, "3.18.0") >= 0) {
                 // A value 0 means the peer is offline in CPG.
                 crm_xml_add_ll(node_state, PCMK_XA_CRMD, node->when_online);
 
             } else {
                 // @COMPAT DCs < 2.1.7 use online/offline rather than timestamp
                 value = PCMK_VALUE_OFFLINE;
                 if (pcmk_is_set(node->processes, crm_get_cluster_proc())) {
                     value = PCMK_VALUE_ONLINE;
                 }
                 crm_xml_add(node_state, PCMK_XA_CRMD, value);
             }
         }
 
         if (flags & node_update_join) {
             if (node->join <= crm_join_none) {
                 value = CRMD_JOINSTATE_DOWN;
             } else {
                 value = CRMD_JOINSTATE_MEMBER;
             }
             crm_xml_add(node_state, PCMK__XA_JOIN, value);
         }
 
         if (flags & node_update_expected) {
             crm_xml_add(node_state, PCMK_XA_EXPECTED, node->expected);
         }
     }
 
     crm_xml_add(node_state, PCMK_XA_CRM_DEBUG_ORIGIN, source);
 
     return node_state;
 }
 
 static void
 remove_conflicting_node_callback(xmlNode * msg, int call_id, int rc,
                                  xmlNode * output, void *user_data)
 {
     char *node_uuid = user_data;
 
     do_crm_log_unlikely(rc == 0 ? LOG_DEBUG : LOG_NOTICE,
                         "Deletion of the unknown conflicting node \"%s\": %s (rc=%d)",
                         node_uuid, pcmk_strerror(rc), rc);
 }
 
 static void
 search_conflicting_node_callback(xmlNode * msg, int call_id, int rc,
                                  xmlNode * output, void *user_data)
 {
     char *new_node_uuid = user_data;
     xmlNode *node_xml = NULL;
 
     if (rc != pcmk_ok) {
         if (rc != -ENXIO) {
             crm_notice("Searching conflicting nodes for %s failed: %s (%d)",
                        new_node_uuid, pcmk_strerror(rc), rc);
         }
         return;
 
     } else if (output == NULL) {
         return;
     }
 
     if (pcmk__xe_is(output, PCMK_XE_NODE)) {
         node_xml = output;
 
     } else {
         node_xml = pcmk__xml_first_child(output);
     }
 
     for (; node_xml != NULL; node_xml = pcmk__xml_next(node_xml)) {
         const char *node_uuid = NULL;
         const char *node_uname = NULL;
         GHashTableIter iter;
         crm_node_t *node = NULL;
         gboolean known = FALSE;
 
         if (!pcmk__xe_is(node_xml, PCMK_XE_NODE)) {
             continue;
         }
 
         node_uuid = crm_element_value(node_xml, PCMK_XA_ID);
         node_uname = crm_element_value(node_xml, PCMK_XA_UNAME);
 
         if (node_uuid == NULL || node_uname == NULL) {
             continue;
         }
 
         g_hash_table_iter_init(&iter, crm_peer_cache);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
             if (node->uuid
                 && pcmk__str_eq(node->uuid, node_uuid, pcmk__str_casei)
                 && node->uname
                 && pcmk__str_eq(node->uname, node_uname, pcmk__str_casei)) {
 
                 known = TRUE;
                 break;
             }
         }
 
         if (known == FALSE) {
             cib_t *cib_conn = controld_globals.cib_conn;
             int delete_call_id = 0;
             xmlNode *node_state_xml = NULL;
 
             crm_notice("Deleting unknown node %s/%s which has conflicting uname with %s",
                        node_uuid, node_uname, new_node_uuid);
 
             delete_call_id = cib_conn->cmds->remove(cib_conn, PCMK_XE_NODES,
                                                     node_xml, cib_scope_local);
             fsa_register_cib_callback(delete_call_id, strdup(node_uuid),
                                       remove_conflicting_node_callback);
 
             node_state_xml = create_xml_node(NULL, PCMK__XE_NODE_STATE);
             crm_xml_add(node_state_xml, PCMK_XA_ID, node_uuid);
             crm_xml_add(node_state_xml, PCMK_XA_UNAME, node_uname);
 
             delete_call_id = cib_conn->cmds->remove(cib_conn, PCMK_XE_STATUS,
                                                     node_state_xml,
                                                     cib_scope_local);
             fsa_register_cib_callback(delete_call_id, strdup(node_uuid),
                                       remove_conflicting_node_callback);
             free_xml(node_state_xml);
         }
     }
 }
 
 static void
 node_list_update_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     fsa_data_t *msg_data = NULL;
 
     if(call_id < pcmk_ok) {
         crm_err("Node list update failed: %s (%d)", pcmk_strerror(call_id), call_id);
         crm_log_xml_debug(msg, "update:failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
 
     } else if(rc < pcmk_ok) {
         crm_err("Node update %d failed: %s (%d)", call_id, pcmk_strerror(rc), rc);
         crm_log_xml_debug(msg, "update:failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 }
 
 void
 populate_cib_nodes(enum node_update_flags flags, const char *source)
 {
     cib_t *cib_conn = controld_globals.cib_conn;
 
     int call_id = 0;
     gboolean from_hashtable = TRUE;
     xmlNode *node_list = create_xml_node(NULL, PCMK_XE_NODES);
 
 #if SUPPORT_COROSYNC
     if (!pcmk_is_set(flags, node_update_quick) && is_corosync_cluster()) {
         from_hashtable = pcmk__corosync_add_nodes(node_list);
     }
 #endif
 
     if (from_hashtable) {
         GHashTableIter iter;
         crm_node_t *node = NULL;
         GString *xpath = NULL;
 
         g_hash_table_iter_init(&iter, crm_peer_cache);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
             xmlNode *new_node = NULL;
 
             if ((node->uuid != NULL) && (node->uname != NULL)) {
                 crm_trace("Creating node entry for %s/%s", node->uname, node->uuid);
                 if (xpath == NULL) {
                     xpath = g_string_sized_new(512);
                 } else {
                     g_string_truncate(xpath, 0);
                 }
 
                 /* We need both to be valid */
                 new_node = create_xml_node(node_list, PCMK_XE_NODE);
                 crm_xml_add(new_node, PCMK_XA_ID, node->uuid);
                 crm_xml_add(new_node, PCMK_XA_UNAME, node->uname);
 
                 /* Search and remove unknown nodes with the conflicting uname from CIB */
                 pcmk__g_strcat(xpath,
                                "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
                                "/" PCMK_XE_NODES "/" PCMK_XE_NODE
                                "[@" PCMK_XA_UNAME "='", node->uname, "']"
                                "[@" PCMK_XA_ID "!='", node->uuid, "']", NULL);
 
                 call_id = cib_conn->cmds->query(cib_conn,
                                                 (const char *) xpath->str,
                                                 NULL,
                                                 cib_scope_local|cib_xpath);
                 fsa_register_cib_callback(call_id, strdup(node->uuid),
                                           search_conflicting_node_callback);
             }
         }
 
         if (xpath != NULL) {
             g_string_free(xpath, TRUE);
         }
     }
 
     crm_trace("Populating <nodes> section from %s", from_hashtable ? "hashtable" : "cluster");
 
     if ((controld_update_cib(PCMK_XE_NODES, node_list, cib_scope_local,
                              node_list_update_callback) == pcmk_rc_ok)
          && (crm_peer_cache != NULL) && AM_I_DC) {
         /*
          * There is no need to update the local CIB with our values if
          * we've not seen valid membership data
          */
         GHashTableIter iter;
         crm_node_t *node = NULL;
 
         free_xml(node_list);
         node_list = create_xml_node(NULL, PCMK_XE_STATUS);
 
         g_hash_table_iter_init(&iter, crm_peer_cache);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
             create_node_state_update(node, flags, node_list, source);
         }
 
         if (crm_remote_peer_cache) {
             g_hash_table_iter_init(&iter, crm_remote_peer_cache);
             while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
                 create_node_state_update(node, flags, node_list, source);
             }
         }
 
         controld_update_cib(PCMK_XE_STATUS, node_list, cib_scope_local,
                             crmd_node_update_complete);
     }
     free_xml(node_list);
 }
 
 static void
 cib_quorum_update_complete(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     fsa_data_t *msg_data = NULL;
 
     if (rc == pcmk_ok) {
         crm_trace("Quorum update %d complete", call_id);
 
     } else {
         crm_err("Quorum update %d failed: %s (%d)", call_id, pcmk_strerror(rc), rc);
         crm_log_xml_debug(msg, "failed");
         register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
     }
 }
 
 void
 crm_update_quorum(gboolean quorum, gboolean force_update)
 {
     bool has_quorum = pcmk_is_set(controld_globals.flags, controld_has_quorum);
 
     if (quorum) {
         controld_set_global_flags(controld_ever_had_quorum);
 
     } else if (pcmk_all_flags_set(controld_globals.flags,
                                   controld_ever_had_quorum
                                   |controld_no_quorum_suicide)) {
         pcmk__panic(__func__);
     }
 
     if (AM_I_DC
         && ((has_quorum && !quorum) || (!has_quorum && quorum)
             || force_update)) {
         xmlNode *update = NULL;
 
         update = create_xml_node(NULL, PCMK_XE_CIB);
         crm_xml_add_int(update, PCMK_XA_HAVE_QUORUM, quorum);
         crm_xml_add(update, PCMK_XA_DC_UUID, controld_globals.our_uuid);
 
         crm_debug("Updating quorum status to %s", pcmk__btoa(quorum));
         controld_update_cib(PCMK_XE_CIB, update, cib_scope_local,
                             cib_quorum_update_complete);
         free_xml(update);
 
         /* Quorum changes usually cause a new transition via other activity:
          * quorum gained via a node joining will abort via the node join,
          * and quorum lost via a node leaving will usually abort via resource
          * activity and/or fencing.
          *
          * However, it is possible that nothing else causes a transition (e.g.
          * someone forces quorum via corosync-cmaptcl, or quorum is lost due to
          * a node in standby shutting down cleanly), so here ensure a new
          * transition is triggered.
          */
         if (quorum) {
             /* If quorum was gained, abort after a short delay, in case multiple
              * nodes are joining around the same time, so the one that brings us
              * to quorum doesn't cause all the remaining ones to be fenced.
              */
-            abort_after_delay(INFINITY, pcmk__graph_restart, "Quorum gained",
-                              5000);
+            abort_after_delay(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                              "Quorum gained", 5000);
         } else {
-            abort_transition(INFINITY, pcmk__graph_restart, "Quorum lost",
-                             NULL);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Quorum lost", NULL);
         }
     }
 
     if (quorum) {
         controld_set_global_flags(controld_has_quorum);
     } else {
         controld_clear_global_flags(controld_has_quorum);
     }
 }
diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c
index 44626b57ae..c52482268c 100644
--- a/daemons/controld/controld_te_callbacks.c
+++ b/daemons/controld/controld_te_callbacks.c
@@ -1,700 +1,704 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <pacemaker-controld.h>
 
 void te_update_confirm(const char *event, xmlNode * msg);
 
 #define RSC_OP_PREFIX "//" PCMK__XE_DIFF_ADDED "//" PCMK_XE_CIB \
                       "//" PCMK__XE_LRM_RSC_OP "[@" PCMK_XA_ID "='"
 
 // An explicit PCMK_OPT_SHUTDOWN_LOCK of 0 means the lock has been cleared
 static bool
 shutdown_lock_cleared(xmlNode *lrm_resource)
 {
     time_t shutdown_lock = 0;
 
     return (crm_element_value_epoch(lrm_resource, PCMK_OPT_SHUTDOWN_LOCK,
                                     &shutdown_lock) == pcmk_ok)
            && (shutdown_lock == 0);
 }
 
 static void
 te_update_diff_v1(const char *event, xmlNode *diff)
 {
     int lpc, max;
     xmlXPathObject *xpathObj = NULL;
     GString *rsc_op_xpath = NULL;
 
     CRM_CHECK(diff != NULL, return);
 
     pcmk__output_set_log_level(controld_globals.logger_out, LOG_TRACE);
     controld_globals.logger_out->message(controld_globals.logger_out,
                                          "xml-patchset", diff);
 
     if (cib__config_changed_v1(NULL, NULL, &diff)) {
-        abort_transition(INFINITY, pcmk__graph_restart, "Non-status change",
-                         diff);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Non-status change", diff);
         goto bail;              /* configuration changed */
     }
 
     /* Tickets Attributes - Added/Updated */
     xpathObj =
         xpath_search(diff,
                      "//" PCMK__XA_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_ADDED
                      "//" PCMK_XE_TICKETS);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
-        abort_transition(INFINITY, pcmk__graph_restart,
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                          "Ticket attribute: update", aborted);
         goto bail;
 
     }
     freeXpathObject(xpathObj);
 
     /* Tickets Attributes - Removed */
     xpathObj =
         xpath_search(diff,
                      "//" PCMK__XA_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_REMOVED
                      "//" PCMK_XE_TICKETS);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
-        abort_transition(INFINITY, pcmk__graph_restart,
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                          "Ticket attribute: removal", aborted);
         goto bail;
     }
     freeXpathObject(xpathObj);
 
     /* Transient Attributes - Removed */
     xpathObj =
         xpath_search(diff,
                      "//" PCMK__XA_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_REMOVED
                      "//" PCMK__XE_TRANSIENT_ATTRIBUTES);
     if (numXpathResults(xpathObj) > 0) {
         xmlNode *aborted = getXpathResult(xpathObj, 0);
 
-        abort_transition(INFINITY, pcmk__graph_restart,
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                          "Transient attribute: removal", aborted);
         goto bail;
 
     }
     freeXpathObject(xpathObj);
 
     // Check for PCMK__XE_LRM_RESOURCE entries
     xpathObj = xpath_search(diff,
                             "//" PCMK__XA_CIB_UPDATE_RESULT
                             "//" PCMK__XE_DIFF_ADDED
                             "//" PCMK__XE_LRM_RESOURCE);
     max = numXpathResults(xpathObj);
 
     /*
      * Updates by, or in response to, graph actions will never affect more than
      * one resource at a time, so such updates indicate an LRM refresh. In that
      * case, start a new transition rather than check each result individually,
      * which can result in _huge_ speedups in large clusters.
      *
      * Unfortunately, we can only do so when there are no pending actions.
      * Otherwise, we could mistakenly throw away those results here, and
      * the cluster will stall waiting for them and time out the operation.
      */
     if ((controld_globals.transition_graph->pending == 0) && (max > 1)) {
         crm_debug("Ignoring resource operation updates due to history refresh of %d resources",
                   max);
         crm_log_xml_trace(diff, "lrm-refresh");
-        abort_transition(INFINITY, pcmk__graph_restart, "History refresh",
-                         NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "History refresh", NULL);
         goto bail;
     }
 
     if (max == 1) {
         xmlNode *lrm_resource = getXpathResult(xpathObj, 0);
 
         if (shutdown_lock_cleared(lrm_resource)) {
             // @TODO would be more efficient to abort once after transition done
-            abort_transition(INFINITY, pcmk__graph_restart,
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                              "Shutdown lock cleared", lrm_resource);
             // Still process results, so we stop timers and update failcounts
         }
     }
     freeXpathObject(xpathObj);
 
     /* Process operation updates */
     xpathObj =
         xpath_search(diff,
                      "//" PCMK__XA_CIB_UPDATE_RESULT
                      "//" PCMK__XE_DIFF_ADDED
                      "//" PCMK__XE_LRM_RSC_OP);
     max = numXpathResults(xpathObj);
     if (max > 0) {
         int lpc = 0;
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
             const char *node = get_node_id(rsc_op);
 
             process_graph_event(rsc_op, node);
         }
     }
     freeXpathObject(xpathObj);
 
     /* Detect deleted (as opposed to replaced or added) actions - eg. crm_resource -C */
     xpathObj = xpath_search(diff,
                             "//" PCMK__XE_DIFF_REMOVED
                             "//" PCMK__XE_LRM_RSC_OP);
     max = numXpathResults(xpathObj);
     for (lpc = 0; lpc < max; lpc++) {
         const char *op_id = NULL;
         xmlXPathObject *op_match = NULL;
         xmlNode *match = getXpathResult(xpathObj, lpc);
 
         CRM_LOG_ASSERT(match != NULL);
         if(match == NULL) { continue; };
 
         op_id = pcmk__xe_id(match);
 
         if (rsc_op_xpath == NULL) {
             rsc_op_xpath = g_string_new(RSC_OP_PREFIX);
         } else {
             g_string_truncate(rsc_op_xpath, sizeof(RSC_OP_PREFIX) - 1);
         }
         pcmk__g_strcat(rsc_op_xpath, op_id, "']", NULL);
 
         op_match = xpath_search(diff, (const char *) rsc_op_xpath->str);
         if (numXpathResults(op_match) == 0) {
             /* Prevent false positives by matching cancelations too */
             const char *node = get_node_id(match);
             pcmk__graph_action_t *cancelled = get_cancel_action(op_id, node);
 
             if (cancelled == NULL) {
                 crm_debug("No match for deleted action %s (%s on %s)",
                           (const char *) rsc_op_xpath->str, op_id, node);
-                abort_transition(INFINITY, pcmk__graph_restart,
+                abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                                  "Resource op removal", match);
                 freeXpathObject(op_match);
                 goto bail;
 
             } else {
                 crm_debug("Deleted " PCMK__XE_LRM_RSC_OP " %s on %s was for "
                           "graph event %d",
                           op_id, node, cancelled->id);
             }
         }
 
         freeXpathObject(op_match);
     }
 
   bail:
     freeXpathObject(xpathObj);
     if (rsc_op_xpath != NULL) {
         g_string_free(rsc_op_xpath, TRUE);
     }
 }
 
 static void
 process_lrm_resource_diff(xmlNode *lrm_resource, const char *node)
 {
     for (xmlNode *rsc_op = pcmk__xml_first_child(lrm_resource); rsc_op != NULL;
          rsc_op = pcmk__xml_next(rsc_op)) {
         process_graph_event(rsc_op, node);
     }
     if (shutdown_lock_cleared(lrm_resource)) {
         // @TODO would be more efficient to abort once after transition done
-        abort_transition(INFINITY, pcmk__graph_restart, "Shutdown lock cleared",
-                         lrm_resource);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Shutdown lock cleared", lrm_resource);
     }
 }
 
 static void
 process_resource_updates(const char *node, xmlNode *xml, xmlNode *change,
                          const char *op, const char *xpath)
 {
     xmlNode *rsc = NULL;
 
     if (xml == NULL) {
         return;
     }
 
     if (pcmk__xe_is(xml, PCMK__XE_LRM)) {
         xml = first_named_child(xml, PCMK__XE_LRM_RESOURCES);
         CRM_CHECK(xml != NULL, return);
     }
 
     CRM_CHECK(pcmk__xe_is(xml, PCMK__XE_LRM_RESOURCES), return);
 
     /*
      * Updates by, or in response to, TE actions will never contain updates
      * for more than one resource at a time, so such updates indicate an
      * LRM refresh.
      *
      * In that case, start a new transition rather than check each result
      * individually, which can result in _huge_ speedups in large clusters.
      *
      * Unfortunately, we can only do so when there are no pending actions.
      * Otherwise, we could mistakenly throw away those results here, and
      * the cluster will stall waiting for them and time out the operation.
      */
     if ((controld_globals.transition_graph->pending == 0)
         && (xml->children != NULL) && (xml->children->next != NULL)) {
 
         crm_log_xml_trace(change, "lrm-refresh");
-        abort_transition(INFINITY, pcmk__graph_restart, "History refresh",
-                         NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "History refresh", NULL);
         return;
     }
 
     for (rsc = pcmk__xml_first_child(xml); rsc != NULL;
          rsc = pcmk__xml_next(rsc)) {
         crm_trace("Processing %s", pcmk__xe_id(rsc));
         process_lrm_resource_diff(rsc, node);
     }
 }
 
 static char *extract_node_uuid(const char *xpath) 
 {
     char *mutable_path = strdup(xpath);
     char *node_uuid = NULL;
     char *search = NULL;
     char *match = NULL;
 
     match = strstr(mutable_path, PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'");
     if (match == NULL) {
         free(mutable_path);
         return NULL;
     }
     match += strlen(PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'");
 
     search = strchr(match, '\'');
     if (search == NULL) {
         free(mutable_path);
         return NULL;
     }
     search[0] = 0;
 
     node_uuid = strdup(match);
     free(mutable_path);
     return node_uuid;
 }
 
 static void
 abort_unless_down(const char *xpath, const char *op, xmlNode *change,
                   const char *reason)
 {
     char *node_uuid = NULL;
     pcmk__graph_action_t *down = NULL;
 
     if (!pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none)) {
-        abort_transition(INFINITY, pcmk__graph_restart, reason, change);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
+                         change);
         return;
     }
 
     node_uuid = extract_node_uuid(xpath);
     if(node_uuid == NULL) {
         crm_err("Could not extract node ID from %s", xpath);
-        abort_transition(INFINITY, pcmk__graph_restart, reason, change);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
+                         change);
         return;
     }
 
     down = match_down_event(node_uuid);
     if (down == NULL) {
         crm_trace("Not expecting %s to be down (%s)", node_uuid, xpath);
-        abort_transition(INFINITY, pcmk__graph_restart, reason, change);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
+                         change);
     } else {
         crm_trace("Expecting changes to %s (%s)", node_uuid, xpath);
     }
     free(node_uuid);
 }
 
 static void
 process_op_deletion(const char *xpath, xmlNode *change)
 {
     char *mutable_key = strdup(xpath);
     char *key;
     char *node_uuid;
 
     // Extract the part of xpath between last pair of single quotes
     key = strrchr(mutable_key, '\'');
     if (key != NULL) {
         *key = '\0';
         key = strrchr(mutable_key, '\'');
     }
     if (key == NULL) {
         crm_warn("Ignoring malformed CIB update (resource deletion of %s)",
                  xpath);
         free(mutable_key);
         return;
     }
     ++key;
 
     node_uuid = extract_node_uuid(xpath);
     if (confirm_cancel_action(key, node_uuid) == FALSE) {
-        abort_transition(INFINITY, pcmk__graph_restart,
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                          "Resource operation removal", change);
     }
     free(mutable_key);
     free(node_uuid);
 }
 
 static void
 process_delete_diff(const char *xpath, const char *op, xmlNode *change)
 {
     if (strstr(xpath, "/" PCMK__XE_LRM_RSC_OP "[")) {
         process_op_deletion(xpath, change);
 
     } else if (strstr(xpath, "/" PCMK__XE_LRM "[")) {
         abort_unless_down(xpath, op, change, "Resource state removal");
 
     } else if (strstr(xpath, "/" PCMK__XE_NODE_STATE "[")) {
         abort_unless_down(xpath, op, change, "Node state removal");
 
     } else {
         crm_trace("Ignoring delete of %s", xpath);
     }
 }
 
 static void
 process_node_state_diff(xmlNode *state, xmlNode *change, const char *op,
                         const char *xpath)
 {
     xmlNode *lrm = first_named_child(state, PCMK__XE_LRM);
 
     process_resource_updates(pcmk__xe_id(state), lrm, change, op, xpath);
 }
 
 static void
 process_status_diff(xmlNode *status, xmlNode *change, const char *op,
                     const char *xpath)
 {
     for (xmlNode *state = pcmk__xml_first_child(status); state != NULL;
          state = pcmk__xml_next(state)) {
         process_node_state_diff(state, change, op, xpath);
     }
 }
 
 static void
 process_cib_diff(xmlNode *cib, xmlNode *change, const char *op,
                  const char *xpath)
 {
     xmlNode *status = first_named_child(cib, PCMK_XE_STATUS);
     xmlNode *config = first_named_child(cib, PCMK_XE_CONFIGURATION);
 
     if (status) {
         process_status_diff(status, change, op, xpath);
     }
     if (config) {
-        abort_transition(INFINITY, pcmk__graph_restart,
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                          "Non-status-only change", change);
     }
 }
 
 static void
 te_update_diff_v2(xmlNode *diff)
 {
     crm_log_xml_trace(diff, "Patch:Raw");
 
     for (xmlNode *change = pcmk__xml_first_child(diff); change != NULL;
          change = pcmk__xml_next(change)) {
 
         xmlNode *match = NULL;
         const char *name = NULL;
         const char *xpath = crm_element_value(change, PCMK_XA_PATH);
 
         // Possible ops: create, modify, delete, move
         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
 
         // Ignore uninteresting updates
         if (op == NULL) {
             continue;
 
         } else if (xpath == NULL) {
             crm_trace("Ignoring %s change for version field", op);
             continue;
 
         } else if ((strcmp(op, PCMK_VALUE_MOVE) == 0)
                    && (strstr(xpath,
                               "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
                               "/" PCMK_XE_RESOURCES) == NULL)) {
             /* We still need to consider moves within the resources section,
              * since they affect placement order.
              */
             crm_trace("Ignoring move change at %s", xpath);
             continue;
         }
 
         // Find the result of create/modify ops
         if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
             match = change->children;
 
         } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
             match = first_named_child(change, PCMK_XE_CHANGE_RESULT);
             if(match) {
                 match = match->children;
             }
 
         } else if (!pcmk__str_any_of(op,
                                      PCMK_VALUE_DELETE, PCMK_VALUE_MOVE,
                                      NULL)) {
             crm_warn("Ignoring malformed CIB update (%s operation on %s is unrecognized)",
                      op, xpath);
             continue;
         }
 
         if (match) {
             if (match->type == XML_COMMENT_NODE) {
                 crm_trace("Ignoring %s operation for comment at %s", op, xpath);
                 continue;
             }
             name = (const char *)match->name;
         }
 
         crm_trace("Handling %s operation for %s%s%s",
                   op, (xpath? xpath : "CIB"),
                   (name? " matched by " : ""), (name? name : ""));
 
         if (strstr(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION)) {
-            abort_transition(INFINITY, pcmk__graph_restart,
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                              "Configuration change", change);
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strstr(xpath, "/" PCMK_XE_TICKETS)
                    || pcmk__str_eq(name, PCMK_XE_TICKETS, pcmk__str_none)) {
-            abort_transition(INFINITY, pcmk__graph_restart,
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                              "Ticket attribute change", change);
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strstr(xpath, "/" PCMK__XE_TRANSIENT_ATTRIBUTES "[")
                    || pcmk__str_eq(name, PCMK__XE_TRANSIENT_ATTRIBUTES,
                                    pcmk__str_none)) {
             abort_unless_down(xpath, op, change, "Transient attribute change");
             break; // Won't be packaged with operation results we may be waiting for
 
         } else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
             process_delete_diff(xpath, op, change);
 
         } else if (name == NULL) {
             crm_warn("Ignoring malformed CIB update (%s at %s has no result)",
                      op, xpath);
 
         } else if (strcmp(name, PCMK_XE_CIB) == 0) {
             process_cib_diff(match, change, op, xpath);
 
         } else if (strcmp(name, PCMK_XE_STATUS) == 0) {
             process_status_diff(match, change, op, xpath);
 
         } else if (strcmp(name, PCMK__XE_NODE_STATE) == 0) {
             process_node_state_diff(match, change, op, xpath);
 
         } else if (strcmp(name, PCMK__XE_LRM) == 0) {
             process_resource_updates(pcmk__xe_id(match), match, change, op,
                                      xpath);
 
         } else if (strcmp(name, PCMK__XE_LRM_RESOURCES) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
 
             process_resource_updates(local_node, match, change, op, xpath);
             free(local_node);
 
         } else if (strcmp(name, PCMK__XE_LRM_RESOURCE) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
 
             process_lrm_resource_diff(match, local_node);
             free(local_node);
 
         } else if (strcmp(name, PCMK__XE_LRM_RSC_OP) == 0) {
             char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
 
             process_graph_event(match, local_node);
             free(local_node);
 
         } else {
             crm_warn("Ignoring malformed CIB update (%s at %s has unrecognized result %s)",
                      op, xpath, name);
         }
     }
 }
 
 void
 te_update_diff(const char *event, xmlNode * msg)
 {
     xmlNode *diff = NULL;
     const char *op = NULL;
     int rc = -EINVAL;
     int format = 1;
     int p_add[] = { 0, 0, 0 };
     int p_del[] = { 0, 0, 0 };
 
     CRM_CHECK(msg != NULL, return);
     crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
 
     if (controld_globals.transition_graph == NULL) {
         crm_trace("No graph");
         return;
 
     } else if (rc < pcmk_ok) {
         crm_trace("Filter rc=%d (%s)", rc, pcmk_strerror(rc));
         return;
 
     } else if (controld_globals.transition_graph->complete
                && (controld_globals.fsa_state != S_IDLE)
                && (controld_globals.fsa_state != S_TRANSITION_ENGINE)
                && (controld_globals.fsa_state != S_POLICY_ENGINE)) {
         crm_trace("Filter state=%s (complete)",
                   fsa_state2string(controld_globals.fsa_state));
         return;
     }
 
     op = crm_element_value(msg, PCMK__XA_CIB_OP);
     diff = get_message_xml(msg, PCMK__XA_CIB_UPDATE_RESULT);
 
     xml_patch_versions(diff, p_add, p_del);
     crm_debug("Processing (%s) diff: %d.%d.%d -> %d.%d.%d (%s)", op,
               p_del[0], p_del[1], p_del[2], p_add[0], p_add[1], p_add[2],
               fsa_state2string(controld_globals.fsa_state));
 
     crm_element_value_int(diff, PCMK_XA_FORMAT, &format);
     switch (format) {
         case 1:
             te_update_diff_v1(event, diff);
             break;
         case 2:
             te_update_diff_v2(diff);
             break;
         default:
             crm_warn("Ignoring malformed CIB update (unknown patch format %d)",
                      format);
     }
     controld_remove_all_outside_events();
 }
 
 void
 process_te_message(xmlNode * msg, xmlNode * xml_data)
 {
     const char *value = NULL;
     xmlXPathObject *xpathObj = NULL;
     int nmatches = 0;
 
     CRM_CHECK(msg != NULL, return);
 
     // Transition requests must specify transition engine as subsystem
     value = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
     if (pcmk__str_empty(value)
         || !pcmk__str_eq(value, CRM_SYSTEM_TENGINE, pcmk__str_none)) {
         crm_info("Received invalid transition request: subsystem '%s' not '"
                  CRM_SYSTEM_TENGINE "'", pcmk__s(value, ""));
         return;
     }
 
     // Only the lrm_invoke command is supported as a transition request
     value = crm_element_value(msg, PCMK__XA_CRM_TASK);
     if (!pcmk__str_eq(value, CRM_OP_INVOKE_LRM, pcmk__str_none)) {
         crm_info("Received invalid transition request: command '%s' not '"
                  CRM_OP_INVOKE_LRM "'", pcmk__s(value, ""));
         return;
     }
 
     // Transition requests must be marked as coming from the executor
     value = crm_element_value(msg, PCMK__XA_CRM_SYS_FROM);
     if (!pcmk__str_eq(value, CRM_SYSTEM_LRMD, pcmk__str_none)) {
         crm_info("Received invalid transition request: from '%s' not '"
                  CRM_SYSTEM_LRMD "'", pcmk__s(value, ""));
         return;
     }
 
     crm_debug("Processing transition request with ref='%s' origin='%s'",
               pcmk__s(crm_element_value(msg, PCMK_XA_REFERENCE), ""),
               pcmk__s(crm_element_value(msg, PCMK__XA_SRC), ""));
 
     xpathObj = xpath_search(xml_data, "//" PCMK__XE_LRM_RSC_OP);
     nmatches = numXpathResults(xpathObj);
     if (nmatches == 0) {
         crm_err("Received transition request with no results (bug?)");
     } else {
         for (int lpc = 0; lpc < nmatches; lpc++) {
             xmlNode *rsc_op = getXpathResult(xpathObj, lpc);
             const char *node = get_node_id(rsc_op);
 
             process_graph_event(rsc_op, node);
         }
     }
     freeXpathObject(xpathObj);
 }
 
 void
 cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
 {
     if (rc < pcmk_ok) {
         crm_err("Update %d FAILED: %s", call_id, pcmk_strerror(rc));
     }
 }
 
 /*!
  * \brief Handle a timeout in node-to-node communication
  *
  * \param[in,out] data  Pointer to graph action
  *
  * \return FALSE (indicating that source should be not be re-added)
  */
 gboolean
 action_timer_callback(gpointer data)
 {
     pcmk__graph_action_t *action = (pcmk__graph_action_t *) data;
     const char *task = NULL;
     const char *on_node = NULL;
     const char *via_node = NULL;
 
     CRM_CHECK(data != NULL, return FALSE);
 
     stop_te_timer(action);
 
     task = crm_element_value(action->xml, PCMK_XA_OPERATION);
     on_node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     via_node = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE);
 
     if (controld_globals.transition_graph->complete) {
         crm_notice("Node %s did not send %s result (via %s) within %dms "
                    "(ignoring because transition not in progress)",
                    (on_node? on_node : ""), (task? task : "unknown action"),
                    (via_node? via_node : "controller"), action->timeout);
     } else {
         /* fail the action */
 
         crm_err("Node %s did not send %s result (via %s) within %dms "
                 "(action timeout plus " PCMK_OPT_CLUSTER_DELAY ")",
                 (on_node? on_node : ""), (task? task : "unknown action"),
                 (via_node? via_node : "controller"),
                 (action->timeout
                  + controld_globals.transition_graph->network_delay));
         pcmk__log_graph_action(LOG_ERR, action);
 
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
 
         te_action_confirmed(action, controld_globals.transition_graph);
-        abort_transition(INFINITY, pcmk__graph_restart, "Action lost", NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Action lost", NULL);
 
         // Record timeout in the CIB if appropriate
         if ((action->type == pcmk__rsc_graph_action)
             && controld_action_is_recordable(task)) {
             controld_record_action_timeout(action);
         }
     }
 
     return FALSE;
 }
diff --git a/daemons/controld/controld_te_events.c b/daemons/controld/controld_te_events.c
index 6f71b80441..93bd95cb2d 100644
--- a/daemons/controld/controld_te_events.c
+++ b/daemons/controld/controld_te_events.c
@@ -1,611 +1,614 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <sys/param.h>
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/common/xml.h>
 
 #include <pacemaker-controld.h>
 
 #include <crm/common/attrs_internal.h>
 #include <crm/common/ipc_attrd_internal.h>
 
 /*!
  * \internal
  * \brief Action numbers of outside events processed in current update diff
  *
  * This table is to be used as a set. It should be empty when the transitioner
  * begins processing a CIB update diff. It ensures that if there are multiple
  * events (for example, "_last_0" and "_last_failure_0") for the same action,
  * only one of them updates the failcount. Events that originate outside the
  * cluster can't be confirmed, since they're not in the transition graph.
  */
 static GHashTable *outside_events = NULL;
 
 /*!
  * \internal
  * \brief Empty the hash table containing action numbers of outside events
  */
 void
 controld_remove_all_outside_events(void)
 {
     if (outside_events != NULL) {
         g_hash_table_remove_all(outside_events);
     }
 }
 
 /*!
  * \internal
  * \brief Destroy the hash table containing action numbers of outside events
  */
 void
 controld_destroy_outside_events_table(void)
 {
     if (outside_events != NULL) {
         g_hash_table_destroy(outside_events);
         outside_events = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Add an outside event's action number to a set
  *
  * \return Standard Pacemaker return code. Specifically, \p pcmk_rc_ok if the
  *         event was not already in the set, or \p pcmk_rc_already otherwise.
  */
 static int
 record_outside_event(gint action_num)
 {
     if (outside_events == NULL) {
         outside_events = g_hash_table_new(NULL, NULL);
     }
 
     if (g_hash_table_add(outside_events, GINT_TO_POINTER(action_num))) {
         return pcmk_rc_ok;
     }
     return pcmk_rc_already;
 }
 
 gboolean
 fail_incompletable_actions(pcmk__graph_t *graph, const char *down_node)
 {
     const char *target_uuid = NULL;
     const char *router = NULL;
     const char *router_uuid = NULL;
     xmlNode *last_action = NULL;
 
     GList *gIter = NULL;
     GList *gIter2 = NULL;
 
     if (graph == NULL || graph->complete) {
         return FALSE;
     }
 
     gIter = graph->synapses;
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) gIter->data;
 
         if (pcmk_any_flags_set(synapse->flags, pcmk__synapse_confirmed|pcmk__synapse_failed)) {
             /* We've already been here */
             continue;
         }
 
         gIter2 = synapse->actions;
         for (; gIter2 != NULL; gIter2 = gIter2->next) {
             pcmk__graph_action_t *action = (pcmk__graph_action_t *) gIter2->data;
 
             if ((action->type == pcmk__pseudo_graph_action)
                 || pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
                 continue;
             } else if (action->type == pcmk__cluster_graph_action) {
                 const char *task = crm_element_value(action->xml,
                                                      PCMK_XA_OPERATION);
 
                 if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_casei)) {
                     continue;
                 }
             }
 
             target_uuid = crm_element_value(action->xml,
                                             PCMK__META_ON_NODE_UUID);
             router = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE);
             if (router) {
                 crm_node_t *node = pcmk__get_node(0, router, NULL,
                                                   pcmk__node_search_cluster);
 
                 if (node) {
                     router_uuid = node->uuid;
                 }
             }
 
             if (pcmk__str_eq(target_uuid, down_node, pcmk__str_casei) || pcmk__str_eq(router_uuid, down_node, pcmk__str_casei)) {
                 pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
                 pcmk__set_synapse_flags(synapse, pcmk__synapse_failed);
                 last_action = action->xml;
                 stop_te_timer(action);
                 pcmk__update_graph(graph, action);
 
                 if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
                     crm_notice("Action %d (%s) was pending on %s (offline)",
                                action->id,
                                crm_element_value(action->xml,
                                                  PCMK__XA_OPERATION_KEY),
                                down_node);
                 } else {
                     crm_info("Action %d (%s) is scheduled for %s (offline)",
                              action->id,
                              crm_element_value(action->xml, PCMK__XA_OPERATION_KEY),
                              down_node);
                 }
             }
         }
     }
 
     if (last_action != NULL) {
         crm_info("Node %s shutdown resulted in un-runnable actions", down_node);
-        abort_transition(INFINITY, pcmk__graph_restart, "Node failure",
-                         last_action);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Node failure", last_action);
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Update failure-related node attributes if warranted
  *
  * \param[in] event            XML describing operation that (maybe) failed
  * \param[in] event_node_uuid  Node that event occurred on
  * \param[in] rc               Actual operation return code
  * \param[in] target_rc        Expected operation return code
  * \param[in] do_update        If TRUE, do update regardless of operation type
  * \param[in] ignore_failures  If TRUE, update last failure but not fail count
  *
  * \return TRUE if this was not a direct nack, success or lrm status refresh
  */
 static gboolean
 update_failcount(const xmlNode *event, const char *event_node_uuid, int rc,
                  int target_rc, gboolean do_update, gboolean ignore_failures)
 {
     guint interval_ms = 0;
 
     char *task = NULL;
     char *rsc_id = NULL;
 
     const char *value = NULL;
     const char *id = crm_element_value(event, PCMK__XA_OPERATION_KEY);
     const char *on_uname = crm_peer_uname(event_node_uuid);
     const char *origin = crm_element_value(event, PCMK_XA_CRM_DEBUG_ORIGIN);
 
     // Nothing needs to be done for success or status refresh
     if (rc == target_rc) {
         return FALSE;
     } else if (pcmk__str_eq(origin, "build_active_RAs", pcmk__str_casei)) {
         crm_debug("No update for %s (rc=%d) on %s: Old failure from lrm status refresh",
                   id, rc, on_uname);
         return FALSE;
     }
 
     /* Sanity check */
     CRM_CHECK(on_uname != NULL, return TRUE);
     CRM_CHECK(parse_op_key(id, &rsc_id, &task, &interval_ms),
               crm_err("Couldn't parse: %s", pcmk__xe_id(event)); goto bail);
 
     /* Decide whether update is necessary and what value to use */
     if ((interval_ms > 0)
         || pcmk__str_eq(task, PCMK_ACTION_PROMOTE, pcmk__str_none)
         || pcmk__str_eq(task, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
         do_update = TRUE;
 
     } else if (pcmk__str_eq(task, PCMK_ACTION_START, pcmk__str_none)) {
         do_update = TRUE;
         value = pcmk__s(controld_globals.transition_graph->failed_start_offset,
                         PCMK_VALUE_INFINITY);
 
     } else if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_none)) {
         do_update = TRUE;
         value = pcmk__s(controld_globals.transition_graph->failed_stop_offset,
                         PCMK_VALUE_INFINITY);
     }
 
     if (do_update) {
         pcmk__attrd_query_pair_t *fail_pair = NULL;
         pcmk__attrd_query_pair_t *last_pair = NULL;
         char *fail_name = NULL;
         char *last_name = NULL;
         GList *attrs = NULL;
 
         uint32_t opts = pcmk__node_attr_none;
 
         char *now = pcmk__ttoa(time(NULL));
 
         // Fail count will be either incremented or set to infinity
         if (!pcmk_str_is_infinity(value)) {
             value = PCMK_XA_VALUE "++";
         }
 
         if (g_hash_table_lookup(crm_remote_peer_cache, event_node_uuid)) {
             opts |= pcmk__node_attr_remote;
         }
 
         crm_info("Updating %s for %s on %s after failed %s: rc=%d (update=%s, time=%s)",
                  (ignore_failures? "last failure" : "failcount"),
                  rsc_id, on_uname, task, rc, value, now);
 
         /* Update the fail count, if we're not ignoring failures */
         if (!ignore_failures) {
             fail_pair = calloc(1, sizeof(pcmk__attrd_query_pair_t));
             CRM_ASSERT(fail_pair != NULL);
 
             fail_name = pcmk__failcount_name(rsc_id, task, interval_ms);
             fail_pair->name = fail_name;
             fail_pair->value = value;
             fail_pair->node = on_uname;
 
             attrs = g_list_prepend(attrs, fail_pair);
         }
 
         /* Update the last failure time (even if we're ignoring failures,
          * so that failure can still be detected and shown, e.g. by crm_mon)
          */
         last_pair = calloc(1, sizeof(pcmk__attrd_query_pair_t));
         CRM_ASSERT(last_pair != NULL);
 
         last_name = pcmk__lastfailure_name(rsc_id, task, interval_ms);
         last_pair->name = last_name;
         last_pair->value = now;
         last_pair->node = on_uname;
 
         attrs = g_list_prepend(attrs, last_pair);
 
         update_attrd_list(attrs, opts);
 
         free(fail_name);
         free(fail_pair);
 
         free(last_name);
         free(last_pair);
         g_list_free(attrs);
 
         free(now);
     }
 
   bail:
     free(rsc_id);
     free(task);
     return TRUE;
 }
 
 pcmk__graph_action_t *
 controld_get_action(int id)
 {
     for (GList *item = controld_globals.transition_graph->synapses;
          item != NULL; item = item->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) item->data;
 
         for (GList *item2 = synapse->actions; item2; item2 = item2->next) {
             pcmk__graph_action_t *action = (pcmk__graph_action_t *) item2->data;
 
             if (action->id == id) {
                 return action;
             }
         }
     }
     return NULL;
 }
 
 pcmk__graph_action_t *
 get_cancel_action(const char *id, const char *node)
 {
     GList *gIter = NULL;
     GList *gIter2 = NULL;
 
     gIter = controld_globals.transition_graph->synapses;
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) gIter->data;
 
         gIter2 = synapse->actions;
         for (; gIter2 != NULL; gIter2 = gIter2->next) {
             const char *task = NULL;
             const char *target = NULL;
             pcmk__graph_action_t *action = (pcmk__graph_action_t *) gIter2->data;
 
             task = crm_element_value(action->xml, PCMK_XA_OPERATION);
             if (!pcmk__str_eq(PCMK_ACTION_CANCEL, task, pcmk__str_casei)) {
                 continue;
             }
 
             task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY);
             if (!pcmk__str_eq(task, id, pcmk__str_casei)) {
                 crm_trace("Wrong key %s for %s on %s", task, id, node);
                 continue;
             }
 
             target = crm_element_value(action->xml, PCMK__META_ON_NODE_UUID);
             if (node && !pcmk__str_eq(target, node, pcmk__str_casei)) {
                 crm_trace("Wrong node %s for %s on %s", target, id, node);
                 continue;
             }
 
             crm_trace("Found %s on %s", id, node);
             return action;
         }
     }
 
     return NULL;
 }
 
 bool
 confirm_cancel_action(const char *id, const char *node_id)
 {
     const char *op_key = NULL;
     const char *node_name = NULL;
     pcmk__graph_action_t *cancel = get_cancel_action(id, node_id);
 
     if (cancel == NULL) {
         return FALSE;
     }
     op_key = crm_element_value(cancel->xml, PCMK__XA_OPERATION_KEY);
     node_name = crm_element_value(cancel->xml, PCMK__META_ON_NODE);
 
     stop_te_timer(cancel);
     te_action_confirmed(cancel, controld_globals.transition_graph);
 
     crm_info("Cancellation of %s on %s confirmed (action %d)",
              op_key, node_name, cancel->id);
     return TRUE;
 }
 
 /* downed nodes are listed like: <downed> <node id="UUID1" /> ... </downed> */
 #define XPATH_DOWNED "//" PCMK__XE_DOWNED \
                      "/" PCMK_XE_NODE "[@" PCMK_XA_ID "='%s']"
 
 /*!
  * \brief Find a transition event that would have made a specified node down
  *
  * \param[in] target  UUID of node to match
  *
  * \return Matching event if found, NULL otherwise
  */
 pcmk__graph_action_t *
 match_down_event(const char *target)
 {
     pcmk__graph_action_t *match = NULL;
     xmlXPathObjectPtr xpath_ret = NULL;
     GList *gIter, *gIter2;
 
     char *xpath = crm_strdup_printf(XPATH_DOWNED, target);
 
     for (gIter = controld_globals.transition_graph->synapses;
          gIter != NULL && match == NULL;
          gIter = gIter->next) {
 
         for (gIter2 = ((pcmk__graph_synapse_t * ) gIter->data)->actions;
              gIter2 != NULL && match == NULL;
              gIter2 = gIter2->next) {
 
             match = (pcmk__graph_action_t *) gIter2->data;
             if (pcmk_is_set(match->flags, pcmk__graph_action_executed)) {
                 xpath_ret = xpath_search(match->xml, xpath);
                 if (numXpathResults(xpath_ret) < 1) {
                     match = NULL;
                 }
                 freeXpathObject(xpath_ret);
             } else {
                 // Only actions that were actually started can match
                 match = NULL;
             }
         }
     }
 
     free(xpath);
 
     if (match != NULL) {
         crm_debug("Shutdown action %d (%s) found for node %s", match->id,
                   crm_element_value(match->xml, PCMK__XA_OPERATION_KEY),
                   target);
     } else {
         crm_debug("No reason to expect node %s to be down", target);
     }
     return match;
 }
 
 void
 process_graph_event(xmlNode *event, const char *event_node)
 {
     int rc = -1;                // Actual result
     int target_rc = -1;         // Expected result
     int status = -1;            // Executor status
     int callid = -1;            // Executor call ID
     int transition_num = -1;    // Transition number
     int action_num = -1;        // Action number within transition
     char *update_te_uuid = NULL;
     bool ignore_failures = FALSE;
     const char *id = NULL;
     const char *desc = NULL;
     const char *magic = NULL;
     const char *uname = NULL;
 
     CRM_ASSERT(event != NULL);
 
 /*
 <lrm_rsc_op id="rsc_east-05_last_0" operation_key="rsc_east-05_monitor_0" operation="monitor" crm-debug-origin="do_update_resource" crm_feature_set="3.0.6" transition-key="9:2:7:be2e97d9-05e2-439d-863e-48f7aecab2aa" transition-magic="0:7;9:2:7:be2e97d9-05e2-439d-863e-48f7aecab2aa" call-id="17" rc-code="7" op-status="0" interval="0" last-rc-change="1355361636" exec-time="128" queue-time="0" op-digest="c81f5f40b1c9e859c992e800b1aa6972"/>
 */
 
     magic = crm_element_value(event, PCMK__XA_TRANSITION_KEY);
     if (magic == NULL) {
         /* non-change */
         return;
     }
 
     crm_element_value_int(event, PCMK__XA_OP_STATUS, &status);
     if (status == PCMK_EXEC_PENDING) {
         return;
     }
 
     id = crm_element_value(event, PCMK__XA_OPERATION_KEY);
     crm_element_value_int(event, PCMK__XA_RC_CODE, &rc);
     crm_element_value_int(event, PCMK__XA_CALL_ID, &callid);
 
     rc = pcmk__effective_rc(rc);
 
     if (decode_transition_key(magic, &update_te_uuid, &transition_num,
                               &action_num, &target_rc) == FALSE) {
         // decode_transition_key() already logged the bad key
         crm_err("Can't process action %s result: Incompatible versions? "
                 CRM_XS " call-id=%d", id, callid);
-        abort_transition(INFINITY, pcmk__graph_restart, "Bad event", event);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Bad event", event);
         return;
     }
 
     if (transition_num == -1) {
         // E.g. crm_resource --fail
         if (record_outside_event(action_num) != pcmk_rc_ok) {
             crm_debug("Outside event with transition key '%s' has already been "
                       "processed", magic);
             goto bail;
         }
         desc = "initiated outside of the cluster";
-        abort_transition(INFINITY, pcmk__graph_restart, "Unexpected event",
-                         event);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Unexpected event", event);
 
     } else if ((action_num < 0)
                || !pcmk__str_eq(update_te_uuid, controld_globals.te_uuid,
                                 pcmk__str_none)) {
         desc = "initiated by a different DC";
-        abort_transition(INFINITY, pcmk__graph_restart, "Foreign event", event);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Foreign event", event);
 
     } else if ((controld_globals.transition_graph->id != transition_num)
                || controld_globals.transition_graph->complete) {
 
         // Action is not from currently active transition
 
         guint interval_ms = 0;
 
         if (parse_op_key(id, NULL, NULL, &interval_ms)
             && (interval_ms != 0)) {
             /* Recurring actions have the transition number they were first
              * scheduled in.
              */
 
             if (status == PCMK_EXEC_CANCELLED) {
                 confirm_cancel_action(id, get_node_id(event));
                 goto bail;
             }
 
             desc = "arrived after initial scheduling";
-            abort_transition(INFINITY, pcmk__graph_restart,
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                              "Change in recurring result", event);
 
         } else if (controld_globals.transition_graph->id != transition_num) {
             desc = "arrived really late";
-            abort_transition(INFINITY, pcmk__graph_restart, "Old event", event);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Old event", event);
         } else {
             desc = "arrived late";
-            abort_transition(INFINITY, pcmk__graph_restart, "Inactive graph",
-                             event);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Inactive graph", event);
         }
 
     } else {
         // Event is result of an action from currently active transition
         pcmk__graph_action_t *action = controld_get_action(action_num);
 
         if (action == NULL) {
             // Should never happen
             desc = "unknown";
-            abort_transition(INFINITY, pcmk__graph_restart, "Unknown event",
-                             event);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Unknown event", event);
 
         } else if (pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
             /* Nothing further needs to be done if the action has already been
              * confirmed. This can happen e.g. when processing both an
              * "xxx_last_0" or "xxx_last_failure_0" record as well as the main
              * history record, which would otherwise result in incorrectly
              * bumping the fail count twice.
              */
             crm_log_xml_debug(event, "Event already confirmed:");
             goto bail;
 
         } else {
             /* An action result needs to be confirmed.
              * (This is the only case where desc == NULL.)
              */
 
             if (pcmk__str_eq(crm_meta_value(action->params, PCMK_META_ON_FAIL),
                              PCMK_VALUE_IGNORE, pcmk__str_casei)) {
                 ignore_failures = TRUE;
 
             } else if (rc != target_rc) {
                 pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
             }
 
             stop_te_timer(action);
             te_action_confirmed(action, controld_globals.transition_graph);
 
             if (pcmk_is_set(action->flags, pcmk__graph_action_failed)) {
                 abort_transition(action->synapse->priority + 1,
                                  pcmk__graph_restart, "Event failed", event);
             }
         }
     }
 
     if (id == NULL) {
         id = "unknown action";
     }
     uname = crm_element_value(event, PCMK__META_ON_NODE);
     if (uname == NULL) {
         uname = "unknown node";
     }
 
     if (status == PCMK_EXEC_INVALID) {
         // We couldn't attempt the action
         crm_info("Transition %d action %d (%s on %s): %s",
                  transition_num, action_num, id, uname,
                  pcmk_exec_status_str(status));
 
     } else if (desc && update_failcount(event, event_node, rc, target_rc,
                                         (transition_num == -1), FALSE)) {
         crm_notice("Transition %d action %d (%s on %s): expected '%s' but got '%s' "
                    CRM_XS " target-rc=%d rc=%d call-id=%d event='%s'",
                    transition_num, action_num, id, uname,
                    services_ocf_exitcode_str(target_rc),
                    services_ocf_exitcode_str(rc),
                    target_rc, rc, callid, desc);
 
     } else if (desc) {
         crm_info("Transition %d action %d (%s on %s): %s "
                  CRM_XS " rc=%d target-rc=%d call-id=%d",
                  transition_num, action_num, id, uname,
                  desc, rc, target_rc, callid);
 
     } else if (rc == target_rc) {
         crm_info("Transition %d action %d (%s on %s) confirmed: %s "
                  CRM_XS " rc=%d call-id=%d",
                  transition_num, action_num, id, uname,
                  services_ocf_exitcode_str(rc), rc, callid);
 
     } else {
         update_failcount(event, event_node, rc, target_rc,
                          (transition_num == -1), ignore_failures);
         crm_notice("Transition %d action %d (%s on %s): expected '%s' but got '%s' "
                    CRM_XS " target-rc=%d rc=%d call-id=%d",
                    transition_num, action_num, id, uname,
                    services_ocf_exitcode_str(target_rc),
                    services_ocf_exitcode_str(rc),
                    target_rc, rc, callid);
     }
 
   bail:
     free(update_te_uuid);
 }
diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c
index 39b03789d5..c0c7bea1ba 100644
--- a/daemons/controld/controld_te_utils.c
+++ b/daemons/controld/controld_te_utils.c
@@ -1,509 +1,509 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 
 #include <pacemaker-controld.h>
 
 //! Triggers transition graph processing
 static crm_trigger_t *transition_trigger = NULL;
 
 static GHashTable *node_pending_timers = NULL;
 
 gboolean
 stop_te_timer(pcmk__graph_action_t *action)
 {
     if (action == NULL) {
         return FALSE;
     }
     if (action->timer != 0) {
         crm_trace("Stopping action timer");
         g_source_remove(action->timer);
         action->timer = 0;
     } else {
         crm_trace("Action timer was already stopped");
         return FALSE;
     }
     return TRUE;
 }
 
 static gboolean
 te_graph_trigger(gpointer user_data)
 {
     if (controld_globals.transition_graph == NULL) {
         crm_debug("Nothing to do");
         return TRUE;
     }
 
     crm_trace("Invoking graph %d in state %s",
               controld_globals.transition_graph->id,
               fsa_state2string(controld_globals.fsa_state));
 
     switch (controld_globals.fsa_state) {
         case S_STARTING:
         case S_PENDING:
         case S_NOT_DC:
         case S_HALT:
         case S_ILLEGAL:
         case S_STOPPING:
         case S_TERMINATE:
             return TRUE;
         default:
             break;
     }
 
     if (!controld_globals.transition_graph->complete) {
         enum pcmk__graph_status graph_rc;
         int orig_limit = controld_globals.transition_graph->batch_limit;
         int throttled_limit = throttle_get_total_job_limit(orig_limit);
 
         controld_globals.transition_graph->batch_limit = throttled_limit;
         graph_rc = pcmk__execute_graph(controld_globals.transition_graph);
         controld_globals.transition_graph->batch_limit = orig_limit;
 
         if (graph_rc == pcmk__graph_active) {
             crm_trace("Transition not yet complete");
             return TRUE;
 
         } else if (graph_rc == pcmk__graph_pending) {
             crm_trace("Transition not yet complete - no actions fired");
             return TRUE;
         }
 
         if (graph_rc != pcmk__graph_complete) {
             crm_warn("Transition failed: %s",
                      pcmk__graph_status2text(graph_rc));
             pcmk__log_graph(LOG_NOTICE, controld_globals.transition_graph);
         }
     }
 
     crm_debug("Transition %d is now complete",
               controld_globals.transition_graph->id);
     controld_globals.transition_graph->complete = true;
     notify_crmd(controld_globals.transition_graph);
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Initialize transition trigger
  */
 void
 controld_init_transition_trigger(void)
 {
     transition_trigger = mainloop_add_trigger(G_PRIORITY_LOW, te_graph_trigger,
                                               NULL);
 }
 
 /*!
  * \internal
  * \brief Destroy transition trigger
  */
 void
 controld_destroy_transition_trigger(void)
 {
     mainloop_destroy_trigger(transition_trigger);
     transition_trigger = NULL;
 }
 
 void
 controld_trigger_graph_as(const char *fn, int line)
 {
     crm_trace("%s:%d - Triggered graph processing", fn, line);
     mainloop_set_trigger(transition_trigger);
 }
 
 static struct abort_timer_s {
     bool aborted;
     guint id;
     int priority;
     enum pcmk__graph_next action;
     const char *text;
 } abort_timer = { 0, };
 
 static gboolean
 abort_timer_popped(gpointer data)
 {
     struct abort_timer_s *abort_timer = (struct abort_timer_s *) data;
 
     if (AM_I_DC && (abort_timer->aborted == FALSE)) {
         abort_transition(abort_timer->priority, abort_timer->action,
                          abort_timer->text, NULL);
     }
     abort_timer->id = 0;
     return FALSE; // do not immediately reschedule timer
 }
 
 /*!
  * \internal
  * \brief Abort transition after delay, if not already aborted in that time
  *
  * \param[in] abort_text  Must be literal string
  */
 void
 abort_after_delay(int abort_priority, enum pcmk__graph_next abort_action,
                   const char *abort_text, guint delay_ms)
 {
     if (abort_timer.id) {
         // Timer already in progress, stop and reschedule
         g_source_remove(abort_timer.id);
     }
     abort_timer.aborted = FALSE;
     abort_timer.priority = abort_priority;
     abort_timer.action = abort_action;
     abort_timer.text = abort_text;
     abort_timer.id = g_timeout_add(delay_ms, abort_timer_popped, &abort_timer);
 }
 
 static void
 free_node_pending_timer(gpointer data)
 {
     struct abort_timer_s *node_pending_timer = (struct abort_timer_s *) data;
 
     if (node_pending_timer->id != 0) {
         g_source_remove(node_pending_timer->id);
         node_pending_timer->id = 0;
     }
 
     free(node_pending_timer);
 }
 
 static gboolean
 node_pending_timer_popped(gpointer key)
 {
     struct abort_timer_s *node_pending_timer = NULL;
 
     if (node_pending_timers == NULL) {
         return FALSE;
     }
 
     node_pending_timer = g_hash_table_lookup(node_pending_timers, key);
     if (node_pending_timer == NULL) {
         return FALSE;
     }
 
     crm_warn("Node with id '%s' pending timed out (%us) on joining the process "
              "group",
              (const char *) key, controld_globals.node_pending_timeout);
 
     if (controld_globals.node_pending_timeout > 0) {
         abort_timer_popped(node_pending_timer);
     }
 
     g_hash_table_remove(node_pending_timers, key);
 
     return FALSE; // do not reschedule timer
 }
 
 static void
 init_node_pending_timer(const crm_node_t *node, guint timeout)
 {
     struct abort_timer_s *node_pending_timer = NULL;
     char *key = NULL;
 
     if (node->uuid == NULL) {
         return;
     }
 
     if (node_pending_timers == NULL) {
         node_pending_timers = pcmk__strikey_table(free,
                                                   free_node_pending_timer);
 
     // The timer is somehow already existing
     } else if (g_hash_table_lookup(node_pending_timers, node->uuid) != NULL) {
         return;
     }
 
     crm_notice("Waiting for pending %s with id '%s' to join the process "
                "group (timeout=%us)",
                node->uname ? node->uname : "node", node->uuid,
                controld_globals.node_pending_timeout);
 
     node_pending_timer = calloc(1, sizeof(struct abort_timer_s));
     CRM_ASSERT(node_pending_timer != NULL);
 
     node_pending_timer->aborted = FALSE;
-    node_pending_timer->priority = INFINITY;
+    node_pending_timer->priority = PCMK_SCORE_INFINITY;
     node_pending_timer->action = pcmk__graph_restart;
     node_pending_timer->text = "Node pending timed out";
 
     key = strdup(node->uuid);
     CRM_ASSERT(key != NULL);
 
     g_hash_table_replace(node_pending_timers, key, node_pending_timer);
 
     node_pending_timer->id = g_timeout_add_seconds(timeout,
                                                    node_pending_timer_popped,
                                                    key);
     CRM_ASSERT(node_pending_timer->id != 0);
 }
 
 static void
 remove_node_pending_timer(const char *node_uuid)
 {
     if (node_pending_timers == NULL) {
         return;
     }
 
     g_hash_table_remove(node_pending_timers, node_uuid);
 }
 
 void
 controld_node_pending_timer(const crm_node_t *node)
 {
     long long remaining_timeout = 0;
 
     /* If the node is not an active cluster node, is leaving the cluster, or is
      * already part of CPG, or PCMK_OPT_NODE_PENDING_TIMEOUT is disabled, free
      * any node pending timer for it.
      */
     if (pcmk_is_set(node->flags, crm_remote_node)
         || (node->when_member <= 1) || (node->when_online > 0)
         || (controld_globals.node_pending_timeout == 0)) {
         remove_node_pending_timer(node->uuid);
         return;
     }
 
     // Node is a cluster member but offline in CPG
 
     remaining_timeout = node->when_member - time(NULL)
                         + controld_globals.node_pending_timeout;
 
     /* It already passed node pending timeout somehow.
      * Free any node pending timer of it.
      */
     if (remaining_timeout <= 0) {
         remove_node_pending_timer(node->uuid);
         return;
     }
 
     init_node_pending_timer(node, remaining_timeout);
 }
 
 void
 controld_free_node_pending_timers(void)
 {
     if (node_pending_timers == NULL) {
         return;
     }
 
     g_hash_table_destroy(node_pending_timers);
     node_pending_timers = NULL;
 }
 
 static const char *
 abort2text(enum pcmk__graph_next abort_action)
 {
     switch (abort_action) {
         case pcmk__graph_done:      return "done";
         case pcmk__graph_wait:      return "stop";
         case pcmk__graph_restart:   return "restart";
         case pcmk__graph_shutdown:  return "shutdown";
     }
     return "unknown";
 }
 
 static bool
 update_abort_priority(pcmk__graph_t *graph, int priority,
                       enum pcmk__graph_next action, const char *abort_reason)
 {
     bool change = FALSE;
 
     if (graph == NULL) {
         return change;
     }
 
     if (graph->abort_priority < priority) {
         crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority);
         graph->abort_priority = priority;
         if (graph->abort_reason != NULL) {
             crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason);
         }
         graph->abort_reason = abort_reason;
         change = TRUE;
     }
 
     if (graph->completion_action < action) {
         crm_debug("Abort action %s superseded by %s: %s",
                   abort2text(graph->completion_action), abort2text(action), abort_reason);
         graph->completion_action = action;
         change = TRUE;
     }
 
     return change;
 }
 
 void
 abort_transition_graph(int abort_priority, enum pcmk__graph_next abort_action,
                        const char *abort_text, const xmlNode *reason,
                        const char *fn, int line)
 {
     int add[] = { 0, 0, 0 };
     int del[] = { 0, 0, 0 };
     int level = LOG_INFO;
     const xmlNode *diff = NULL;
     const xmlNode *change = NULL;
 
     CRM_CHECK(controld_globals.transition_graph != NULL, return);
 
     switch (controld_globals.fsa_state) {
         case S_STARTING:
         case S_PENDING:
         case S_NOT_DC:
         case S_HALT:
         case S_ILLEGAL:
         case S_STOPPING:
         case S_TERMINATE:
             crm_info("Abort %s suppressed: state=%s (%scomplete)",
                      abort_text, fsa_state2string(controld_globals.fsa_state),
                      (controld_globals.transition_graph->complete? "" : "in"));
             return;
         default:
             break;
     }
 
     abort_timer.aborted = TRUE;
     controld_expect_sched_reply(NULL);
 
     if (!controld_globals.transition_graph->complete
         && update_abort_priority(controld_globals.transition_graph,
                                  abort_priority, abort_action,
                                  abort_text)) {
         level = LOG_NOTICE;
     }
 
     if (reason != NULL) {
         const xmlNode *search = NULL;
 
         for(search = reason; search; search = search->parent) {
             if (pcmk__xe_is(search, PCMK_XE_DIFF)) {
                 diff = search;
                 break;
             }
         }
 
         if(diff) {
             xml_patch_versions(diff, add, del);
             for(search = reason; search; search = search->parent) {
                 if (pcmk__xe_is(search, PCMK_XE_CHANGE)) {
                     change = search;
                     break;
                 }
             }
         }
     }
 
     if (reason == NULL) {
         do_crm_log(level,
                    "Transition %d aborted: %s " CRM_XS " source=%s:%d "
                    "complete=%s", controld_globals.transition_graph->id,
                    abort_text, fn, line,
                    pcmk__btoa(controld_globals.transition_graph->complete));
 
     } else if(change == NULL) {
         GString *local_path = pcmk__element_xpath(reason);
         CRM_ASSERT(local_path != NULL);
 
         do_crm_log(level, "Transition %d aborted by %s.%s: %s "
                    CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
                    controld_globals.transition_graph->id, reason->name,
                    pcmk__xe_id(reason), abort_text, add[0], add[1], add[2], fn,
                    line, (const char *) local_path->str,
                    pcmk__btoa(controld_globals.transition_graph->complete));
         g_string_free(local_path, TRUE);
 
     } else {
         const char *op = crm_element_value(change, PCMK_XA_OPERATION);
         const char *path = crm_element_value(change, PCMK_XA_PATH);
 
         if(change == reason) {
             if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
                 reason = reason->children;
 
             } else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
                 reason = first_named_child(reason, PCMK_XE_CHANGE_RESULT);
                 if(reason) {
                     reason = reason->children;
                 }
             }
             CRM_CHECK(reason != NULL, goto done);
         }
 
         if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
             const char *shortpath = strrchr(path, '/');
 
             do_crm_log(level, "Transition %d aborted by deletion of %s: %s "
                        CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
                        controld_globals.transition_graph->id,
                        (shortpath? (shortpath + 1) : path), abort_text,
                        add[0], add[1], add[2], fn, line, path,
                        pcmk__btoa(controld_globals.transition_graph->complete));
 
         } else if (pcmk__xe_is(reason, PCMK_XE_NVPAIR)) {
             do_crm_log(level, "Transition %d aborted by %s doing %s %s=%s: %s "
                        CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
                        controld_globals.transition_graph->id,
                        crm_element_value(reason, PCMK_XA_ID), op,
                        crm_element_value(reason, PCMK_XA_NAME),
                        crm_element_value(reason, PCMK_XA_VALUE),
                        abort_text, add[0], add[1], add[2], fn, line, path,
                        pcmk__btoa(controld_globals.transition_graph->complete));
 
         } else if (pcmk__xe_is(reason, PCMK__XE_LRM_RSC_OP)) {
             const char *magic = crm_element_value(reason,
                                                   PCMK__XA_TRANSITION_MAGIC);
 
             do_crm_log(level, "Transition %d aborted by operation %s '%s' on %s: %s "
                        CRM_XS " magic=%s cib=%d.%d.%d source=%s:%d complete=%s",
                        controld_globals.transition_graph->id,
                        crm_element_value(reason, PCMK__XA_OPERATION_KEY), op,
                        crm_element_value(reason, PCMK__META_ON_NODE),
                        abort_text,
                        magic, add[0], add[1], add[2], fn, line,
                        pcmk__btoa(controld_globals.transition_graph->complete));
 
         } else if (pcmk__str_any_of((const char *) reason->name,
                    PCMK__XE_NODE_STATE, PCMK_XE_NODE, NULL)) {
             const char *uname = crm_peer_uname(pcmk__xe_id(reason));
 
             do_crm_log(level, "Transition %d aborted by %s '%s' on %s: %s "
                        CRM_XS " cib=%d.%d.%d source=%s:%d complete=%s",
                        controld_globals.transition_graph->id,
                        reason->name, op, pcmk__s(uname, pcmk__xe_id(reason)),
                        abort_text, add[0], add[1], add[2], fn, line,
                        pcmk__btoa(controld_globals.transition_graph->complete));
 
         } else {
             const char *id = pcmk__xe_id(reason);
 
             do_crm_log(level, "Transition %d aborted by %s.%s '%s': %s "
                        CRM_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
                        controld_globals.transition_graph->id,
                        reason->name, pcmk__s(id, ""), pcmk__s(op, "change"),
                        abort_text, add[0], add[1], add[2], fn, line, path,
                        pcmk__btoa(controld_globals.transition_graph->complete));
         }
     }
 
 done:
     if (controld_globals.transition_graph->complete) {
         if (controld_get_period_transition_timer() > 0) {
             controld_stop_transition_timer();
             controld_start_transition_timer();
         } else {
             register_fsa_input(C_FSA_INTERNAL, I_PE_CALC, NULL);
         }
         return;
     }
 
     trigger_graph();
 }
diff --git a/daemons/controld/controld_transition.c b/daemons/controld/controld_transition.c
index 241a6c81eb..6a17f6e45b 100644
--- a/daemons/controld/controld_transition.c
+++ b/daemons/controld/controld_transition.c
@@ -1,181 +1,183 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 
 #include <pacemaker-controld.h>
 
 static pcmk__graph_t *
 create_blank_graph(void)
 {
     pcmk__graph_t *a_graph = pcmk__unpack_graph(NULL, NULL);
 
     a_graph->complete = true;
     a_graph->abort_reason = "DC Takeover";
     a_graph->completion_action = pcmk__graph_restart;
     return a_graph;
 }
 
 /*	 A_TE_START, A_TE_STOP, O_TE_RESTART	*/
 void
 do_te_control(long long action,
               enum crmd_fsa_cause cause,
               enum crmd_fsa_state cur_state,
               enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
     cib_t *cib_conn = controld_globals.cib_conn;
     gboolean init_ok = TRUE;
 
     if (pcmk_is_set(action, A_TE_STOP)) {
         pcmk__free_graph(controld_globals.transition_graph);
         controld_globals.transition_graph = NULL;
 
         if (cib_conn != NULL) {
             cib_conn->cmds->del_notify_callback(cib_conn, T_CIB_DIFF_NOTIFY,
                                                 te_update_diff);
         }
 
         controld_clear_fsa_input_flags(R_TE_CONNECTED);
         crm_info("Transitioner is now inactive");
     }
 
     if ((action & A_TE_START) == 0) {
         return;
 
     } else if (pcmk_is_set(controld_globals.fsa_input_register,
                            R_TE_CONNECTED)) {
         crm_debug("The transitioner is already active");
         return;
 
     } else if ((action & A_TE_START) && cur_state == S_STOPPING) {
         crm_info("Ignoring request to start the transitioner while shutting down");
         return;
     }
 
     if (controld_globals.te_uuid == NULL) {
         controld_globals.te_uuid = crm_generate_uuid();
         crm_info("Registering TE UUID: %s", controld_globals.te_uuid);
     }
 
     if (cib_conn == NULL) {
         crm_err("Could not set CIB callbacks");
         init_ok = FALSE;
 
     } else {
         if (cib_conn->cmds->add_notify_callback(cib_conn, T_CIB_DIFF_NOTIFY,
                                                 te_update_diff) != pcmk_ok) {
             crm_err("Could not set CIB notification callback");
             init_ok = FALSE;
         }
     }
 
     if (init_ok) {
         controld_register_graph_functions();
         pcmk__free_graph(controld_globals.transition_graph);
 
         /* create a blank one */
         crm_debug("Transitioner is now active");
         controld_globals.transition_graph = create_blank_graph();
         controld_set_fsa_input_flags(R_TE_CONNECTED);
     }
 }
 
 /*	 A_TE_INVOKE, A_TE_CANCEL	*/
 void
 do_te_invoke(long long action,
              enum crmd_fsa_cause cause,
              enum crmd_fsa_state cur_state,
              enum crmd_fsa_input current_input, fsa_data_t * msg_data)
 {
 
     if (!AM_I_DC
         || ((controld_globals.fsa_state != S_TRANSITION_ENGINE)
             && pcmk_is_set(action, A_TE_INVOKE))) {
         crm_notice("No need to invoke the TE (%s) in state %s",
                    fsa_action2string(action),
                    fsa_state2string(controld_globals.fsa_state));
         return;
     }
 
     if (action & A_TE_CANCEL) {
         crm_debug("Cancelling the transition: %sactive",
                   controld_globals.transition_graph->complete? "in" : "");
-        abort_transition(INFINITY, pcmk__graph_restart, "Peer Cancelled", NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                         "Peer Cancelled", NULL);
         if (!controld_globals.transition_graph->complete) {
             crmd_fsa_stall(FALSE);
         }
 
     } else if (action & A_TE_HALT) {
-        abort_transition(INFINITY, pcmk__graph_wait, "Peer Halt", NULL);
+        abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_wait, "Peer Halt",
+                         NULL);
         if (!controld_globals.transition_graph->complete) {
             crmd_fsa_stall(FALSE);
         }
 
     } else if (action & A_TE_INVOKE) {
         ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg);
         xmlNode *graph_data = input->xml;
         const char *ref = crm_element_value(input->msg, PCMK_XA_REFERENCE);
         const char *graph_input = crm_element_value(input->msg,
                                                     PCMK__XA_CRM_TGRAPH_IN);
 
         if (graph_data == NULL) {
             crm_log_xml_err(input->msg, "Bad command");
             register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
             return;
         }
 
         if (!controld_globals.transition_graph->complete) {
             crm_info("Another transition is already active");
-            abort_transition(INFINITY, pcmk__graph_restart, "Transition Active",
-                             NULL);
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
+                             "Transition Active", NULL);
             return;
         }
 
         if ((controld_globals.fsa_pe_ref == NULL)
             || !pcmk__str_eq(controld_globals.fsa_pe_ref, ref,
                              pcmk__str_none)) {
             crm_info("Transition is redundant: %s expected but %s received",
                      pcmk__s(controld_globals.fsa_pe_ref, "no reference"),
                      pcmk__s(ref, "no reference"));
-            abort_transition(INFINITY, pcmk__graph_restart,
+            abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
                              "Transition Redundant", NULL);
         }
 
         if (controld_is_started_transition_timer()) {
             crm_debug("The transitioner wait for a transition timer");
             return;
         }
 
         CRM_CHECK(graph_data != NULL,
                   crm_err("Input raised by %s is invalid", msg_data->origin);
                   crm_log_xml_err(input->msg, "Bad command");
                   return);
 
         pcmk__free_graph(controld_globals.transition_graph);
         controld_globals.transition_graph = pcmk__unpack_graph(graph_data,
                                                                graph_input);
         CRM_CHECK(controld_globals.transition_graph != NULL,
                   controld_globals.transition_graph = create_blank_graph();
                   return);
         crm_info("Processing graph %d (ref=%s) derived from %s",
                  controld_globals.transition_graph->id, ref, graph_input);
 
         te_reset_job_counts();
 
         trigger_graph();
         pcmk__log_graph(LOG_TRACE, controld_globals.transition_graph);
 
         if (graph_data != input->xml) {
             free_xml(graph_data);
         }
     }
 }
diff --git a/include/crm/common/scores.h b/include/crm/common/scores.h
index 6fd333fa8d..4b73f6610d 100644
--- a/include/crm/common/scores.h
+++ b/include/crm/common/scores.h
@@ -1,30 +1,33 @@
 /*
  * 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.
  */
 
 #ifndef PCMK__CRM_COMMON_SCORES__H
 #define PCMK__CRM_COMMON_SCORES__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /**
  * \file
  * \brief Pacemaker APIs related to scores
  * \ingroup core
  */
 
+//! Integer score to use to represent "infinity"
+#define PCMK_SCORE_INFINITY 1000000
+
 const char *pcmk_readable_score(int score);
 int char2score(const char *score);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_SCORES__H
diff --git a/lib/common/scores.c b/lib/common/scores.c
index 0f3047b911..33da7ee4b6 100644
--- a/lib/common/scores.c
+++ b/lib/common/scores.c
@@ -1,166 +1,166 @@
 /*
  * 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 <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <stdio.h>      // snprintf(), NULL
 #include <string.h>     // strcpy(), strdup()
 #include <sys/types.h>  // size_t
 
 int pcmk__score_red = 0;
 int pcmk__score_green = 0;
 int pcmk__score_yellow = 0;
 
 /*!
  * \brief Get the integer value of a score string
  *
  * Given a string representation of a score, return the integer equivalent.
  * This accepts infinity strings as well as red, yellow, and green, and
  * bounds the result to +/-INFINITY.
  *
  * \param[in] score  Score as string
  *
  * \return Integer value corresponding to \p score
  */
 int
 char2score(const char *score)
 {
     if (score == NULL) {
         return 0;
 
     } else if (pcmk_str_is_minus_infinity(score)) {
-        return -CRM_SCORE_INFINITY;
+        return -PCMK_SCORE_INFINITY;
 
     } else if (pcmk_str_is_infinity(score)) {
-        return CRM_SCORE_INFINITY;
+        return PCMK_SCORE_INFINITY;
 
     } else if (pcmk__str_eq(score, PCMK_VALUE_RED, pcmk__str_casei)) {
         return pcmk__score_red;
 
     } else if (pcmk__str_eq(score, PCMK_VALUE_YELLOW, pcmk__str_casei)) {
         return pcmk__score_yellow;
 
     } else if (pcmk__str_eq(score, PCMK_VALUE_GREEN, pcmk__str_casei)) {
         return pcmk__score_green;
 
     } else {
         long long score_ll;
 
         pcmk__scan_ll(score, &score_ll, 0LL);
-        if (score_ll > CRM_SCORE_INFINITY) {
-            return CRM_SCORE_INFINITY;
+        if (score_ll > PCMK_SCORE_INFINITY) {
+            return PCMK_SCORE_INFINITY;
 
-        } else if (score_ll < -CRM_SCORE_INFINITY) {
-            return -CRM_SCORE_INFINITY;
+        } else if (score_ll < -PCMK_SCORE_INFINITY) {
+            return -PCMK_SCORE_INFINITY;
 
         } else {
             return (int) score_ll;
         }
     }
 }
 
 /*!
  * \brief Return a displayable static string for a score value
  *
  * Given a score value, return a pointer to a static string representation of
  * the score suitable for log messages, output, etc.
  *
  * \param[in] score  Score to display
  *
  * \return Pointer to static memory containing string representation of \p score
  * \note Subsequent calls to this function will overwrite the returned value, so
  *       it should be used only in a local context such as a printf()-style
  *       statement.
  */
 const char *
 pcmk_readable_score(int score)
 {
     // The longest possible result is "-INFINITY"
     static char score_s[sizeof(PCMK_VALUE_MINUS_INFINITY)];
 
-    if (score >= CRM_SCORE_INFINITY) {
+    if (score >= PCMK_SCORE_INFINITY) {
         strcpy(score_s, PCMK_VALUE_INFINITY);
 
-    } else if (score <= -CRM_SCORE_INFINITY) {
+    } else if (score <= -PCMK_SCORE_INFINITY) {
         strcpy(score_s, PCMK_VALUE_MINUS_INFINITY);
 
     } else {
         // Range is limited to +/-1000000, so no chance of overflow
         snprintf(score_s, sizeof(score_s), "%d", score);
     }
 
     return score_s;
 }
 
 /*!
  * \internal
  * \brief Add two scores, bounding to +/-INFINITY
  *
  * \param[in] score1  First score to add
  * \param[in] score2  Second score to add
  *
  * \note This function does not have context about what the scores mean, so it
  *       does not log any messages.
  */
 int
 pcmk__add_scores(int score1, int score2)
 {
-    /* As long as CRM_SCORE_INFINITY is less than half of the maximum integer,
+    /* As long as PCMK_SCORE_INFINITY is less than half of the maximum integer,
      * we can ignore the possibility of integer overflow.
      */
     int result = score1 + score2;
 
     // First handle the cases where one or both is infinite
-    if ((score1 <= -CRM_SCORE_INFINITY) || (score2 <= -CRM_SCORE_INFINITY)) {
-        return -CRM_SCORE_INFINITY;
+    if ((score1 <= -PCMK_SCORE_INFINITY) || (score2 <= -PCMK_SCORE_INFINITY)) {
+        return -PCMK_SCORE_INFINITY;
     }
-    if ((score1 >= CRM_SCORE_INFINITY) || (score2 >= CRM_SCORE_INFINITY)) {
-        return CRM_SCORE_INFINITY;
+    if ((score1 >= PCMK_SCORE_INFINITY) || (score2 >= PCMK_SCORE_INFINITY)) {
+        return PCMK_SCORE_INFINITY;
     }
 
     // Bound result to infinity.
-    if (result >= CRM_SCORE_INFINITY) {
-        return CRM_SCORE_INFINITY;
+    if (result >= PCMK_SCORE_INFINITY) {
+        return PCMK_SCORE_INFINITY;
     }
-    if (result <= -CRM_SCORE_INFINITY) {
-        return -CRM_SCORE_INFINITY;
+    if (result <= -PCMK_SCORE_INFINITY) {
+        return -PCMK_SCORE_INFINITY;
     }
 
     return result;
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/scores_compat.h>
 
 char *
 score2char(int score)
 {
     char *result = strdup(pcmk_readable_score(score));
 
     CRM_ASSERT(result != NULL);
     return result;
 }
 
 char *
 score2char_stack(int score, char *buf, size_t len)
 {
     CRM_CHECK((buf != NULL) && (len >= sizeof(PCMK_VALUE_MINUS_INFINITY)),
               return NULL);
     strcpy(buf, pcmk_readable_score(score));
     return buf;
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/common/tests/scores/char2score_test.c b/lib/common/tests/scores/char2score_test.c
index fbba12a067..5d7252f4a2 100644
--- a/lib/common/tests/scores/char2score_test.c
+++ b/lib/common/tests/scores/char2score_test.c
@@ -1,75 +1,77 @@
 /*
- * Copyright 2022 the Pacemaker project contributors
+ * Copyright 2022-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 <crm_internal.h>
 
 #include <crm/common/unittest_internal.h>
 
 extern int pcmk__score_red;
 extern int pcmk__score_green;
 extern int pcmk__score_yellow;
 
 static void
 empty_input(void **state)
 {
     assert_int_equal(char2score(NULL), 0);
 }
 
 static void
 bad_input(void **state)
 {
     assert_int_equal(char2score("PQRST"), 0);
     assert_int_equal(char2score("3.141592"), 3);
     assert_int_equal(char2score("0xf00d"), 0);
 }
 
 static void
 special_values(void **state)
 {
-    assert_int_equal(char2score("-INFINITY"), -CRM_SCORE_INFINITY);
-    assert_int_equal(char2score("INFINITY"), CRM_SCORE_INFINITY);
-    assert_int_equal(char2score("+INFINITY"), CRM_SCORE_INFINITY);
+    assert_int_equal(char2score("-INFINITY"), -PCMK_SCORE_INFINITY);
+    assert_int_equal(char2score("INFINITY"), PCMK_SCORE_INFINITY);
+    assert_int_equal(char2score("+INFINITY"), PCMK_SCORE_INFINITY);
 
     pcmk__score_red = 10;
     pcmk__score_green = 20;
     pcmk__score_yellow = 30;
 
     assert_int_equal(char2score("red"), pcmk__score_red);
     assert_int_equal(char2score("green"), pcmk__score_green);
     assert_int_equal(char2score("yellow"), pcmk__score_yellow);
 
     assert_int_equal(char2score("ReD"), pcmk__score_red);
     assert_int_equal(char2score("GrEeN"), pcmk__score_green);
     assert_int_equal(char2score("yElLoW"), pcmk__score_yellow);
 }
 
 /* These ridiculous macros turn an integer constant into a string constant. */
 #define A(x) #x
 #define B(x) A(x)
 
 static void
 outside_limits(void **state)
 {
-    assert_int_equal(char2score(B(CRM_SCORE_INFINITY) "00"), CRM_SCORE_INFINITY);
-    assert_int_equal(char2score("-" B(CRM_SCORE_INFINITY) "00"), -CRM_SCORE_INFINITY);
+    assert_int_equal(char2score(B(PCMK_SCORE_INFINITY) "00"),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(char2score("-" B(PCMK_SCORE_INFINITY) "00"),
+                     -PCMK_SCORE_INFINITY);
 }
 
 static void
 inside_limits(void **state)
 {
     assert_int_equal(char2score("1234"), 1234);
     assert_int_equal(char2score("-1234"), -1234);
 }
 
 PCMK__UNIT_TEST(NULL, NULL,
                 cmocka_unit_test(empty_input),
                 cmocka_unit_test(bad_input),
                 cmocka_unit_test(special_values),
                 cmocka_unit_test(outside_limits),
                 cmocka_unit_test(inside_limits))
diff --git a/lib/common/tests/scores/pcmk__add_scores_test.c b/lib/common/tests/scores/pcmk__add_scores_test.c
index 1309659fb3..952cf978ad 100644
--- a/lib/common/tests/scores/pcmk__add_scores_test.c
+++ b/lib/common/tests/scores/pcmk__add_scores_test.c
@@ -1,76 +1,99 @@
 /*
- * Copyright 2022-2023 the Pacemaker project contributors
+ * Copyright 2022-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 <crm_internal.h>
 
 #include <crm/common/unittest_internal.h>
 
 static void
 score1_minus_inf(void **state)
 {
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY, -CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY, -1), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY, 0), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY, 1), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY, CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY,
+                                      -PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY, -1),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY, 0),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY, 1),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY,
+                                      PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
 }
 
 static void
 score2_minus_inf(void **state)
 {
-    assert_int_equal(pcmk__add_scores(-1, -CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(0, -CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(1, -CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY, -CRM_SCORE_INFINITY), -CRM_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-1, -PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(0, -PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(1, -PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY,
+                                      -PCMK_SCORE_INFINITY),
+                     -PCMK_SCORE_INFINITY);
 }
 
 static void
 score1_pos_inf(void **state)
 {
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY, CRM_SCORE_INFINITY), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY, -1), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY, 0), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY, 1), CRM_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY, PCMK_SCORE_INFINITY),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY, -1),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY, 0),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY, 1),
+                     PCMK_SCORE_INFINITY);
 }
 
 static void
 score2_pos_inf(void **state)
 {
-    assert_int_equal(pcmk__add_scores(-1, CRM_SCORE_INFINITY), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(0, CRM_SCORE_INFINITY), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(1, CRM_SCORE_INFINITY), CRM_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-1, PCMK_SCORE_INFINITY),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(0, PCMK_SCORE_INFINITY),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(1, PCMK_SCORE_INFINITY),
+                     PCMK_SCORE_INFINITY);
 }
 
 static void
 result_infinite(void **state)
 {
-    assert_int_equal(pcmk__add_scores(INT_MAX, INT_MAX), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(INT_MIN, INT_MIN), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(2000000, 50), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY/2, CRM_SCORE_INFINITY/2), CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY/2, -CRM_SCORE_INFINITY/2), -CRM_SCORE_INFINITY);
-    assert_int_equal(pcmk__add_scores(-4000000, 50), -CRM_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(INT_MAX, INT_MAX), PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(INT_MIN, INT_MIN), -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(2000000, 50), PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(PCMK_SCORE_INFINITY/2,
+                                      PCMK_SCORE_INFINITY/2),
+                     PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-PCMK_SCORE_INFINITY/2,
+                                      -PCMK_SCORE_INFINITY/2),
+                     -PCMK_SCORE_INFINITY);
+    assert_int_equal(pcmk__add_scores(-4000000, 50), -PCMK_SCORE_INFINITY);
 }
 
 static void
 result_finite(void **state)
 {
     assert_int_equal(pcmk__add_scores(0, 0), 0);
     assert_int_equal(pcmk__add_scores(0, 100), 100);
     assert_int_equal(pcmk__add_scores(200, 0), 200);
     assert_int_equal(pcmk__add_scores(200, -50), 150);
 }
 
 PCMK__UNIT_TEST(NULL, NULL,
                 cmocka_unit_test(score1_minus_inf),
                 cmocka_unit_test(score2_minus_inf),
                 cmocka_unit_test(score1_pos_inf),
                 cmocka_unit_test(score2_pos_inf),
                 cmocka_unit_test(result_infinite),
                 cmocka_unit_test(result_finite))
diff --git a/lib/common/tests/scores/pcmk_readable_score_test.c b/lib/common/tests/scores/pcmk_readable_score_test.c
index 444b3878a0..c3d66f6ab1 100644
--- a/lib/common/tests/scores/pcmk_readable_score_test.c
+++ b/lib/common/tests/scores/pcmk_readable_score_test.c
@@ -1,33 +1,33 @@
 /*
  * Copyright 2022-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 <crm_internal.h>
 
 #include <crm/common/unittest_internal.h>
 
 static void
 outside_limits(void **state)
 {
-    assert_string_equal(pcmk_readable_score(CRM_SCORE_INFINITY * 2),
+    assert_string_equal(pcmk_readable_score(PCMK_SCORE_INFINITY * 2),
                         PCMK_VALUE_INFINITY);
-    assert_string_equal(pcmk_readable_score(-CRM_SCORE_INFINITY * 2),
+    assert_string_equal(pcmk_readable_score(-PCMK_SCORE_INFINITY * 2),
                         PCMK_VALUE_MINUS_INFINITY);
 }
 
 static void
 inside_limits(void **state)
 {
     assert_string_equal(pcmk_readable_score(0), "0");
     assert_string_equal(pcmk_readable_score(1024), "1024");
     assert_string_equal(pcmk_readable_score(-1024), "-1024");
 }
 
 PCMK__UNIT_TEST(NULL, NULL,
                 cmocka_unit_test(outside_limits),
                 cmocka_unit_test(inside_limits))
diff --git a/lib/common/xml.c b/lib/common/xml.c
index f23e96e58b..1948c08e27 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,3001 +1,3001 @@
 /*
  * 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 <crm_internal.h>
 
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <time.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdarg.h>
 #include <bzlib.h>
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 #include <libxml/xmlIO.h>  /* xmlAllocOutputBuffer */
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>  // PCMK__XML_LOG_BASE, etc.
 #include "crmcommon_private.h"
 
 // Define this as 1 in development to get insanely verbose trace messages
 #ifndef XML_PARSER_DEBUG
 #define XML_PARSER_DEBUG 0
 #endif
 
 /* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around
  * by libxml2, which is potentially ambiguous and dangerous. We should drop it
  * when we can break backward compatibility with configurations that might be
  * relying on it (i.e. pacemaker 3.0.0).
  *
  * It might be a good idea to have a transitional period where we first try
  * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with
  * it, logging a warning if it succeeds.
  */
 #define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER    (XML_PARSE_NOBLANKS)
 #define PCMK__XML_PARSE_OPTS_WITH_RECOVER       (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER)
 
 bool
 pcmk__tracking_xml_changes(xmlNode *xml, bool lazy)
 {
     if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
         return FALSE;
     } else if (!pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
                             pcmk__xf_tracking)) {
         return FALSE;
     } else if (lazy && !pcmk_is_set(((xml_doc_private_t *)xml->doc->_private)->flags,
                                     pcmk__xf_lazy)) {
         return FALSE;
     }
     return TRUE;
 }
 
 static inline void
 set_parent_flag(xmlNode *xml, long flag) 
 {
     for(; xml; xml = xml->parent) {
         xml_node_private_t *nodepriv = xml->_private;
 
         if (nodepriv == NULL) {
             /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
         } else {
             pcmk__set_xml_flags(nodepriv, flag);
         }
     }
 }
 
 void
 pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
 {
     if(xml && xml->doc && xml->doc->_private){
         /* During calls to xmlDocCopyNode(), xml->doc may be unset */
         xml_doc_private_t *docpriv = xml->doc->_private;
 
         pcmk__set_xml_flags(docpriv, flag);
     }
 }
 
 // Mark document, element, and all element's parents as changed
 void
 pcmk__mark_xml_node_dirty(xmlNode *xml)
 {
     pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty);
     set_parent_flag(xml, pcmk__xf_dirty);
 }
 
 // Clear flags on XML node and its children
 static void
 reset_xml_node_flags(xmlNode *xml)
 {
     xmlNode *cIter = NULL;
     xml_node_private_t *nodepriv = xml->_private;
 
     if (nodepriv) {
         nodepriv->flags = 0;
     }
 
     for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
          cIter = pcmk__xml_next(cIter)) {
         reset_xml_node_flags(cIter);
     }
 }
 
 // Set xpf_created flag on XML node and any children
 void
 pcmk__mark_xml_created(xmlNode *xml)
 {
     xmlNode *cIter = NULL;
     xml_node_private_t *nodepriv = NULL;
 
     CRM_ASSERT(xml != NULL);
     nodepriv = xml->_private;
 
     if (nodepriv && pcmk__tracking_xml_changes(xml, FALSE)) {
         if (!pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
             pcmk__set_xml_flags(nodepriv, pcmk__xf_created);
             pcmk__mark_xml_node_dirty(xml);
         }
         for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
              cIter = pcmk__xml_next(cIter)) {
             pcmk__mark_xml_created(cIter);
         }
     }
 }
 
 #define XML_DOC_PRIVATE_MAGIC   0x81726354UL
 #define XML_NODE_PRIVATE_MAGIC  0x54637281UL
 
 // Free an XML object previously marked as deleted
 static void
 free_deleted_object(void *data)
 {
     if(data) {
         pcmk__deleted_xml_t *deleted_obj = data;
 
         free(deleted_obj->path);
         free(deleted_obj);
     }
 }
 
 // Free and NULL user, ACLs, and deleted objects in an XML node's private data
 static void
 reset_xml_private_data(xml_doc_private_t *docpriv)
 {
     if (docpriv != NULL) {
         CRM_ASSERT(docpriv->check == XML_DOC_PRIVATE_MAGIC);
 
         free(docpriv->user);
         docpriv->user = NULL;
 
         if (docpriv->acls != NULL) {
             pcmk__free_acls(docpriv->acls);
             docpriv->acls = NULL;
         }
 
         if(docpriv->deleted_objs) {
             g_list_free_full(docpriv->deleted_objs, free_deleted_object);
             docpriv->deleted_objs = NULL;
         }
     }
 }
 
 // Free all private data associated with an XML node
 static void
 free_private_data(xmlNode *node)
 {
     /* Note:
     
     This function frees private data assosciated with an XML node,
     unless the function is being called as a result of internal
     XSLT cleanup.
     
     That could happen through, for example, the following chain of
     function calls:
     
        xsltApplyStylesheetInternal
     -> xsltFreeTransformContext
     -> xsltFreeRVTs
     -> xmlFreeDoc
 
     And in that case, the node would fulfill three conditions:
     
     1. It would be a standalone document (i.e. it wouldn't be 
        part of a document)
     2. It would have a space-prefixed name (for reference, please
        see xsltInternals.h: XSLT_MARK_RES_TREE_FRAG)
     3. It would carry its own payload in the _private field.
     
     We do not free data in this circumstance to avoid a failed
     assertion on the XML_*_PRIVATE_MAGIC later.
     
     */
     if (node->name == NULL || node->name[0] != ' ') {
         if (node->_private) {
             if (node->type == XML_DOCUMENT_NODE) {
                 reset_xml_private_data(node->_private);
             } else {
                 CRM_ASSERT(((xml_node_private_t *) node->_private)->check
                                == XML_NODE_PRIVATE_MAGIC);
                 /* nothing dynamically allocated nested */
             }
             free(node->_private);
             node->_private = NULL;
         }
     }
 }
 
 // Allocate and initialize private data for an XML node
 static void
 new_private_data(xmlNode *node)
 {
     switch (node->type) {
         case XML_DOCUMENT_NODE: {
             xml_doc_private_t *docpriv = NULL;
             docpriv = calloc(1, sizeof(xml_doc_private_t));
             CRM_ASSERT(docpriv != NULL);
             docpriv->check = XML_DOC_PRIVATE_MAGIC;
             /* Flags will be reset if necessary when tracking is enabled */
             pcmk__set_xml_flags(docpriv, pcmk__xf_dirty|pcmk__xf_created);
             node->_private = docpriv;
             break;
         }
         case XML_ELEMENT_NODE:
         case XML_ATTRIBUTE_NODE:
         case XML_COMMENT_NODE: {
             xml_node_private_t *nodepriv = NULL;
             nodepriv = calloc(1, sizeof(xml_node_private_t));
             CRM_ASSERT(nodepriv != NULL);
             nodepriv->check = XML_NODE_PRIVATE_MAGIC;
             /* Flags will be reset if necessary when tracking is enabled */
             pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_created);
             node->_private = nodepriv;
             if (pcmk__tracking_xml_changes(node, FALSE)) {
                 /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
                  * not hooked up at the point we are called
                  */
                 pcmk__mark_xml_node_dirty(node);
             }
             break;
         }
         case XML_TEXT_NODE:
         case XML_DTD_NODE:
         case XML_CDATA_SECTION_NODE:
             break;
         default:
             /* Ignore */
             crm_trace("Ignoring %p %d", node, node->type);
             CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
             break;
     }
 }
 
 void
 xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) 
 {
     xml_accept_changes(xml);
     crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
     pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking);
     if(enforce_acls) {
         if(acl_source == NULL) {
             acl_source = xml;
         }
         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled);
         pcmk__unpack_acl(acl_source, xml, user);
         pcmk__apply_acl(xml);
     }
 }
 
 bool xml_tracking_changes(xmlNode * xml)
 {
     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
                           pcmk__xf_tracking);
 }
 
 bool xml_document_dirty(xmlNode *xml) 
 {
     return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL)
            && pcmk_is_set(((xml_doc_private_t *)(xml->doc->_private))->flags,
                           pcmk__xf_dirty);
 }
 
 /*!
  * \internal
  * \brief Return ordinal position of an XML node among its siblings
  *
  * \param[in] xml            XML node to check
  * \param[in] ignore_if_set  Don't count siblings with this flag set
  *
  * \return Ordinal position of \p xml (starting with 0)
  */
 int
 pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set)
 {
     int position = 0;
 
     for (const xmlNode *cIter = xml; cIter->prev; cIter = cIter->prev) {
         xml_node_private_t *nodepriv = ((xmlNode*)cIter->prev)->_private;
 
         if (!pcmk_is_set(nodepriv->flags, ignore_if_set)) {
             position++;
         }
     }
 
     return position;
 }
 
 // Remove all attributes marked as deleted from an XML node
 static void
 accept_attr_deletions(xmlNode *xml)
 {
     // Clear XML node's flags
     ((xml_node_private_t *) xml->_private)->flags = pcmk__xf_none;
 
     // Remove this XML node's attributes that were marked as deleted
     pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL);
 
     // Recursively do the same for this XML node's children
     for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL;
          cIter = pcmk__xml_next(cIter)) {
         accept_attr_deletions(cIter);
     }
 }
 
 /*!
  * \internal
  * \brief Find first child XML node matching another given XML node
  *
  * \param[in] haystack  XML whose children should be checked
  * \param[in] needle    XML to match (comment content or element name and ID)
  * \param[in] exact     If true and needle is a comment, position must match
  */
 xmlNode *
 pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact)
 {
     CRM_CHECK(needle != NULL, return NULL);
 
     if (needle->type == XML_COMMENT_NODE) {
         return pcmk__xc_match(haystack, needle, exact);
 
     } else {
         const char *id = pcmk__xe_id(needle);
         const char *attr = (id == NULL)? NULL : PCMK_XA_ID;
 
         return pcmk__xe_match(haystack, (const char *) needle->name, attr, id);
     }
 }
 
 void
 xml_accept_changes(xmlNode * xml)
 {
     xmlNode *top = NULL;
     xml_doc_private_t *docpriv = NULL;
 
     if(xml == NULL) {
         return;
     }
 
     crm_trace("Accepting changes to %p", xml);
     docpriv = xml->doc->_private;
     top = xmlDocGetRootElement(xml->doc);
 
     reset_xml_private_data(xml->doc->_private);
 
     if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) {
         docpriv->flags = pcmk__xf_none;
         return;
     }
 
     docpriv->flags = pcmk__xf_none;
     accept_attr_deletions(top);
 }
 
 xmlNode *
 find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find)
 {
     xmlNode *a_child = NULL;
     const char *name = (root == NULL)? "<NULL>" : (const char *) root->name;
 
     if (search_path == NULL) {
         crm_warn("Will never find <NULL>");
         return NULL;
     }
 
     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
          a_child = pcmk__xml_next(a_child)) {
         if (strcmp((const char *)a_child->name, search_path) == 0) {
             return a_child;
         }
     }
 
     if (must_find) {
         crm_warn("Could not find %s in %s.", search_path, name);
     } else if (root != NULL) {
         crm_trace("Could not find %s in %s.", search_path, name);
     } else {
         crm_trace("Could not find %s in <NULL>.", search_path);
     }
 
     return NULL;
 }
 
 #define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \
                                            (v), pcmk__str_none)
 
 /*!
  * \internal
  * \brief Find first XML child element matching given criteria
  *
  * \param[in] parent     XML element to search
  * \param[in] node_name  If not NULL, only match children of this type
  * \param[in] attr_n     If not NULL, only match children with an attribute
  *                       of this name.
  * \param[in] attr_v     If \p attr_n and this are not NULL, only match children
  *                       with an attribute named \p attr_n and this value
  *
  * \return Matching XML child element, or NULL if none found
  */
 xmlNode *
 pcmk__xe_match(const xmlNode *parent, const char *node_name,
                const char *attr_n, const char *attr_v)
 {
     CRM_CHECK(parent != NULL, return NULL);
     CRM_CHECK(attr_v == NULL || attr_n != NULL, return NULL);
 
     for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL;
          child = pcmk__xml_next(child)) {
         if (((node_name == NULL) || pcmk__xe_is(child, node_name))
             && ((attr_n == NULL) ||
                 (attr_v == NULL && xmlHasProp(child, (pcmkXmlStr) attr_n)) ||
                 (attr_v != NULL && attr_matches(child, attr_n, attr_v)))) {
             return child;
         }
     }
     crm_trace("XML child node <%s%s%s%s%s> not found in %s",
               (node_name? node_name : "(any)"),
               (attr_n? " " : ""),
               (attr_n? attr_n : ""),
               (attr_n? "=" : ""),
               (attr_n? attr_v : ""),
               (const char *) parent->name);
     return NULL;
 }
 
 void
 copy_in_properties(xmlNode *target, const xmlNode *src)
 {
     if (src == NULL) {
         crm_warn("No node to copy properties from");
 
     } else if (target == NULL) {
         crm_err("No node to copy properties into");
 
     } else {
         for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) {
             const char *p_name = (const char *) a->name;
             const char *p_value = pcmk__xml_attr_value(a);
 
             expand_plus_plus(target, p_name, p_value);
             if (xml_acl_denied(target)) {
                 crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name);
                 return;
             }
         }
     }
 
     return;
 }
 
 /*!
  * \brief Parse integer assignment statements on this node and all its child
  *        nodes
  *
  * \param[in,out] target  Root XML node to be processed
  *
  * \note This function is recursive
  */
 void
 fix_plus_plus_recursive(xmlNode *target)
 {
     /* TODO: Remove recursion and use xpath searches for value++ */
     xmlNode *child = NULL;
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) {
         const char *p_name = (const char *) a->name;
         const char *p_value = pcmk__xml_attr_value(a);
 
         expand_plus_plus(target, p_name, p_value);
     }
     for (child = pcmk__xml_first_child(target); child != NULL;
          child = pcmk__xml_next(child)) {
         fix_plus_plus_recursive(child);
     }
 }
 
 /*!
  * \brief Update current XML attribute value per parsed integer assignment
           statement
  *
  * \param[in,out]   target  an XML node, containing a XML attribute that is
  *                          initialized to some numeric value, to be processed
  * \param[in]       name    name of the XML attribute, e.g. X, whose value
  *                          should be updated
  * \param[in]       value   assignment statement, e.g. "X++" or
  *                          "X+=5", to be applied to the initialized value.
  *
  * \note The original XML attribute value is treated as 0 if non-numeric and
  *       truncated to be an integer if decimal-point-containing.
  * \note The final XML attribute value is truncated to not exceed 1000000.
  * \note Undefined behavior if unexpected input.
  */
 void
 expand_plus_plus(xmlNode * target, const char *name, const char *value)
 {
     int offset = 1;
     int name_len = 0;
     int int_value = 0;
     int value_len = 0;
 
     const char *old_value = NULL;
 
     if (target == NULL || value == NULL || name == NULL) {
         return;
     }
 
     old_value = crm_element_value(target, name);
 
     if (old_value == NULL) {
         /* if no previous value, set unexpanded */
         goto set_unexpanded;
 
     } else if (strstr(value, name) != value) {
         goto set_unexpanded;
     }
 
     name_len = strlen(name);
     value_len = strlen(value);
     if (value_len < (name_len + 2)
         || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
         goto set_unexpanded;
     }
 
     /* if we are expanding ourselves,
      * then no previous value was set and leave int_value as 0
      */
     if (old_value != value) {
         int_value = char2score(old_value);
     }
 
     if (value[name_len + 1] != '+') {
         const char *offset_s = value + (name_len + 2);
 
         offset = char2score(offset_s);
     }
     int_value += offset;
 
-    if (int_value > INFINITY) {
-        int_value = (int)INFINITY;
+    if (int_value > PCMK_SCORE_INFINITY) {
+        int_value = PCMK_SCORE_INFINITY;
     }
 
     crm_xml_add_int(target, name, int_value);
     return;
 
   set_unexpanded:
     if (old_value == value) {
         /* the old value is already set, nothing to do */
         return;
     }
     crm_xml_add(target, name, value);
     return;
 }
 
 /*!
  * \internal
  * \brief Remove an XML element's attributes that match some criteria
  *
  * \param[in,out] element    XML element to modify
  * \param[in]     match      If not NULL, only remove attributes for which
  *                           this function returns true
  * \param[in,out] user_data  Data to pass to \p match
  */
 void
 pcmk__xe_remove_matching_attrs(xmlNode *element,
                                bool (*match)(xmlAttrPtr, void *),
                                void *user_data)
 {
     xmlAttrPtr next = NULL;
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) {
         next = a->next; // Grab now because attribute might get removed
         if ((match == NULL) || match(a, user_data)) {
             if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) {
                 crm_trace("ACLs prevent removal of attributes (%s and "
                           "possibly others) from %s element",
                           (const char *) a->name, (const char *) element->name);
                 return; // ACLs apply to element, not particular attributes
             }
 
             if (pcmk__tracking_xml_changes(element, false)) {
                 // Leave (marked for removal) until after diff is calculated
                 set_parent_flag(element, pcmk__xf_dirty);
                 pcmk__set_xml_flags((xml_node_private_t *) a->_private,
                                     pcmk__xf_deleted);
             } else {
                 xmlRemoveProp(a);
             }
         }
     }
 }
 
 xmlNode *
 add_node_copy(xmlNode * parent, xmlNode * src_node)
 {
     xmlNode *child = NULL;
 
     CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL);
 
     child = xmlDocCopyNode(src_node, parent->doc, 1);
     if (child == NULL) {
         return NULL;
     }
     xmlAddChild(parent, child);
     pcmk__mark_xml_created(child);
     return child;
 }
 
 xmlNode *
 create_xml_node(xmlNode * parent, const char *name)
 {
     xmlDoc *doc = NULL;
     xmlNode *node = NULL;
 
     if (pcmk__str_empty(name)) {
         CRM_CHECK(name != NULL && name[0] == 0, return NULL);
         return NULL;
     }
 
     if (parent == NULL) {
         doc = xmlNewDoc((pcmkXmlStr) "1.0");
         if (doc == NULL) {
             return NULL;
         }
 
         node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL);
         if (node == NULL) {
             xmlFreeDoc(doc);
             return NULL;
         }
         xmlDocSetRootElement(doc, node);
 
     } else {
         node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL);
         if (node == NULL) {
             return NULL;
         }
     }
     pcmk__mark_xml_created(node);
     return node;
 }
 
 /*!
  * \internal
  * \brief Set a given string as an XML node's content
  *
  * \param[in,out] node     Node whose content to set
  * \param[in]     content  String to set as the content
  *
  * \note \c xmlNodeSetContent() does not escape special characters.
  */
 void
 pcmk__xe_set_content(xmlNode *node, const char *content)
 {
     if (node != NULL) {
         char *escaped = pcmk__xml_escape(content, false);
 
         xmlNodeSetContent(node, (pcmkXmlStr) escaped);
         free(escaped);
     }
 }
 
 xmlNode *
 pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content)
 {
     xmlNode *node = create_xml_node(parent, name);
 
     pcmk__xe_set_content(node, content);
     return node;
 }
 
 xmlNode *
 pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
                       const char *class_name, const char *text)
 {
     xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text);
 
     if (class_name != NULL) {
         crm_xml_add(node, PCMK_XA_CLASS, class_name);
     }
 
     if (id != NULL) {
         crm_xml_add(node, PCMK_XA_ID, id);
     }
 
     return node;
 }
 
 /*!
  * Free an XML element and all of its children, removing it from its parent
  *
  * \param[in,out] xml  XML element to free
  */
 void
 pcmk_free_xml_subtree(xmlNode *xml)
 {
     xmlUnlinkNode(xml); // Detaches from parent and siblings
     xmlFreeNode(xml);   // Frees
 }
 
 static void
 free_xml_with_position(xmlNode * child, int position)
 {
     if (child != NULL) {
         xmlNode *top = NULL;
         xmlDoc *doc = child->doc;
         xml_node_private_t *nodepriv = child->_private;
         xml_doc_private_t *docpriv = NULL;
 
         if (doc != NULL) {
             top = xmlDocGetRootElement(doc);
         }
 
         if (doc != NULL && top == child) {
             /* Free everything */
             xmlFreeDoc(doc);
 
         } else if (pcmk__check_acl(child, NULL, pcmk__xf_acl_write) == FALSE) {
             GString *xpath = NULL;
 
             pcmk__if_tracing({}, return);
             xpath = pcmk__element_xpath(child);
             qb_log_from_external_source(__func__, __FILE__,
                                         "Cannot remove %s %x", LOG_TRACE,
                                         __LINE__, 0, (const char *) xpath->str,
                                         nodepriv->flags);
             g_string_free(xpath, TRUE);
             return;
 
         } else {
             if (doc && pcmk__tracking_xml_changes(child, FALSE)
                 && !pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
 
                 GString *xpath = pcmk__element_xpath(child);
 
                 if (xpath != NULL) {
                     pcmk__deleted_xml_t *deleted_obj = NULL;
 
                     crm_trace("Deleting %s %p from %p",
                               (const char *) xpath->str, child, doc);
 
                     deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t));
                     deleted_obj->path = strdup((const char *) xpath->str);
 
                     CRM_ASSERT(deleted_obj->path != NULL);
                     g_string_free(xpath, TRUE);
 
                     deleted_obj->position = -1;
                     /* Record the "position" only for XML comments for now */
                     if (child->type == XML_COMMENT_NODE) {
                         if (position >= 0) {
                             deleted_obj->position = position;
 
                         } else {
                             deleted_obj->position = pcmk__xml_position(child,
                                                                        pcmk__xf_skip);
                         }
                     }
 
                     docpriv = doc->_private;
                     docpriv->deleted_objs = g_list_append(docpriv->deleted_objs, deleted_obj);
                     pcmk__set_xml_doc_flag(child, pcmk__xf_dirty);
                 }
             }
             pcmk_free_xml_subtree(child);
         }
     }
 }
 
 
 void
 free_xml(xmlNode * child)
 {
     free_xml_with_position(child, -1);
 }
 
 xmlNode *
 copy_xml(xmlNode * src)
 {
     xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0");
     xmlNode *copy = xmlDocCopyNode(src, doc, 1);
 
     CRM_ASSERT(copy != NULL);
     xmlDocSetRootElement(doc, copy);
     return copy;
 }
 
 xmlNode *
 string2xml(const char *input)
 {
     xmlNode *xml = NULL;
     xmlDocPtr output = NULL;
     xmlParserCtxtPtr ctxt = NULL;
     const xmlError *last_error = NULL;
 
     if (input == NULL) {
         crm_err("Can't parse NULL input");
         return NULL;
     }
 
     /* create a parser context */
     ctxt = xmlNewParserCtxt();
     CRM_CHECK(ctxt != NULL, return NULL);
 
     xmlCtxtResetLastError(ctxt);
     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
     output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
                             PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
 
     if (output == NULL) {
         output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
                                 PCMK__XML_PARSE_OPTS_WITH_RECOVER);
         if (output) {
             crm_warn("Successfully recovered from XML errors "
                      "(note: a future release will treat this as a fatal failure)");
         }
     }
 
     if (output) {
         xml = xmlDocGetRootElement(output);
     }
     last_error = xmlCtxtGetLastError(ctxt);
     if (last_error && last_error->code != XML_ERR_OK) {
         /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
         /*
          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
          */
         crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
                  last_error->domain, last_error->level, last_error->code, last_error->message);
 
         if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
             CRM_LOG_ASSERT("Cannot parse an empty string");
 
         } else if (last_error->code != XML_ERR_DOCUMENT_END) {
             crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
                     input);
             if (xml != NULL) {
                 crm_log_xml_err(xml, "Partial");
             }
 
         } else {
             int len = strlen(input);
             int lpc = 0;
 
             while(lpc < len) {
                 crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
                 lpc += 80;
             }
 
             CRM_LOG_ASSERT("String parsing error");
         }
     }
 
     xmlFreeParserCtxt(ctxt);
     return xml;
 }
 
 xmlNode *
 stdin2xml(void)
 {
     size_t data_length = 0;
     size_t read_chars = 0;
 
     char *xml_buffer = NULL;
     xmlNode *xml_obj = NULL;
 
     do {
         xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE);
         read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE,
                            stdin);
         data_length += read_chars;
     } while (read_chars == PCMK__BUFFER_SIZE);
 
     if (data_length == 0) {
         crm_warn("No XML supplied on stdin");
         free(xml_buffer);
         return NULL;
     }
 
     xml_buffer[data_length] = '\0';
     xml_obj = string2xml(xml_buffer);
     free(xml_buffer);
 
     crm_log_xml_trace(xml_obj, "Created fragment");
     return xml_obj;
 }
 
 static char *
 decompress_file(const char *filename)
 {
     char *buffer = NULL;
     int rc = 0;
     size_t length = 0, read_len = 0;
     BZFILE *bz_file = NULL;
     FILE *input = fopen(filename, "r");
 
     if (input == NULL) {
         crm_perror(LOG_ERR, "Could not open %s for reading", filename);
         return NULL;
     }
 
     bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
     rc = pcmk__bzlib2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         crm_err("Could not prepare to read compressed %s: %s "
                 CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
         BZ2_bzReadClose(&rc, bz_file);
         fclose(input);
         return NULL;
     }
 
     rc = BZ_OK;
     // cppcheck seems not to understand the abort-logic in pcmk__realloc
     // cppcheck-suppress memleak
     while (rc == BZ_OK) {
         buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1);
         read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE);
 
         crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
 
         if (rc == BZ_OK || rc == BZ_STREAM_END) {
             length += read_len;
         }
     }
 
     buffer[length] = '\0';
 
     rc = pcmk__bzlib2rc(rc);
 
     if (rc != pcmk_rc_ok) {
         crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d",
                 filename, pcmk_rc_str(rc), rc);
         free(buffer);
         buffer = NULL;
     }
 
     BZ2_bzReadClose(&rc, bz_file);
     fclose(input);
     return buffer;
 }
 
 /*!
  * \internal
  * \brief Remove XML text nodes from specified XML and all its children
  *
  * \param[in,out] xml  XML to strip text from
  */
 void
 pcmk__strip_xml_text(xmlNode *xml)
 {
     xmlNode *iter = xml->children;
 
     while (iter) {
         xmlNode *next = iter->next;
 
         switch (iter->type) {
             case XML_TEXT_NODE:
                 /* Remove it */
                 pcmk_free_xml_subtree(iter);
                 break;
 
             case XML_ELEMENT_NODE:
                 /* Search it */
                 pcmk__strip_xml_text(iter);
                 break;
 
             default:
                 /* Leave it */
                 break;
         }
 
         iter = next;
     }
 }
 
 xmlNode *
 filename2xml(const char *filename)
 {
     xmlNode *xml = NULL;
     xmlDocPtr output = NULL;
     bool uncompressed = true;
     xmlParserCtxtPtr ctxt = NULL;
     const xmlError *last_error = NULL;
 
     /* create a parser context */
     ctxt = xmlNewParserCtxt();
     CRM_CHECK(ctxt != NULL, return NULL);
 
     xmlCtxtResetLastError(ctxt);
     xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err);
 
     if (filename) {
         uncompressed = !pcmk__ends_with_ext(filename, ".bz2");
     }
 
     if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
         /* STDIN_FILENO == fileno(stdin) */
         output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
                                PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
 
         if (output == NULL) {
             output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL,
                                    PCMK__XML_PARSE_OPTS_WITH_RECOVER);
             if (output) {
                 crm_warn("Successfully recovered from XML errors "
                          "(note: a future release will treat this as a fatal failure)");
             }
         }
 
     } else if (uncompressed) {
         output = xmlCtxtReadFile(ctxt, filename, NULL,
                                  PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
 
         if (output == NULL) {
             output = xmlCtxtReadFile(ctxt, filename, NULL,
                                      PCMK__XML_PARSE_OPTS_WITH_RECOVER);
             if (output) {
                 crm_warn("Successfully recovered from XML errors "
                          "(note: a future release will treat this as a fatal failure)");
             }
         }        
 
     } else {
         char *input = decompress_file(filename);
 
         output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
                                 PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER);
 
         if (output == NULL) {
             output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL,
                                     PCMK__XML_PARSE_OPTS_WITH_RECOVER);
             if (output) {
                 crm_warn("Successfully recovered from XML errors "
                          "(note: a future release will treat this as a fatal failure)");
             }
         }        
 
         free(input);
     }
 
     if (output && (xml = xmlDocGetRootElement(output))) {
         pcmk__strip_xml_text(xml);
     }
 
     last_error = xmlCtxtGetLastError(ctxt);
     if (last_error && last_error->code != XML_ERR_OK) {
         /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
         /*
          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
          * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
          */
         crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
                 last_error->domain, last_error->level, last_error->code, last_error->message);
 
         if (last_error && last_error->code != XML_ERR_OK) {
             crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
             if (xml != NULL) {
                 crm_log_xml_err(xml, "Partial");
             }
         }
     }
 
     xmlFreeParserCtxt(ctxt);
     return xml;
 }
 
 /*!
  * \internal
  * \brief Add a "last written" attribute to an XML element, set to current time
  *
  * \param[in,out] xe  XML element to add attribute to
  *
  * \return Value that was set, or NULL on error
  */
 const char *
 pcmk__xe_add_last_written(xmlNode *xe)
 {
     char *now_s = pcmk__epoch2str(NULL, 0);
     const char *result = NULL;
 
     result = crm_xml_add(xe, PCMK_XA_CIB_LAST_WRITTEN,
                          pcmk__s(now_s, "Could not determine current time"));
     free(now_s);
     return result;
 }
 
 /*!
  * \brief Sanitize a string so it is usable as an XML ID
  *
  * \param[in,out] id  String to sanitize
  */
 void
 crm_xml_sanitize_id(char *id)
 {
     char *c;
 
     for (c = id; *c; ++c) {
         /* @TODO Sanitize more comprehensively */
         switch (*c) {
             case ':':
             case '#':
                 *c = '.';
         }
     }
 }
 
 /*!
  * \brief Set the ID of an XML element using a format
  *
  * \param[in,out] xml  XML element
  * \param[in]     fmt  printf-style format
  * \param[in]     ...  any arguments required by format
  */
 void
 crm_xml_set_id(xmlNode *xml, const char *format, ...)
 {
     va_list ap;
     int len = 0;
     char *id = NULL;
 
     /* equivalent to crm_strdup_printf() */
     va_start(ap, format);
     len = vasprintf(&id, format, ap);
     va_end(ap);
     CRM_ASSERT(len > 0);
 
     crm_xml_sanitize_id(id);
     crm_xml_add(xml, PCMK_XA_ID, id);
     free(id);
 }
 
 /*!
  * \internal
  * \brief Write XML to a file stream
  *
  * \param[in]     xml       XML to write
  * \param[in]     filename  Name of file being written (for logging only)
  * \param[in,out] stream    Open file stream corresponding to filename
  * \param[in]     compress  Whether to compress XML before writing
  * \param[out]    nbytes    Number of bytes written
  *
  * \return Standard Pacemaker return code
  */
 static int
 write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream,
                  bool compress, unsigned int *nbytes)
 {
     int rc = pcmk_rc_ok;
     char *buffer = NULL;
 
     *nbytes = 0;
     crm_log_xml_trace(xml, "writing");
 
     buffer = dump_xml_formatted(xml);
     CRM_CHECK(buffer && strlen(buffer),
               crm_log_xml_warn(xml, "formatting failed");
               rc = pcmk_rc_error;
               goto bail);
 
     if (compress) {
         unsigned int in = 0;
         BZFILE *bz_file = NULL;
 
         rc = BZ_OK;
         bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
         rc = pcmk__bzlib2rc(rc);
 
         if (rc != pcmk_rc_ok) {
             crm_warn("Not compressing %s: could not prepare file stream: %s "
                      CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc);
         } else {
             BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
             rc = pcmk__bzlib2rc(rc);
 
             if (rc != pcmk_rc_ok) {
                 crm_warn("Not compressing %s: could not compress data: %s "
                          CRM_XS " rc=%d errno=%d",
                          filename, pcmk_rc_str(rc), rc, errno);
             }
         }
 
         if (rc == pcmk_rc_ok) {
             BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes);
             rc = pcmk__bzlib2rc(rc);
 
             if (rc != pcmk_rc_ok) {
                 crm_warn("Not compressing %s: could not write compressed data: %s "
                          CRM_XS " rc=%d errno=%d",
                          filename, pcmk_rc_str(rc), rc, errno);
                 *nbytes = 0; // retry without compression
             } else {
                 crm_trace("Compressed XML for %s from %u bytes to %u",
                           filename, in, *nbytes);
             }
         }
         rc = pcmk_rc_ok; // Either true, or we'll retry without compression
     }
 
     if (*nbytes == 0) {
         rc = fprintf(stream, "%s", buffer);
         if (rc < 0) {
             rc = errno;
             crm_perror(LOG_ERR, "writing %s", filename);
         } else {
             *nbytes = (unsigned int) rc;
             rc = pcmk_rc_ok;
         }
     }
 
   bail:
 
     if (fflush(stream) != 0) {
         rc = errno;
         crm_perror(LOG_ERR, "flushing %s", filename);
     }
 
     /* Don't report error if the file does not support synchronization */
     if (fsync(fileno(stream)) < 0 && errno != EROFS  && errno != EINVAL) {
         rc = errno;
         crm_perror(LOG_ERR, "synchronizing %s", filename);
     }
 
     fclose(stream);
 
     crm_trace("Saved %d bytes to %s as XML", *nbytes, filename);
     free(buffer);
 
     return rc;
 }
 
 /*!
  * \brief Write XML to a file descriptor
  *
  * \param[in] xml       XML to write
  * \param[in] filename  Name of file being written (for logging only)
  * \param[in] fd        Open file descriptor corresponding to filename
  * \param[in] compress  Whether to compress XML before writing
  *
  * \return Number of bytes written on success, -errno otherwise
  */
 int
 write_xml_fd(const xmlNode *xml, const char *filename, int fd,
              gboolean compress)
 {
     FILE *stream = NULL;
     unsigned int nbytes = 0;
     int rc = pcmk_rc_ok;
 
     CRM_CHECK((xml != NULL) && (fd > 0), return -EINVAL);
     stream = fdopen(fd, "w");
     if (stream == NULL) {
         return -errno;
     }
     rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
     if (rc != pcmk_rc_ok) {
         return pcmk_rc2legacy(rc);
     }
     return (int) nbytes;
 }
 
 /*!
  * \brief Write XML to a file
  *
  * \param[in] xml       XML to write
  * \param[in] filename  Name of file to write
  * \param[in] compress  Whether to compress XML before writing
  *
  * \return Number of bytes written on success, -errno otherwise
  */
 int
 write_xml_file(const xmlNode *xml, const char *filename, gboolean compress)
 {
     FILE *stream = NULL;
     unsigned int nbytes = 0;
     int rc = pcmk_rc_ok;
 
     CRM_CHECK((xml != NULL) && (filename != NULL), return -EINVAL);
     stream = fopen(filename, "w");
     if (stream == NULL) {
         return -errno;
     }
     rc = write_xml_stream(xml, filename, stream, compress, &nbytes);
     if (rc != pcmk_rc_ok) {
         return pcmk_rc2legacy(rc);
     }
     return (int) nbytes;
 }
 
 /*!
  * \internal
  * \brief Get consecutive bytes encoding non-ASCII UTF-8 characters
  *
  * \param[in] text  String to check
  *
  * \return Number of non-ASCII UTF-8 bytes at the beginning of \p text
  */
 static size_t
 utf8_bytes(const char *text)
 {
     // Total number of consecutive bytes containing UTF-8 characters
     size_t c_bytes = 0;
 
     if (text == NULL) {
         return 0;
     }
 
     /* UTF-8 uses one to four 8-bit bytes per character. The first byte
      * indicates the width of the character. A byte beginning with a '0' bit is
      * a one-byte ASCII character.
      *
      * A C byte is 8 bits on most systems, but this is not guaranteed.
      *
      * Count until we find an ASCII character or an invalid byte. Check bytes
      * aligned with the C byte boundary.
      */
     for (const uint8_t *utf8_byte = (const uint8_t *) text;
          (*utf8_byte & 0x80) != 0;
          utf8_byte = (const uint8_t *) (text + c_bytes)) {
 
         size_t utf8_bits = 0;
 
         if ((*utf8_byte & 0xf0) == 0xf0) {
             // Four-byte character (first byte: 11110xxx)
             utf8_bits = 32;
 
         } else if ((*utf8_byte & 0xe0) == 0xe0) {
             // Three-byte character (first byte: 1110xxxx)
             utf8_bits = 24;
 
         } else if ((*utf8_byte & 0xc0) == 0xc0) {
             // Two-byte character (first byte: 110xxxxx)
             utf8_bits = 16;
 
         } else {
             crm_warn("Found invalid UTF-8 character %.2x",
                      (unsigned char) *utf8_byte);
             return c_bytes;
         }
 
         c_bytes += utf8_bits / CHAR_BIT;
 
 #if (CHAR_BIT != 8) // Coverity complains about dead code without this CPP guard
         if ((utf8_bits % CHAR_BIT) > 0) {
             c_bytes++;
         }
 #endif  // CHAR_BIT != 8
     }
 
     return c_bytes;
 }
 
 /*!
  * \internal
  * \brief Replace a character in a dynamically allocated string, reallocating
  *        memory
  *
  * \param[in,out] text     String to replace a character in
  * \param[in,out] index    Index of character to replace with new string; on
  *                         return, reset to index of end of replacement string
  * \param[in,out] length   Length of \p text
  * \param[in]     replace  String to replace character at \p index with (must
  *                         not be empty)
  *
  * \return \p text, with the character at \p index replaced by \p replace
  */
 static char *
 replace_text(char *text, size_t *index, size_t *length, const char *replace)
 {
     /* @TODO Replace with GString? Or at least copy char-by-char, escaping
      * characters as needed, instead of shifting characters on every replacement
      */
 
     // We have space for 1 char already
     size_t offset = strlen(replace) - 1;
 
     if (offset > 0) {
         *length += offset;
         text = pcmk__realloc(text, *length + 1);
 
         // Shift characters to the right to make room for the replacement string
         for (size_t i = *length; i > (*index + offset); i--) {
             text[i] = text[i - offset];
         }
     }
 
     // Replace the character at index by the replacement string
     memcpy(text + *index, replace, offset + 1);
 
     // Reset index to the end of replacement string
     *index += offset;
     return text;
 }
 
 /*!
  * \internal
  * \brief Check whether a string has XML special characters that must be escaped
  *
  * See \c pcmk__xml_escape() for more details.
  *
  * \param[in] text          String to check
  * \param[in] escape_quote  If \c true, double quotes must be escaped
  *
  * \return \c true if \p text has special characters that need to be escaped, or
  *         \c false otherwise
  */
 bool
 pcmk__xml_needs_escape(const char *text, bool escape_quote)
 {
     size_t length = 0;
 
     if (text == NULL) {
         return false;
     }
     length = strlen(text);
 
     for (size_t index = 0; index < length; index++) {
         // Don't escape any non-ASCII characters
         index += utf8_bytes(&(text[index]));
 
         switch (text[index]) {
             case '\0':
                 // Reached end of string by skipping UTF-8 bytes
                 return false;
             case '<':
                 return true;
             case '>':
                 // Not necessary, but for symmetry with '<'
                 return true;
             case '&':
                 return true;
             case '"':
                 if (escape_quote) {
                     return true;
                 }
                 break;
             case '\n':
             case '\t':
                 // Don't escape newline or tab
                 break;
             default:
                 if ((text[index] < 0x20) || (text[index] >= 0x7f)) {
                     // Escape non-printing characters
                     return true;
                 }
                 break;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Replace special characters with their XML escape sequences
  *
  * XML allows the escaping of special characters by replacing them with entity
  * references (for example, <tt>"&quot;"</tt>) or character references (for
  * example, <tt>"&#13;"</tt>).
  *
  * The special characters <tt>'<'</tt> and <tt>'&'</tt> are not allowed in their
  * literal forms in XML character data. Character data is non-markup text (for
  * example, the content of a text node).
  *
  * Additionally, if an attribute value is delimited by single quotes, then
  * single quotes must be escaped within the value. Similarly, if an attribute
  * value is delimited by double quotes, then double quotes must be escaped
  * within the value.
  *
  * For more details, see the "Character Data and Markup" section of the XML
  * spec, currently section 2.4:
  * https://www.w3.org/TR/xml/#dt-markup
  *
  * Pacemaker always delimits attribute values with double quotes, so this
  * function doesn't escape single quotes.
  *
  * \param[in] text          Text to escape
  * \param[in] escape_quote  If \c true, escape double quotes (should be enabled
  *                          for attribute values)
  *
  * \return Newly allocated string equivalent to \p text but with special
  *         characters replaced with XML escape sequences (or \c NULL if \p text
  *         is \c NULL). If \p text is not \c NULL, the return value is
  *         guaranteed not to be \c NULL.
  *
  * \note There are libxml functions that purport to do this:
  *       \c xmlEncodeEntitiesReentrant() and \c xmlEncodeSpecialChars().
  *       However, their escaping is incomplete. See:
  *       https://discourse.gnome.org/t/intended-use-of-xmlencodeentitiesreentrant-vs-xmlencodespecialchars/19252
  */
 char *
 pcmk__xml_escape(const char *text, bool escape_quote)
 {
     size_t length = 0;
     char *copy = NULL;
     char buf[32] = { '\0', };
 
     if (text == NULL) {
         return NULL;
     }
     length = strlen(text);
     pcmk__str_update(&copy, text);
 
     for (size_t index = 0; index < length; index++) {
         // Don't escape any non-ASCII characters
         index += utf8_bytes(&(copy[index]));
 
         switch (copy[index]) {
             case '\0':
                 // Reached end of string by skipping UTF-8 bytes
                 break;
             case '<':
                 copy = replace_text(copy, &index, &length, "&lt;");
                 break;
             case '>':
                 // Not necessary, but for symmetry with '<'
                 copy = replace_text(copy, &index, &length, "&gt;");
                 break;
             case '&':
                 copy = replace_text(copy, &index, &length, "&amp;");
                 break;
             case '"':
                 if (escape_quote) {
                     copy = replace_text(copy, &index, &length, "&quot;");
                 }
                 break;
             case '\n':
             case '\t':
                 // Don't escape newlines and tabs
                 break;
             default:
                 if ((copy[index] < 0x20) || (copy[index] >= 0x7f)) {
                     // Escape non-printing characters
                     snprintf(buf, sizeof(buf), "&#%.2x;", copy[index]);
                     copy = replace_text(copy, &index, &length, buf);
                 }
                 break;
         }
     }
     return copy;
 }
 
 /*!
  * \internal
  * \brief Append a string representation of an XML element to a buffer
  *
  * \param[in]     data     XML whose representation to append
  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
  * \param[in]     depth    Current indentation level
  */
 static void
 dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer,
                  int depth)
 {
     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered);
     int spaces = pretty? (2 * depth) : 0;
 
     for (int lpc = 0; lpc < spaces; lpc++) {
         g_string_append_c(buffer, ' ');
     }
 
     pcmk__g_strcat(buffer, "<", data->name, NULL);
 
     for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL;
          attr = attr->next) {
 
         if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) {
             pcmk__dump_xml_attr(attr, buffer);
         }
     }
 
     if (data->children == NULL) {
         g_string_append(buffer, "/>");
 
     } else {
         g_string_append_c(buffer, '>');
     }
 
     if (pretty) {
         g_string_append_c(buffer, '\n');
     }
 
     if (data->children) {
         for (const xmlNode *child = data->children; child != NULL;
              child = child->next) {
             pcmk__xml2text(child, options, buffer, depth + 1);
         }
 
         for (int lpc = 0; lpc < spaces; lpc++) {
             g_string_append_c(buffer, ' ');
         }
 
         pcmk__g_strcat(buffer, "</", data->name, ">", NULL);
 
         if (pretty) {
             g_string_append_c(buffer, '\n');
         }
     }
 }
 
 /*!
  * \internal
  * \brief Append XML text content to a buffer
  *
  * \param[in]     data     XML whose content to append
  * \param[in]     options  Group of \p xml_log_options flags
  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
  * \param[in]     depth    Current indentation level
  */
 static void
 dump_xml_text(const xmlNode *data, uint32_t options, GString *buffer,
               int depth)
 {
     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     int spaces = pretty? (2 * depth) : 0;
     const char *content = (const char *) data->content;
     char *content_esc = NULL;
 
     if (pcmk__xml_needs_escape(content, false)) {
         content_esc = pcmk__xml_escape(content, false);
         content = content_esc;
     }
 
     for (int lpc = 0; lpc < spaces; lpc++) {
         g_string_append_c(buffer, ' ');
     }
 
     g_string_append(buffer, content);
 
     if (pretty) {
         g_string_append_c(buffer, '\n');
     }
     free(content_esc);
 }
 
 /*!
  * \internal
  * \brief Append XML CDATA content to a buffer
  *
  * \param[in]     data     XML whose content to append
  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
  * \param[in]     depth    Current indentation level
  */
 static void
 dump_xml_cdata(const xmlNode *data, uint32_t options, GString *buffer,
                int depth)
 {
     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     int spaces = pretty? (2 * depth) : 0;
 
     for (int lpc = 0; lpc < spaces; lpc++) {
         g_string_append_c(buffer, ' ');
     }
 
     pcmk__g_strcat(buffer, "<![CDATA[", (const char *) data->content, "]]>",
                    NULL);
 
     if (pretty) {
         g_string_append_c(buffer, '\n');
     }
 }
 
 /*!
  * \internal
  * \brief Append an XML comment to a buffer
  *
  * \param[in]     data     XML whose content to append
  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  * \param[in,out] buffer   Where to append the content (must not be \p NULL)
  * \param[in]     depth    Current indentation level
  */
 static void
 dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer,
                  int depth)
 {
     bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty);
     int spaces = pretty? (2 * depth) : 0;
 
     for (int lpc = 0; lpc < spaces; lpc++) {
         g_string_append_c(buffer, ' ');
     }
 
     pcmk__g_strcat(buffer, "<!--", (const char *) data->content, "-->", NULL);
 
     if (pretty) {
         g_string_append_c(buffer, '\n');
     }
 }
 
 /*!
  * \internal
  * \brief Get a string representation of an XML element type
  *
  * \param[in] type  XML element type
  *
  * \return String representation of \p type
  */
 static const char *
 xml_element_type2str(xmlElementType type)
 {
     static const char *const element_type_names[] = {
         [XML_ELEMENT_NODE]       = "element",
         [XML_ATTRIBUTE_NODE]     = "attribute",
         [XML_TEXT_NODE]          = "text",
         [XML_CDATA_SECTION_NODE] = "CDATA section",
         [XML_ENTITY_REF_NODE]    = "entity reference",
         [XML_ENTITY_NODE]        = "entity",
         [XML_PI_NODE]            = "PI",
         [XML_COMMENT_NODE]       = "comment",
         [XML_DOCUMENT_NODE]      = "document",
         [XML_DOCUMENT_TYPE_NODE] = "document type",
         [XML_DOCUMENT_FRAG_NODE] = "document fragment",
         [XML_NOTATION_NODE]      = "notation",
         [XML_HTML_DOCUMENT_NODE] = "HTML document",
         [XML_DTD_NODE]           = "DTD",
         [XML_ELEMENT_DECL]       = "element declaration",
         [XML_ATTRIBUTE_DECL]     = "attribute declaration",
         [XML_ENTITY_DECL]        = "entity declaration",
         [XML_NAMESPACE_DECL]     = "namespace declaration",
         [XML_XINCLUDE_START]     = "XInclude start",
         [XML_XINCLUDE_END]       = "XInclude end",
     };
 
     if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) {
         return "unrecognized type";
     }
     return element_type_names[type];
 }
 
 /*!
  * \internal
  * \brief Create a text representation of an XML object
  *
  * \param[in]     data     XML to convert
  * \param[in]     options  Group of \p pcmk__xml_fmt_options flags
  * \param[in,out] buffer   Where to store the text (must not be \p NULL)
  * \param[in]     depth    Current indentation level
  */
 void
 pcmk__xml2text(const xmlNode *data, uint32_t options, GString *buffer,
                int depth)
 {
     if (data == NULL) {
         crm_trace("Nothing to dump");
         return;
     }
 
     CRM_ASSERT(buffer != NULL);
     CRM_CHECK(depth >= 0, depth = 0);
 
     switch(data->type) {
         case XML_ELEMENT_NODE:
             /* Handle below */
             dump_xml_element(data, options, buffer, depth);
             break;
         case XML_TEXT_NODE:
             if (pcmk_is_set(options, pcmk__xml_fmt_text)) {
                 dump_xml_text(data, options, buffer, depth);
             }
             break;
         case XML_COMMENT_NODE:
             dump_xml_comment(data, options, buffer, depth);
             break;
         case XML_CDATA_SECTION_NODE:
             dump_xml_cdata(data, options, buffer, depth);
             break;
         default:
             crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d",
                      xml_element_type2str(data->type), data->type);
             break;
     }
 }
 
 char *
 dump_xml_formatted_with_text(const xmlNode *xml)
 {
     /* libxml's xmlNodeDumpOutput() would work here since we're not specifically
      * filtering out any nodes. However, use pcmk__xml2text() for consistency,
      * to escape attribute values, and to allow a const argument.
      */
     char *buffer = NULL;
     GString *g_buffer = g_string_sized_new(1024);
 
     pcmk__xml2text(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, g_buffer, 0);
 
     pcmk__str_update(&buffer, g_buffer->str);
     g_string_free(g_buffer, TRUE);
     return buffer;
 }
 
 char *
 dump_xml_formatted(const xmlNode *xml)
 {
     char *buffer = NULL;
     GString *g_buffer = g_string_sized_new(1024);
 
     pcmk__xml2text(xml, pcmk__xml_fmt_pretty, g_buffer, 0);
 
     pcmk__str_update(&buffer, g_buffer->str);
     g_string_free(g_buffer, TRUE);
     return buffer;
 }
 
 char *
 dump_xml_unformatted(const xmlNode *xml)
 {
     char *buffer = NULL;
     GString *g_buffer = g_string_sized_new(1024);
 
     pcmk__xml2text(xml, 0, g_buffer, 0);
 
     pcmk__str_update(&buffer, g_buffer->str);
     g_string_free(g_buffer, TRUE);
     return buffer;
 }
 
 int
 pcmk__xml2fd(int fd, xmlNode *cur)
 {
     bool success;
 
     xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL);
     CRM_ASSERT(fd_out != NULL);
     xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL);
 
     success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1;
 
     success = xmlOutputBufferClose(fd_out) != -1 && success;
 
     if (!success) {
         return EIO;
     }
 
     fsync(fd);
     return pcmk_rc_ok;
 }
 
 void
 xml_remove_prop(xmlNode * obj, const char *name)
 {
     if (crm_element_value(obj, name) == NULL) {
         return;
     }
 
     if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) {
         crm_trace("Cannot remove %s from %s", name, obj->name);
 
     } else if (pcmk__tracking_xml_changes(obj, FALSE)) {
         /* Leave in place (marked for removal) until after the diff is calculated */
         xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name);
         xml_node_private_t *nodepriv = attr->_private;
 
         set_parent_flag(obj, pcmk__xf_dirty);
         pcmk__set_xml_flags(nodepriv, pcmk__xf_deleted);
     } else {
         xmlUnsetProp(obj, (pcmkXmlStr) name);
     }
 }
 
 void
 save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
 {
     char *f = NULL;
 
     if (filename == NULL) {
         char *uuid = crm_generate_uuid();
 
         f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid);
         filename = f;
         free(uuid);
     }
 
     crm_info("Saving %s to %s", desc, filename);
     write_xml_file(xml, filename, FALSE);
     free(f);
 }
 
 /*!
  * \internal
  * \brief Set a flag on all attributes of an XML element
  *
  * \param[in,out] xml   XML node to set flags on
  * \param[in]     flag  XML private flag to set
  */
 static void
 set_attrs_flag(xmlNode *xml, enum xml_private_flags flag)
 {
     for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) {
         pcmk__set_xml_flags((xml_node_private_t *) (attr->_private), flag);
     }
 }
 
 /*!
  * \internal
  * \brief Add an XML attribute to a node, marked as deleted
  *
  * When calculating XML changes, we need to know when an attribute has been
  * deleted. Add the attribute back to the new XML, so that we can check the
  * removal against ACLs, and mark it as deleted for later removal after
  * differences have been calculated.
  *
  * \param[in,out] new_xml     XML to modify
  * \param[in]     element     Name of XML element that changed (for logging)
  * \param[in]     attr_name   Name of attribute that was deleted
  * \param[in]     old_value   Value of attribute that was deleted
  */
 static void
 mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name,
                   const char *old_value)
 {
     xml_doc_private_t *docpriv = new_xml->doc->_private;
     xmlAttr *attr = NULL;
     xml_node_private_t *nodepriv;
 
     // Prevent the dirty flag being set recursively upwards
     pcmk__clear_xml_flags(docpriv, pcmk__xf_tracking);
 
     // Restore the old value (and the tracking flag)
     attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
     pcmk__set_xml_flags(docpriv, pcmk__xf_tracking);
 
     // Reset flags (so the attribute doesn't appear as newly created)
     nodepriv = attr->_private;
     nodepriv->flags = 0;
 
     // Check ACLs and mark restored value for later removal
     xml_remove_prop(new_xml, attr_name);
 
     crm_trace("XML attribute %s=%s was removed from %s",
               attr_name, old_value, element);
 }
 
 /*
  * \internal
  * \brief Check ACLs for a changed XML attribute
  */
 static void
 mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name,
                   const char *old_value)
 {
     char *vcopy = crm_element_value_copy(new_xml, attr_name);
 
     crm_trace("XML attribute %s was changed from '%s' to '%s' in %s",
               attr_name, old_value, vcopy, element);
 
     // Restore the original value
     xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value);
 
     // Change it back to the new value, to check ACLs
     crm_xml_add(new_xml, attr_name, vcopy);
     free(vcopy);
 }
 
 /*!
  * \internal
  * \brief Mark an XML attribute as having changed position
  *
  * \param[in,out] new_xml     XML to modify
  * \param[in]     element     Name of XML element that changed (for logging)
  * \param[in,out] old_attr    Attribute that moved, in original XML
  * \param[in,out] new_attr    Attribute that moved, in \p new_xml
  * \param[in]     p_old       Ordinal position of \p old_attr in original XML
  * \param[in]     p_new       Ordinal position of \p new_attr in \p new_xml
  */
 static void
 mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr,
                 xmlAttr *new_attr, int p_old, int p_new)
 {
     xml_node_private_t *nodepriv = new_attr->_private;
 
     crm_trace("XML attribute %s moved from position %d to %d in %s",
               old_attr->name, p_old, p_new, element);
 
     // Mark document, element, and all element's parents as changed
     pcmk__mark_xml_node_dirty(new_xml);
 
     // Mark attribute as changed
     pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved);
 
     nodepriv = (p_old > p_new)? old_attr->_private : new_attr->_private;
     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
 }
 
 /*!
  * \internal
  * \brief Calculate differences in all previously existing XML attributes
  *
  * \param[in,out] old_xml  Original XML to compare
  * \param[in,out] new_xml  New XML to compare
  */
 static void
 xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml)
 {
     xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml);
 
     while (attr_iter != NULL) {
         const char *name = (const char *) attr_iter->name;
         xmlAttr *old_attr = attr_iter;
         xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name);
         const char *old_value = pcmk__xml_attr_value(attr_iter);
 
         attr_iter = attr_iter->next;
         if (new_attr == NULL) {
             mark_attr_deleted(new_xml, (const char *) old_xml->name, name,
                               old_value);
 
         } else {
             xml_node_private_t *nodepriv = new_attr->_private;
             int new_pos = pcmk__xml_position((xmlNode*) new_attr,
                                              pcmk__xf_skip);
             int old_pos = pcmk__xml_position((xmlNode*) old_attr,
                                              pcmk__xf_skip);
             const char *new_value = crm_element_value(new_xml, name);
 
             // This attribute isn't new
             pcmk__clear_xml_flags(nodepriv, pcmk__xf_created);
 
             if (strcmp(new_value, old_value) != 0) {
                 mark_attr_changed(new_xml, (const char *) old_xml->name, name,
                                   old_value);
 
             } else if ((old_pos != new_pos)
                        && !pcmk__tracking_xml_changes(new_xml, TRUE)) {
                 mark_attr_moved(new_xml, (const char *) old_xml->name,
                                 old_attr, new_attr, old_pos, new_pos);
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Check all attributes in new XML for creation
  *
  * For each of a given XML element's attributes marked as newly created, accept
  * (and mark as dirty) or reject the creation according to ACLs.
  *
  * \param[in,out] new_xml  XML to check
  */
 static void
 mark_created_attrs(xmlNode *new_xml)
 {
     xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml);
 
     while (attr_iter != NULL) {
         xmlAttr *new_attr = attr_iter;
         xml_node_private_t *nodepriv = attr_iter->_private;
 
         attr_iter = attr_iter->next;
         if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
             const char *attr_name = (const char *) new_attr->name;
 
             crm_trace("Created new attribute %s=%s in %s",
                       attr_name, pcmk__xml_attr_value(new_attr),
                       new_xml->name);
 
             /* Check ACLs (we can't use the remove-then-create trick because it
              * would modify the attribute position).
              */
             if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) {
                 pcmk__mark_xml_attr_dirty(new_attr);
             } else {
                 // Creation was not allowed, so remove the attribute
                 xmlUnsetProp(new_xml, new_attr->name);
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Calculate differences in attributes between two XML nodes
  *
  * \param[in,out] old_xml  Original XML to compare
  * \param[in,out] new_xml  New XML to compare
  */
 static void
 xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml)
 {
     set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new
     xml_diff_old_attrs(old_xml, new_xml);
     mark_created_attrs(new_xml);
 }
 
 /*!
  * \internal
  * \brief Add an XML child element to a node, marked as deleted
  *
  * When calculating XML changes, we need to know when a child element has been
  * deleted. Add the child back to the new XML, so that we can check the removal
  * against ACLs, and mark it as deleted for later removal after differences have
  * been calculated.
  *
  * \param[in,out] old_child    Child element from original XML
  * \param[in,out] new_parent   New XML to add marked copy to
  */
 static void
 mark_child_deleted(xmlNode *old_child, xmlNode *new_parent)
 {
     // Re-create the child element so we can check ACLs
     xmlNode *candidate = add_node_copy(new_parent, old_child);
 
     // Clear flags on new child and its children
     reset_xml_node_flags(candidate);
 
     // Check whether ACLs allow the deletion
     pcmk__apply_acl(xmlDocGetRootElement(candidate->doc));
 
     // Remove the child again (which will track it in document's deleted_objs)
     free_xml_with_position(candidate,
                            pcmk__xml_position(old_child, pcmk__xf_skip));
 
     if (pcmk__xml_match(new_parent, old_child, true) == NULL) {
         pcmk__set_xml_flags((xml_node_private_t *) (old_child->_private),
                             pcmk__xf_skip);
     }
 }
 
 static void
 mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child,
                  int p_old, int p_new)
 {
     xml_node_private_t *nodepriv = new_child->_private;
 
     crm_trace("Child element %s with id='%s' moved from position %d to %d under %s",
               new_child->name, pcmk__s(pcmk__xe_id(new_child), "<no id>"),
               p_old, p_new, new_parent->name);
     pcmk__mark_xml_node_dirty(new_parent);
     pcmk__set_xml_flags(nodepriv, pcmk__xf_moved);
 
     if (p_old > p_new) {
         nodepriv = old_child->_private;
     } else {
         nodepriv = new_child->_private;
     }
     pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
 }
 
 // Given original and new XML, mark new XML portions that have changed
 static void
 mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top)
 {
     xmlNode *cIter = NULL;
     xml_node_private_t *nodepriv = NULL;
 
     CRM_CHECK(new_xml != NULL, return);
     if (old_xml == NULL) {
         pcmk__mark_xml_created(new_xml);
         pcmk__apply_creation_acl(new_xml, check_top);
         return;
     }
 
     nodepriv = new_xml->_private;
     CRM_CHECK(nodepriv != NULL, return);
 
     if(nodepriv->flags & pcmk__xf_processed) {
         /* Avoid re-comparing nodes */
         return;
     }
     pcmk__set_xml_flags(nodepriv, pcmk__xf_processed);
 
     xml_diff_attrs(old_xml, new_xml);
 
     // Check for differences in the original children
     for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) {
         xmlNode *old_child = cIter;
         xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true);
 
         cIter = pcmk__xml_next(cIter);
         if(new_child) {
             mark_xml_changes(old_child, new_child, TRUE);
 
         } else {
             mark_child_deleted(old_child, new_xml);
         }
     }
 
     // Check for moved or created children
     for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) {
         xmlNode *new_child = cIter;
         xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true);
 
         cIter = pcmk__xml_next(cIter);
         if(old_child == NULL) {
             // This is a newly created child
             nodepriv = new_child->_private;
             pcmk__set_xml_flags(nodepriv, pcmk__xf_skip);
             mark_xml_changes(old_child, new_child, TRUE);
 
         } else {
             /* Check for movement, we already checked for differences */
             int p_new = pcmk__xml_position(new_child, pcmk__xf_skip);
             int p_old = pcmk__xml_position(old_child, pcmk__xf_skip);
 
             if(p_old != p_new) {
                 mark_child_moved(old_child, new_xml, new_child, p_old, p_new);
             }
         }
     }
 }
 
 void
 xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml)
 {
     pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy);
     xml_calculate_changes(old_xml, new_xml);
 }
 
 // Called functions may set the \p pcmk__xf_skip flag on parts of \p old_xml
 void
 xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml)
 {
     CRM_CHECK((old_xml != NULL) && (new_xml != NULL)
               && pcmk__xe_is(old_xml, (const char *) new_xml->name)
               && pcmk__str_eq(pcmk__xe_id(old_xml), pcmk__xe_id(new_xml),
                               pcmk__str_none),
               return);
 
     if(xml_tracking_changes(new_xml) == FALSE) {
         xml_track_changes(new_xml, NULL, NULL, FALSE);
     }
 
     mark_xml_changes(old_xml, new_xml, FALSE);
 }
 
 gboolean
 can_prune_leaf(xmlNode * xml_node)
 {
     xmlNode *cIter = NULL;
     gboolean can_prune = TRUE;
 
     CRM_CHECK(xml_node != NULL, return FALSE);
 
     /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed for
      * rolling upgrades)
      */
     if (pcmk__strcase_any_of((const char *) xml_node->name,
                              PCMK_XE_RESOURCE_REF, PCMK_XE_OBJ_REF,
                              PCMK_XE_ROLE, PCMK__XE_ROLE_REF,
                              NULL)) {
         return FALSE;
     }
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) {
         const char *p_name = (const char *) a->name;
 
         if (strcmp(p_name, PCMK_XA_ID) == 0) {
             continue;
         }
         can_prune = FALSE;
     }
 
     cIter = pcmk__xml_first_child(xml_node);
     while (cIter) {
         xmlNode *child = cIter;
 
         cIter = pcmk__xml_next(cIter);
         if (can_prune_leaf(child)) {
             free_xml(child);
         } else {
             can_prune = FALSE;
         }
     }
     return can_prune;
 }
 
 /*!
  * \internal
  * \brief Find a comment with matching content in specified XML
  *
  * \param[in] root            XML to search
  * \param[in] search_comment  Comment whose content should be searched for
  * \param[in] exact           If true, comment must also be at same position
  */
 xmlNode *
 pcmk__xc_match(const xmlNode *root, const xmlNode *search_comment, bool exact)
 {
     xmlNode *a_child = NULL;
     int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip);
 
     CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
 
     for (a_child = pcmk__xml_first_child(root); a_child != NULL;
          a_child = pcmk__xml_next(a_child)) {
         if (exact) {
             int offset = pcmk__xml_position(a_child, pcmk__xf_skip);
             xml_node_private_t *nodepriv = a_child->_private;
 
             if (offset < search_offset) {
                 continue;
 
             } else if (offset > search_offset) {
                 return NULL;
             }
 
             if (pcmk_is_set(nodepriv->flags, pcmk__xf_skip)) {
                 continue;
             }
         }
 
         if (a_child->type == XML_COMMENT_NODE
             && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) {
             return a_child;
 
         } else if (exact) {
             return NULL;
         }
     }
 
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Make one XML comment match another (in content)
  *
  * \param[in,out] parent   If \p target is NULL and this is not, add or update
  *                         comment child of this XML node that matches \p update
  * \param[in,out] target   If not NULL, update this XML comment node
  * \param[in]     update   Make comment content match this (must not be NULL)
  *
  * \note At least one of \parent and \target must be non-NULL
  */
 void
 pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update)
 {
     CRM_CHECK(update != NULL, return);
     CRM_CHECK(update->type == XML_COMMENT_NODE, return);
 
     if (target == NULL) {
         target = pcmk__xc_match(parent, update, false);
     }
 
     if (target == NULL) {
         add_node_copy(parent, update);
 
     } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) {
         xmlFree(target->content);
         target->content = xmlStrdup(update->content);
     }
 }
 
 /*!
  * \internal
  * \brief Make one XML tree match another (in children and attributes)
  *
  * \param[in,out] parent   If \p target is NULL and this is not, add or update
  *                         child of this XML node that matches \p update
  * \param[in,out] target   If not NULL, update this XML
  * \param[in]     update   Make the desired XML match this (must not be NULL)
  * \param[in]     as_diff  If false, expand "++" when making attributes match
  *
  * \note At least one of \p parent and \p target must be non-NULL
  */
 void
 pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update,
                  bool as_diff)
 {
     xmlNode *a_child = NULL;
     const char *object_name = NULL,
                *object_href = NULL,
                *object_href_val = NULL;
 
 #if XML_PARSER_DEBUG
     crm_log_xml_trace(update, "update:");
     crm_log_xml_trace(target, "target:");
 #endif
 
     CRM_CHECK(update != NULL, return);
 
     if (update->type == XML_COMMENT_NODE) {
         pcmk__xc_update(parent, target, update);
         return;
     }
 
     object_name = (const char *) update->name;
     object_href_val = pcmk__xe_id(update);
     if (object_href_val != NULL) {
         object_href = PCMK_XA_ID;
     } else {
         object_href_val = crm_element_value(update, PCMK_XA_ID_REF);
         object_href = (object_href_val == NULL)? NULL : PCMK_XA_ID_REF;
     }
 
     CRM_CHECK(object_name != NULL, return);
     CRM_CHECK(target != NULL || parent != NULL, return);
 
     if (target == NULL) {
         target = pcmk__xe_match(parent, object_name,
                                 object_href, object_href_val);
     }
 
     if (target == NULL) {
         target = create_xml_node(parent, object_name);
         CRM_CHECK(target != NULL, return);
 #if XML_PARSER_DEBUG
         crm_trace("Added  <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
                   object_href ? " " : "",
                   object_href ? object_href : "",
                   object_href ? "=" : "",
                   object_href ? object_href_val : "");
 
     } else {
         crm_trace("Found node <%s%s%s%s%s/> to update",
                   pcmk__s(object_name, "<null>"),
                   object_href ? " " : "",
                   object_href ? object_href : "",
                   object_href ? "=" : "",
                   object_href ? object_href_val : "");
 #endif
     }
 
     CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return);
 
     if (as_diff == FALSE) {
         /* So that expand_plus_plus() gets called */
         copy_in_properties(target, update);
 
     } else {
         /* No need for expand_plus_plus(), just raw speed */
         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
              a = a->next) {
             const char *p_value = pcmk__xml_attr_value(a);
 
             /* Remove it first so the ordering of the update is preserved */
             xmlUnsetProp(target, a->name);
             xmlSetProp(target, a->name, (pcmkXmlStr) p_value);
         }
     }
 
     for (a_child = pcmk__xml_first_child(update); a_child != NULL;
          a_child = pcmk__xml_next(a_child)) {
 #if XML_PARSER_DEBUG
         crm_trace("Updating child <%s%s%s%s%s/>",
                   pcmk__s(object_name, "<null>"),
                   object_href ? " " : "",
                   object_href ? object_href : "",
                   object_href ? "=" : "",
                   object_href ? object_href_val : "");
 #endif
         pcmk__xml_update(target, NULL, a_child, as_diff);
     }
 
 #if XML_PARSER_DEBUG
     crm_trace("Finished with <%s%s%s%s%s/>", pcmk__s(object_name, "<null>"),
               object_href ? " " : "",
               object_href ? object_href : "",
               object_href ? "=" : "",
               object_href ? object_href_val : "");
 #endif
 }
 
 gboolean
 update_xml_child(xmlNode * child, xmlNode * to_update)
 {
     gboolean can_update = TRUE;
     xmlNode *child_of_child = NULL;
 
     CRM_CHECK(child != NULL, return FALSE);
     CRM_CHECK(to_update != NULL, return FALSE);
 
     if (!pcmk__xe_is(to_update, (const char *) child->name)) {
         can_update = FALSE;
 
     } else if (!pcmk__str_eq(pcmk__xe_id(to_update), pcmk__xe_id(child),
                              pcmk__str_none)) {
         can_update = FALSE;
 
     } else if (can_update) {
 #if XML_PARSER_DEBUG
         crm_log_xml_trace(child, "Update match found...");
 #endif
         pcmk__xml_update(NULL, child, to_update, false);
     }
 
     for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL;
          child_of_child = pcmk__xml_next(child_of_child)) {
         /* only update the first one */
         if (can_update) {
             break;
         }
         can_update = update_xml_child(child_of_child, to_update);
     }
 
     return can_update;
 }
 
 int
 find_xml_children(xmlNode ** children, xmlNode * root,
                   const char *tag, const char *field, const char *value, gboolean search_matches)
 {
     int match_found = 0;
 
     CRM_CHECK(root != NULL, return FALSE);
     CRM_CHECK(children != NULL, return FALSE);
 
     if ((tag != NULL) && !pcmk__xe_is(root, tag)) {
 
     } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) {
 
     } else {
         if (*children == NULL) {
             *children = create_xml_node(NULL, __func__);
         }
         add_node_copy(*children, root);
         match_found = 1;
     }
 
     if (search_matches || match_found == 0) {
         xmlNode *child = NULL;
 
         for (child = pcmk__xml_first_child(root); child != NULL;
              child = pcmk__xml_next(child)) {
             match_found += find_xml_children(children, child, tag, field, value, search_matches);
         }
     }
 
     return match_found;
 }
 
 gboolean
 replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
 {
     gboolean can_delete = FALSE;
     xmlNode *child_of_child = NULL;
 
     const char *up_id = NULL;
     const char *child_id = NULL;
     const char *right_val = NULL;
 
     CRM_CHECK(child != NULL, return FALSE);
     CRM_CHECK(update != NULL, return FALSE);
 
     up_id = pcmk__xe_id(update);
     child_id = pcmk__xe_id(child);
 
     if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
         can_delete = TRUE;
     }
     if (!pcmk__xe_is(update, (const char *) child->name)) {
         can_delete = FALSE;
     }
     if (can_delete && delete_only) {
         for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL;
              a = a->next) {
             const char *p_name = (const char *) a->name;
             const char *p_value = pcmk__xml_attr_value(a);
 
             right_val = crm_element_value(child, p_name);
             if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) {
                 can_delete = FALSE;
             }
         }
     }
 
     if (can_delete && parent != NULL) {
         crm_log_xml_trace(child, "Delete match found...");
         if (delete_only || update == NULL) {
             free_xml(child);
 
         } else {
             xmlNode *old = child;
             xmlNode *new = xmlCopyNode(update, 1);
 
             CRM_ASSERT(new != NULL);
 
             // May be unnecessary but avoids slight changes to some test outputs
             reset_xml_node_flags(new);
 
             old = xmlReplaceNode(old, new);
 
             if (xml_tracking_changes(new)) {
                 // Replaced sections may have included relevant ACLs
                 pcmk__apply_acl(new);
             }
             xml_calculate_changes(old, new);
             xmlFreeNode(old);
         }
         return TRUE;
 
     } else if (can_delete) {
         crm_log_xml_debug(child, "Cannot delete the search root");
         can_delete = FALSE;
     }
 
     child_of_child = pcmk__xml_first_child(child);
     while (child_of_child) {
         xmlNode *next = pcmk__xml_next(child_of_child);
 
         can_delete = replace_xml_child(child, child_of_child, update, delete_only);
 
         /* only delete the first one */
         if (can_delete) {
             child_of_child = NULL;
         } else {
             child_of_child = next;
         }
     }
 
     return can_delete;
 }
 
 xmlNode *
 sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
 {
     xmlNode *child = NULL;
     GSList *nvpairs = NULL;
     xmlNode *result = NULL;
 
     CRM_CHECK(input != NULL, return NULL);
 
     result = create_xml_node(parent, (const char *) input->name);
     nvpairs = pcmk_xml_attrs2nvpairs(input);
     nvpairs = pcmk_sort_nvpairs(nvpairs);
     pcmk_nvpairs2xml_attrs(nvpairs, result);
     pcmk_free_nvpairs(nvpairs);
 
     for (child = pcmk__xml_first_child(input); child != NULL;
          child = pcmk__xml_next(child)) {
 
         if (recursive) {
             sorted_xml(child, result, recursive);
         } else {
             add_node_copy(result, child);
         }
     }
 
     return result;
 }
 
 xmlNode *
 first_named_child(const xmlNode *parent, const char *name)
 {
     xmlNode *match = NULL;
 
     for (match = pcmk__xe_first_child(parent); match != NULL;
          match = pcmk__xe_next(match)) {
         /*
          * name == NULL gives first child regardless of name; this is
          * semantically incorrect in this function, but may be necessary
          * due to prior use of xml_child_iter_filter
          */
         if ((name == NULL) || pcmk__xe_is(match, name)) {
             return match;
         }
     }
     return NULL;
 }
 
 /*!
  * \brief Get next instance of same XML tag
  *
  * \param[in] sibling  XML tag to start from
  *
  * \return Next sibling XML tag with same name
  */
 xmlNode *
 crm_next_same_xml(const xmlNode *sibling)
 {
     xmlNode *match = pcmk__xe_next(sibling);
 
     while (match != NULL) {
         if (pcmk__xe_is(match, (const char *) sibling->name)) {
             return match;
         }
         match = pcmk__xe_next(match);
     }
     return NULL;
 }
 
 void
 crm_xml_init(void)
 {
     static bool init = true;
 
     if(init) {
         init = false;
         /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
          * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds)
          * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
          * less than 1 second.
          */
         xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
 
         /* Populate and free the _private field when nodes are created and destroyed */
         xmlDeregisterNodeDefault(free_private_data);
         xmlRegisterNodeDefault(new_private_data);
 
         crm_schema_init();
     }
 }
 
 void
 crm_xml_cleanup(void)
 {
     crm_schema_cleanup();
     xmlCleanupParser();
 }
 
 #define XPATH_MAX 512
 
 xmlNode *
 expand_idref(xmlNode * input, xmlNode * top)
 {
     char *xpath = NULL;
     const char *ref = NULL;
     xmlNode *result = NULL;
 
     if (input == NULL) {
         return NULL;
     }
 
     ref = crm_element_value(input, PCMK_XA_ID_REF);
     if (ref == NULL) {
         return input;
     }
 
     if (top == NULL) {
         top = input;
     }
 
     xpath = crm_strdup_printf("//%s[@" PCMK_XA_ID "='%s']", input->name, ref);
     result = get_xpath_object(xpath, top, LOG_DEBUG);
     if (result == NULL) { // Not possible with schema validation enabled
         pcmk__config_err("Ignoring invalid %s configuration: "
                          PCMK_XA_ID_REF " '%s' does not reference "
                          "a valid object " CRM_XS " xpath=%s",
                          input->name, ref, xpath);
     }
     free(xpath);
     return result;
 }
 
 char *
 pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns)
 {
     static const char *base = NULL;
     char *ret = NULL;
 
     if (base == NULL) {
         base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY);
     }
     if (pcmk__str_empty(base)) {
         base = CRM_SCHEMA_DIRECTORY;
     }
 
     switch (ns) {
         case pcmk__xml_artefact_ns_legacy_rng:
         case pcmk__xml_artefact_ns_legacy_xslt:
             ret = strdup(base);
             break;
         case pcmk__xml_artefact_ns_base_rng:
         case pcmk__xml_artefact_ns_base_xslt:
             ret = crm_strdup_printf("%s/base", base);
             break;
         default:
             crm_err("XML artefact family specified as %u not recognized", ns);
     }
     return ret;
 }
 
 static char *
 find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec)
 {
     char *ret = NULL;
 
     switch (ns) {
         case pcmk__xml_artefact_ns_legacy_rng:
         case pcmk__xml_artefact_ns_base_rng:
             if (pcmk__ends_with(filespec, ".rng")) {
                 ret = crm_strdup_printf("%s/%s", path, filespec);
             } else {
                 ret = crm_strdup_printf("%s/%s.rng", path, filespec);
             }
             break;
         case pcmk__xml_artefact_ns_legacy_xslt:
         case pcmk__xml_artefact_ns_base_xslt:
             if (pcmk__ends_with(filespec, ".xsl")) {
                 ret = crm_strdup_printf("%s/%s", path, filespec);
             } else {
                 ret = crm_strdup_printf("%s/%s.xsl", path, filespec);
             }
             break;
         default:
             crm_err("XML artefact family specified as %u not recognized", ns);
     }
 
     return ret;
 }
 
 char *
 pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
 {
     struct stat sb;
     char *base = pcmk__xml_artefact_root(ns);
     char *ret = NULL;
 
     ret = find_artefact(ns, base, filespec);
     free(base);
 
     if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) {
         const char *remote_schema_dir = pcmk__remote_schema_dir();
         ret = find_artefact(ns, remote_schema_dir, filespec);
     }
 
     return ret;
 }
 
 void
 pcmk__xe_set_propv(xmlNodePtr node, va_list pairs)
 {
     while (true) {
         const char *name, *value;
 
         name = va_arg(pairs, const char *);
         if (name == NULL) {
             return;
         }
 
         value = va_arg(pairs, const char *);
         if (value != NULL) {
             crm_xml_add(node, name, value);
         }
     }
 }
 
 void
 pcmk__xe_set_props(xmlNodePtr node, ...)
 {
     va_list pairs;
     va_start(pairs, node);
     pcmk__xe_set_propv(node, pairs);
     va_end(pairs);
 }
 
 int
 pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
                        int (*handler)(xmlNode *xml, void *userdata),
                        void *userdata)
 {
     xmlNode *children = (xml? xml->children : NULL);
 
     CRM_ASSERT(handler != NULL);
 
     for (xmlNode *node = children; node != NULL; node = node->next) {
         if ((node->type == XML_ELEMENT_NODE)
             && ((child_element_name == NULL)
                 || pcmk__xe_is(node, child_element_name))) {
             int rc = handler(node, userdata);
 
             if (rc != pcmk_rc_ok) {
                 return rc;
             }
         }
     }
 
     return pcmk_rc_ok;
 }
 
 // Deprecated functions kept only for backward API compatibility
 // LCOV_EXCL_START
 
 #include <crm/common/xml_compat.h>
 
 xmlNode *
 find_entity(xmlNode *parent, const char *node_name, const char *id)
 {
     return pcmk__xe_match(parent, node_name,
                           ((id == NULL)? id : PCMK_XA_ID), id);
 }
 
 void
 crm_destroy_xml(gpointer data)
 {
     free_xml(data);
 }
 
 xmlDoc *
 getDocPtr(xmlNode *node)
 {
     xmlDoc *doc = NULL;
 
     CRM_CHECK(node != NULL, return NULL);
 
     doc = node->doc;
     if (doc == NULL) {
         doc = xmlNewDoc((pcmkXmlStr) "1.0");
         xmlDocSetRootElement(doc, node);
     }
     return doc;
 }
 
 int
 add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child)
 {
     add_node_copy(parent, child);
     free_xml(child);
     return 1;
 }
 
 gboolean
 xml_has_children(const xmlNode * xml_root)
 {
     if (xml_root != NULL && xml_root->children != NULL) {
         return TRUE;
     }
     return FALSE;
 }
 
 char *
 crm_xml_escape(const char *text)
 {
     size_t length = 0;
     char *copy = NULL;
 
     if (text == NULL) {
         return NULL;
     }
 
     length = strlen(text);
     copy = strdup(text);
     CRM_ASSERT(copy != NULL);
     for (size_t index = 0; index <= length; index++) {
         if(copy[index] & 0x80 && copy[index+1] & 0x80){
             index++;
             continue;
         }
         switch (copy[index]) {
             case 0:
                 // Sanity only; loop should stop at the last non-null byte
                 break;
             case '<':
                 copy = replace_text(copy, &index, &length, "&lt;");
                 break;
             case '>':
                 copy = replace_text(copy, &index, &length, "&gt;");
                 break;
             case '"':
                 copy = replace_text(copy, &index, &length, "&quot;");
                 break;
             case '\'':
                 copy = replace_text(copy, &index, &length, "&apos;");
                 break;
             case '&':
                 copy = replace_text(copy, &index, &length, "&amp;");
                 break;
             case '\t':
                 /* Might as well just expand to a few spaces... */
                 copy = replace_text(copy, &index, &length, "    ");
                 break;
             case '\n':
                 copy = replace_text(copy, &index, &length, "\\n");
                 break;
             case '\r':
                 copy = replace_text(copy, &index, &length, "\\r");
                 break;
             default:
                 /* Check for and replace non-printing characters with their octal equivalent */
                 if(copy[index] < ' ' || copy[index] > '~') {
                     char *replace = crm_strdup_printf("\\%.3o", copy[index]);
 
                     copy = replace_text(copy, &index, &length, replace);
                     free(replace);
                 }
         }
     }
     return copy;
 }
 
 // LCOV_EXCL_STOP
 // End deprecated API
diff --git a/lib/pacemaker/pcmk_graph_consumer.c b/lib/pacemaker/pcmk_graph_consumer.c
index 1b16814ef1..9ceb24ce64 100644
--- a/lib/pacemaker/pcmk_graph_consumer.c
+++ b/lib/pacemaker/pcmk_graph_consumer.c
@@ -1,882 +1,882 @@
 /*
  * 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 <crm_internal.h>
 
 #include <sys/param.h>
 #include <sys/stat.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/lrmd_internal.h>
 #include <pacemaker-internal.h>
 
 
 /*
  * Functions for updating graph
  */
 
 /*!
  * \internal
  * \brief Update synapse after completed prerequisite
  *
  * A synapse is ready to be executed once all its prerequisite actions (inputs)
  * complete. Given a completed action, check whether it is an input for a given
  * synapse, and if so, mark the input as confirmed, and mark the synapse as
  * ready if appropriate.
  *
  * \param[in,out] synapse    Transition graph synapse to update
  * \param[in]     action_id  ID of an action that completed
  *
  * \note The only substantial effect here is confirming synapse inputs.
  *       should_fire_synapse() will recalculate pcmk__synapse_ready, so the only
  *       thing that uses the pcmk__synapse_ready from here is
  *       synapse_state_str().
  */
 static void
 update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id)
 {
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         return; // All inputs have already been confirmed
     }
 
     // Presume ready until proven otherwise
     pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
 
     for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
 
         if (prereq->id == action_id) {
             crm_trace("Confirming input %d of synapse %d",
                       action_id, synapse->id);
             pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed);
 
         } else if (!pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed)) {
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             crm_trace("Synapse %d still not ready after action %d",
                       synapse->id, action_id);
         }
     }
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         crm_trace("Synapse %d is now ready to execute", synapse->id);
     }
 }
 
 /*!
  * \internal
  * \brief Update action and synapse confirmation after action completion
  *
  * \param[in,out] synapse    Transition graph synapse that action belongs to
  * \param[in]     action_id  ID of action that completed
  */
 static void
 update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id)
 {
     bool all_confirmed = true;
 
     for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
 
         if (action->id == action_id) {
             crm_trace("Confirmed action %d of synapse %d",
                       action_id, synapse->id);
             pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
 
         } else if (all_confirmed &&
                    !pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
             all_confirmed = false;
             crm_trace("Synapse %d still not confirmed after action %d",
                       synapse->id, action_id);
         }
     }
 
     if (all_confirmed
         && !pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
         crm_trace("Confirmed synapse %d", synapse->id);
         pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
     }
 }
 
 /*!
  * \internal
  * \brief Update the transition graph with a completed action result
  *
  * \param[in,out] graph   Transition graph to update
  * \param[in]     action  Action that completed
  */
 void
 pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action)
 {
     for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if (pcmk_any_flags_set(synapse->flags,
                                pcmk__synapse_confirmed|pcmk__synapse_failed)) {
             continue; // This synapse already completed
 
         } else if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
             update_synapse_confirmed(synapse, action->id);
 
         } else if (!pcmk_is_set(action->flags, pcmk__graph_action_failed)
-                   || (synapse->priority == INFINITY)) {
+                   || (synapse->priority == PCMK_SCORE_INFINITY)) {
             update_synapse_ready(synapse, action->id);
         }
     }
 }
 
 
 /*
  * Functions for executing graph
  */
 
 /* A transition graph consists of various types of actions. The library caller
  * registers execution functions for each action type, which will be stored
  * here.
  */
 static pcmk__graph_functions_t *graph_fns = NULL;
 
 /*!
  * \internal
  * \brief Set transition graph execution functions
  *
  * \param[in]  Execution functions to use
  */
 void
 pcmk__set_graph_functions(pcmk__graph_functions_t *fns)
 {
     crm_debug("Setting custom functions for executing transition graphs");
     graph_fns = fns;
 
     CRM_ASSERT(graph_fns != NULL);
     CRM_ASSERT(graph_fns->rsc != NULL);
     CRM_ASSERT(graph_fns->cluster != NULL);
     CRM_ASSERT(graph_fns->pseudo != NULL);
     CRM_ASSERT(graph_fns->fence != NULL);
 }
 
 /*!
  * \internal
  * \brief Check whether a graph synapse is ready to be executed
  *
  * \param[in,out] graph    Transition graph that synapse is part of
  * \param[in,out] synapse  Synapse to check
  *
  * \return true if synapse is ready, false otherwise
  */
 static bool
 should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
 {
     GList *lpc = NULL;
 
     pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
     for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
 
         if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) {
             crm_trace("Input %d for synapse %d not yet confirmed",
                       prereq->id, synapse->id);
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             break;
 
         } else if (pcmk_is_set(prereq->flags, pcmk__graph_action_failed)
                    && !pcmk_is_set(prereq->flags,
                                    pcmk__graph_action_can_fail)) {
             crm_trace("Input %d for synapse %d confirmed but failed",
                       prereq->id, synapse->id);
             pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
             break;
         }
     }
     if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
         crm_trace("Synapse %d is ready to execute", synapse->id);
     } else {
         return false;
     }
 
     for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data;
 
         if (a->type == pcmk__pseudo_graph_action) {
             /* None of the below applies to pseudo ops */
 
         } else if (synapse->priority < graph->abort_priority) {
             crm_trace("Skipping synapse %d: priority %d is less than "
                       "abort priority %d",
                       synapse->id, synapse->priority, graph->abort_priority);
             graph->skipped++;
             return false;
 
         } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) {
             crm_trace("Deferring synapse %d: not allowed", synapse->id);
             return false;
         }
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Initiate an action from a transition graph
  *
  * \param[in,out] graph   Transition graph containing action
  * \param[in,out] action  Action to execute
  *
  * \return Standard Pacemaker return code
  */
 static int
 initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *id = pcmk__xe_id(action->xml);
 
     CRM_CHECK(id != NULL, return EINVAL);
     CRM_CHECK(!pcmk_is_set(action->flags, pcmk__graph_action_executed),
               return pcmk_rc_already);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_executed);
     switch (action->type) {
         case pcmk__pseudo_graph_action:
             crm_trace("Executing pseudo-action %d (%s)", action->id, id);
             return graph_fns->pseudo(graph, action);
 
         case pcmk__rsc_graph_action:
             crm_trace("Executing resource action %d (%s)", action->id, id);
             return graph_fns->rsc(graph, action);
 
         case pcmk__cluster_graph_action:
             if (pcmk__str_eq(crm_element_value(action->xml, PCMK_XA_OPERATION),
                              PCMK_ACTION_STONITH, pcmk__str_none)) {
                 crm_trace("Executing fencing action %d (%s)",
                           action->id, id);
                 return graph_fns->fence(graph, action);
             }
             crm_trace("Executing cluster action %d (%s)", action->id, id);
             return graph_fns->cluster(graph, action);
 
         default:
             crm_err("Unsupported graph action type <%s " PCMK_XA_ID "='%s'> "
                     "(bug?)",
                     action->xml->name, id);
             return EINVAL;
     }
 }
 
 /*!
  * \internal
  * \brief Execute a graph synapse
  *
  * \param[in,out] graph    Transition graph with synapse to execute
  * \param[in,out] synapse  Synapse to execute
  *
  * \return Standard Pacemaker return value
  */
 static int
 fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
 {
     pcmk__set_synapse_flags(synapse, pcmk__synapse_executed);
     for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
         int rc = initiate_action(graph, action);
 
         if (rc != pcmk_rc_ok) {
             crm_err("Failed initiating <%s " PCMK_XA_ID "=%d> in synapse %d: "
                     "%s",
                     action->xml->name, action->id, synapse->id,
                     pcmk_rc_str(rc));
             pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
             pcmk__set_graph_action_flags(action,
                                          pcmk__graph_action_confirmed
                                          |pcmk__graph_action_failed);
             return pcmk_rc_error;
         }
     }
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Dummy graph method that can be used with simulations
  *
  * \param[in,out] graph   Transition graph containing action
  * \param[in,out] action  Graph action to be initiated
  *
  * \return Standard Pacemaker return code
  * \note If the PE_fail environment variable is set to the action ID,
  *       then the graph action will be marked as failed.
  */
 static int
 pseudo_action_dummy(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     static int fail = -1;
 
     if (fail < 0) {
         long long fail_ll;
 
         if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok)
             && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) {
             fail = (int) fail_ll;
         } else {
             fail = 0;
         }
     }
 
     if (action->id == fail) {
         crm_err("Dummy event handler: pretending action %d failed", action->id);
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
-        graph->abort_priority = INFINITY;
+        graph->abort_priority = PCMK_SCORE_INFINITY;
     } else {
         crm_trace("Dummy event handler: action %d initiated", action->id);
     }
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 static pcmk__graph_functions_t default_fns = {
     pseudo_action_dummy,
     pseudo_action_dummy,
     pseudo_action_dummy,
     pseudo_action_dummy
 };
 
 /*!
  * \internal
  * \brief Execute all actions in a transition graph
  *
  * \param[in,out] graph  Transition graph to execute
  *
  * \return Status of transition after execution
  */
 enum pcmk__graph_status
 pcmk__execute_graph(pcmk__graph_t *graph)
 {
     GList *lpc = NULL;
     int log_level = LOG_DEBUG;
     enum pcmk__graph_status pass_result = pcmk__graph_active;
     const char *status = "In progress";
 
     if (graph_fns == NULL) {
         graph_fns = &default_fns;
     }
     if (graph == NULL) {
         return pcmk__graph_complete;
     }
 
     graph->fired = 0;
     graph->pending = 0;
     graph->skipped = 0;
     graph->completed = 0;
     graph->incomplete = 0;
 
     // Count completed and in-flight synapses
     for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
             graph->completed++;
 
         } else if (!pcmk_is_set(synapse->flags, pcmk__synapse_failed)
                    && pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
             graph->pending++;
         }
     }
     crm_trace("Executing graph %d (%d synapses already completed, %d pending)",
               graph->id, graph->completed, graph->pending);
 
     // Execute any synapses that are ready
     for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
         pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
 
         if ((graph->batch_limit > 0)
             && (graph->pending >= graph->batch_limit)) {
 
             crm_debug("Throttling graph execution: batch limit (%d) reached",
                       graph->batch_limit);
             break;
 
         } else if (pcmk_is_set(synapse->flags, pcmk__synapse_failed)) {
             graph->skipped++;
             continue;
 
         } else if (pcmk_any_flags_set(synapse->flags,
                                       pcmk__synapse_confirmed
                                       |pcmk__synapse_executed)) {
             continue; // Already handled
 
         } else if (should_fire_synapse(graph, synapse)) {
             graph->fired++;
             if (fire_synapse(graph, synapse) != pcmk_rc_ok) {
                 crm_err("Synapse %d failed to fire", synapse->id);
                 log_level = LOG_ERR;
-                graph->abort_priority = INFINITY;
+                graph->abort_priority = PCMK_SCORE_INFINITY;
                 graph->incomplete++;
                 graph->fired--;
             }
 
             if (!(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) {
                 graph->pending++;
             }
 
         } else {
             crm_trace("Synapse %d cannot fire", synapse->id);
             graph->incomplete++;
         }
     }
 
     if ((graph->pending == 0) && (graph->fired == 0)) {
         graph->complete = true;
 
         if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) {
             log_level = LOG_WARNING;
             pass_result = pcmk__graph_terminated;
             status = "Terminated";
 
         } else if (graph->skipped != 0) {
             log_level = LOG_NOTICE;
             pass_result = pcmk__graph_complete;
             status = "Stopped";
 
         } else {
             log_level = LOG_NOTICE;
             pass_result = pcmk__graph_complete;
             status = "Complete";
         }
 
     } else if (graph->fired == 0) {
         pass_result = pcmk__graph_pending;
     }
 
     do_crm_log(log_level,
                "Transition %d (Complete=%d, Pending=%d,"
                " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s",
                graph->id, graph->completed, graph->pending, graph->fired,
                graph->skipped, graph->incomplete, graph->source, status);
 
     return pass_result;
 }
 
 
 /*
  * Functions for unpacking transition graph XML into structs
  */
 
 /*!
  * \internal
  * \brief Unpack a transition graph action from XML
  *
  * \param[in] parent      Synapse that action is part of
  * \param[in] xml_action  Action XML to unparse
  *
  * \return Newly allocated action on success, or NULL otherwise
  */
 static pcmk__graph_action_t *
 unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action)
 {
     enum pcmk__graph_action_type action_type;
     pcmk__graph_action_t *action = NULL;
     const char *value = pcmk__xe_id(xml_action);
 
     if (value == NULL) {
         crm_err("Ignoring transition graph action without id (bug?)");
         crm_log_xml_trace(xml_action, "invalid");
         return NULL;
     }
 
     if (pcmk__xe_is(xml_action, PCMK__XE_RSC_OP)) {
         action_type = pcmk__rsc_graph_action;
 
     } else if (pcmk__xe_is(xml_action, PCMK__XE_PSEUDO_EVENT)) {
         action_type = pcmk__pseudo_graph_action;
 
     } else if (pcmk__xe_is(xml_action, PCMK__XE_CRM_EVENT)) {
         action_type = pcmk__cluster_graph_action;
 
     } else {
         crm_err("Ignoring transition graph action of unknown type '%s' (bug?)",
                 xml_action->name);
         crm_log_xml_trace(xml_action, "invalid");
         return NULL;
     }
 
     action = calloc(1, sizeof(pcmk__graph_action_t));
     if (action == NULL) {
         crm_perror(LOG_CRIT, "Cannot unpack transition graph action");
         crm_log_xml_trace(xml_action, "lost");
         return NULL;
     }
 
     pcmk__scan_min_int(value, &(action->id), -1);
     action->type = pcmk__rsc_graph_action;
     action->xml = copy_xml(xml_action);
     action->synapse = parent;
     action->type = action_type;
     action->params = xml2list(action->xml);
 
     value = crm_meta_value(action->params, PCMK_META_TIMEOUT);
     pcmk__scan_min_int(value, &(action->timeout), 0);
 
     /* Take PCMK_META_START_DELAY into account for the timeout of the action
      * timer
      */
     value = crm_meta_value(action->params, PCMK_META_START_DELAY);
     {
         int start_delay;
 
         pcmk__scan_min_int(value, &start_delay, 0);
         action->timeout += start_delay;
     }
 
     if (pcmk__guint_from_hash(action->params, CRM_META "_" PCMK_META_INTERVAL,
                               0, &(action->interval_ms)) != pcmk_rc_ok) {
         action->interval_ms = 0;
     }
 
     value = crm_meta_value(action->params, PCMK__META_CAN_FAIL);
     if (value != NULL) {
         int can_fail = 0;
 
         if ((crm_str_to_boolean(value, &can_fail) > 0) && (can_fail > 0)) {
             pcmk__set_graph_action_flags(action, pcmk__graph_action_can_fail);
         } else {
             pcmk__clear_graph_action_flags(action, pcmk__graph_action_can_fail);
         }
 
 #ifndef PCMK__COMPAT_2_0
         if (pcmk_is_set(action->flags, pcmk__graph_action_can_fail)) {
             crm_warn("Support for the " PCMK__META_CAN_FAIL " meta-attribute "
                      "is deprecated and will be removed in a future release");
         }
 #endif
     }
 
     crm_trace("Action %d has timer set to %dms", action->id, action->timeout);
 
     return action;
 }
 
 /*!
  * \internal
  * \brief Unpack transition graph synapse from XML
  *
  * \param[in,out] new_graph    Transition graph that synapse is part of
  * \param[in]     xml_synapse  Synapse XML
  *
  * \return Newly allocated synapse on success, or NULL otherwise
  */
 static pcmk__graph_synapse_t *
 unpack_synapse(pcmk__graph_t *new_graph, const xmlNode *xml_synapse)
 {
     const char *value = NULL;
     xmlNode *action_set = NULL;
     pcmk__graph_synapse_t *new_synapse = NULL;
 
     crm_trace("Unpacking synapse %s", pcmk__xe_id(xml_synapse));
 
     new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t));
     if (new_synapse == NULL) {
         return NULL;
     }
 
     pcmk__scan_min_int(pcmk__xe_id(xml_synapse), &(new_synapse->id), 0);
 
     value = crm_element_value(xml_synapse, PCMK__XA_PRIORITY);
     pcmk__scan_min_int(value, &(new_synapse->priority), 0);
 
     CRM_CHECK(new_synapse->id >= 0, free(new_synapse);
                                     return NULL);
 
     new_graph->num_synapses++;
 
     crm_trace("Unpacking synapse %s action sets",
               crm_element_value(xml_synapse, PCMK_XA_ID));
 
     for (action_set = first_named_child(xml_synapse, "action_set");
          action_set != NULL; action_set = crm_next_same_xml(action_set)) {
 
         for (xmlNode *action = pcmk__xml_first_child(action_set);
              action != NULL; action = pcmk__xml_next(action)) {
 
             pcmk__graph_action_t *new_action = unpack_action(new_synapse,
                                                              action);
 
             if (new_action == NULL) {
                 continue;
             }
 
             crm_trace("Adding action %d to synapse %d",
                       new_action->id, new_synapse->id);
             new_graph->num_actions++;
             new_synapse->actions = g_list_append(new_synapse->actions,
                                                  new_action);
         }
     }
 
     crm_trace("Unpacking synapse %s inputs", pcmk__xe_id(xml_synapse));
 
     for (xmlNode *inputs = first_named_child(xml_synapse, "inputs");
          inputs != NULL; inputs = crm_next_same_xml(inputs)) {
 
         for (xmlNode *trigger = first_named_child(inputs, "trigger");
              trigger != NULL; trigger = crm_next_same_xml(trigger)) {
 
             for (xmlNode *input = pcmk__xml_first_child(trigger);
                  input != NULL; input = pcmk__xml_next(input)) {
 
                 pcmk__graph_action_t *new_input = unpack_action(new_synapse,
                                                                 input);
 
                 if (new_input == NULL) {
                     continue;
                 }
 
                 crm_trace("Adding input %d to synapse %d",
                            new_input->id, new_synapse->id);
 
                 new_synapse->inputs = g_list_append(new_synapse->inputs,
                                                     new_input);
             }
         }
     }
 
     return new_synapse;
 }
 
 /*!
  * \internal
  * \brief Unpack transition graph XML
  *
  * \param[in] xml_graph  Transition graph XML to unpack
  * \param[in] reference  Where the XML came from (for logging)
  *
  * \return Newly allocated transition graph on success, NULL otherwise
  * \note The caller is responsible for freeing the return value using
  *       pcmk__free_graph().
  * \note The XML is expected to be structured like:
          <transition_graph ...>
            <synapse id="0">
              <action_set>
                <rsc_op id="2" ...>
                ...
              </action_set>
              <inputs>
                  <rsc_op id="1" ...
                  ...
              </inputs>
            </synapse>
            ...
          </transition_graph>
  */
 pcmk__graph_t *
 pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference)
 {
     pcmk__graph_t *new_graph = NULL;
 
     new_graph = calloc(1, sizeof(pcmk__graph_t));
     if (new_graph == NULL) {
         return NULL;
     }
 
     new_graph->source = strdup(pcmk__s(reference, "unknown"));
     if (new_graph->source == NULL) {
         free(new_graph);
         return NULL;
     }
 
     new_graph->id = -1;
     new_graph->abort_priority = 0;
     new_graph->network_delay = 0;
     new_graph->stonith_timeout = 0;
     new_graph->completion_action = pcmk__graph_done;
 
     // Parse top-level attributes from PCMK__XE_TRANSITION_GRAPH
     if (xml_graph != NULL) {
         const char *buf = crm_element_value(xml_graph, "transition_id");
 
         CRM_CHECK(buf != NULL, free(new_graph);
                                return NULL);
         pcmk__scan_min_int(buf, &(new_graph->id), -1);
 
         buf = crm_element_value(xml_graph, PCMK_OPT_CLUSTER_DELAY);
         CRM_CHECK(buf != NULL, free(new_graph);
                                return NULL);
         pcmk_parse_interval_spec(buf, &(new_graph->network_delay));
 
         buf = crm_element_value(xml_graph, PCMK_OPT_STONITH_TIMEOUT);
         if (buf == NULL) {
             new_graph->stonith_timeout = new_graph->network_delay;
         } else {
             pcmk_parse_interval_spec(buf, &(new_graph->stonith_timeout));
         }
 
         // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum
         buf = crm_element_value(xml_graph, PCMK_OPT_BATCH_LIMIT);
         if ((buf == NULL)
             || (pcmk__scan_min_int(buf, &(new_graph->batch_limit),
                                    -1) != pcmk_rc_ok)) {
             new_graph->batch_limit = 0;
         }
 
         buf = crm_element_value(xml_graph, PCMK_OPT_MIGRATION_LIMIT);
         pcmk__scan_min_int(buf, &(new_graph->migration_limit), -1);
 
         pcmk__str_update(&(new_graph->failed_stop_offset),
                          crm_element_value(xml_graph, "failed-stop-offset"));
         pcmk__str_update(&(new_graph->failed_start_offset),
                          crm_element_value(xml_graph, "failed-start-offset"));
 
         if (crm_element_value_epoch(xml_graph, "recheck-by",
                                     &(new_graph->recheck_by)) != pcmk_ok) {
             new_graph->recheck_by = 0;
         }
     }
 
     // Unpack each child <synapse> element
     for (const xmlNode *synapse_xml = first_named_child(xml_graph, "synapse");
          synapse_xml != NULL; synapse_xml = crm_next_same_xml(synapse_xml)) {
 
         pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph,
                                                             synapse_xml);
 
         if (new_synapse != NULL) {
             new_graph->synapses = g_list_append(new_graph->synapses,
                                                 new_synapse);
         }
     }
 
     crm_debug("Unpacked transition %d from %s: %d actions in %d synapses",
               new_graph->id, new_graph->source, new_graph->num_actions,
               new_graph->num_synapses);
 
     return new_graph;
 }
 
 
 /*
  * Functions for freeing transition graph objects
  */
 
 /*!
  * \internal
  * \brief Free a transition graph action object
  *
  * \param[in,out] user_data  Action to free
  */
 static void
 free_graph_action(gpointer user_data)
 {
     pcmk__graph_action_t *action = user_data;
 
     if (action->timer != 0) {
         crm_warn("Cancelling timer for graph action %d", action->id);
         g_source_remove(action->timer);
     }
     if (action->params != NULL) {
         g_hash_table_destroy(action->params);
     }
     free_xml(action->xml);
     free(action);
 }
 
 /*!
  * \internal
  * \brief Free a transition graph synapse object
  *
  * \param[in,out] user_data  Synapse to free
  */
 static void
 free_graph_synapse(gpointer user_data)
 {
     pcmk__graph_synapse_t *synapse = user_data;
 
     g_list_free_full(synapse->actions, free_graph_action);
     g_list_free_full(synapse->inputs, free_graph_action);
     free(synapse);
 }
 
 /*!
  * \internal
  * \brief Free a transition graph object
  *
  * \param[in,out] graph  Transition graph to free
  */
 void
 pcmk__free_graph(pcmk__graph_t *graph)
 {
     if (graph != NULL) {
         g_list_free_full(graph->synapses, free_graph_synapse);
         free(graph->source);
         free(graph->failed_stop_offset);
         free(graph->failed_start_offset);
         free(graph);
     }
 }
 
 
 /*
  * Other transition graph utilities
  */
 
 /*!
  * \internal
  * \brief Synthesize an executor event from a graph action
  *
  * \param[in] resource     If not NULL, use greater call ID than in this XML
  * \param[in] action       Graph action
  * \param[in] status       What to use as event execution status
  * \param[in] rc           What to use as event exit status
  * \param[in] exit_reason  What to use as event exit reason
  *
  * \return Newly allocated executor event on success, or NULL otherwise
  */
 lrmd_event_data_t *
 pcmk__event_from_graph_action(const xmlNode *resource,
                               const pcmk__graph_action_t *action,
                               int status, int rc, const char *exit_reason)
 {
     lrmd_event_data_t *op = NULL;
     GHashTableIter iter;
     const char *name = NULL;
     const char *value = NULL;
     xmlNode *action_resource = NULL;
 
     CRM_CHECK(action != NULL, return NULL);
     CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL);
 
     action_resource = first_named_child(action->xml, PCMK_XE_PRIMITIVE);
     CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid");
                                        return NULL);
 
     op = lrmd_new_event(pcmk__xe_id(action_resource),
                         crm_element_value(action->xml, PCMK_XA_OPERATION),
                         action->interval_ms);
     lrmd__set_result(op, rc, status, exit_reason);
     op->t_run = time(NULL);
     op->t_rcchange = op->t_run;
     op->params = pcmk__strkey_table(free, free);
 
     g_hash_table_iter_init(&iter, action->params);
     while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
         pcmk__insert_dup(op->params, name, value);
     }
 
     for (xmlNode *xop = pcmk__xml_first_child(resource); xop != NULL;
          xop = pcmk__xml_next(xop)) {
         int tmp = 0;
 
         crm_element_value_int(xop, PCMK__XA_CALL_ID, &tmp);
         crm_debug("Got call_id=%d for %s", tmp, pcmk__xe_id(resource));
         if (tmp > op->call_id) {
             op->call_id = tmp;
         }
     }
 
     op->call_id++;
     return op;
 }
diff --git a/lib/pacemaker/pcmk_sched_bundle.c b/lib/pacemaker/pcmk_sched_bundle.c
index 091bc012c1..1d1466be9f 100644
--- a/lib/pacemaker/pcmk_sched_bundle.c
+++ b/lib/pacemaker/pcmk_sched_bundle.c
@@ -1,1058 +1,1059 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 struct assign_data {
     const pcmk_node_t *prefer;
     bool stop_if_fail;
 };
 
 /*!
  * \internal
  * \brief Assign a single bundle replica's resources (other than container)
  *
  * \param[in,out] replica    Replica to assign
  * \param[in]     user_data  Preferred node, if any
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 assign_replica(pcmk__bundle_replica_t *replica, void *user_data)
 {
     pcmk_node_t *container_host = NULL;
 
     struct assign_data *assign_data = user_data;
     const pcmk_node_t *prefer = assign_data->prefer;
     bool stop_if_fail = assign_data->stop_if_fail;
 
     const pcmk_resource_t *bundle = pe__const_top_resource(replica->container,
                                                            true);
 
     if (replica->ip != NULL) {
         pcmk__rsc_trace(bundle, "Assigning bundle %s IP %s",
                         bundle->id, replica->ip->id);
         replica->ip->cmds->assign(replica->ip, prefer, stop_if_fail);
     }
 
     container_host = replica->container->allocated_to;
     if (replica->remote != NULL) {
         if (pcmk__is_pacemaker_remote_node(container_host)) {
             /* REMOTE_CONTAINER_HACK: "Nested" connection resources must be on
              * the same host because Pacemaker Remote only supports a single
              * active connection.
              */
             pcmk__new_colocation("#replica-remote-with-host-remote", NULL,
-                                 INFINITY, replica->remote,
+                                 PCMK_SCORE_INFINITY, replica->remote,
                                  container_host->details->remote_rsc, NULL,
                                  NULL, pcmk__coloc_influence);
         }
         pcmk__rsc_trace(bundle, "Assigning bundle %s connection %s",
                         bundle->id, replica->remote->id);
         replica->remote->cmds->assign(replica->remote, prefer, stop_if_fail);
     }
 
     if (replica->child != NULL) {
         pcmk_node_t *node = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, replica->child->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
             if (!pcmk__same_node(node, replica->node)) {
-                node->weight = -INFINITY;
+                node->weight = -PCMK_SCORE_INFINITY;
             } else if (!pcmk__threshold_reached(replica->child, node, NULL)) {
-                node->weight = INFINITY;
+                node->weight = PCMK_SCORE_INFINITY;
             }
         }
 
         pcmk__set_rsc_flags(replica->child->parent, pcmk_rsc_assigning);
         pcmk__rsc_trace(bundle, "Assigning bundle %s replica child %s",
                         bundle->id, replica->child->id);
         replica->child->cmds->assign(replica->child, replica->node,
                                      stop_if_fail);
         pcmk__clear_rsc_flags(replica->child->parent, pcmk_rsc_assigning);
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Assign a bundle resource to a node
  *
  * \param[in,out] rsc           Resource to assign to a node
  * \param[in]     prefer        Node to prefer, if all else is equal
  * \param[in]     stop_if_fail  If \c true and a primitive descendant of \p rsc
  *                              can't be assigned to a node, set the
  *                              descendant's next role to stopped and update
  *                              existing actions
  *
  * \return Node that \p rsc is assigned to, if assigned entirely to one node
  *
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 pcmk_node_t *
 pcmk__bundle_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                     bool stop_if_fail)
 {
     GList *containers = NULL;
     pcmk_resource_t *bundled_resource = NULL;
     struct assign_data assign_data = { prefer, stop_if_fail };
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
 
     pcmk__rsc_trace(rsc, "Assigning bundle %s", rsc->id);
     pcmk__set_rsc_flags(rsc, pcmk_rsc_assigning);
 
     pe__show_node_scores(!pcmk_is_set(rsc->cluster->flags,
                                       pcmk_sched_output_scores),
                          rsc, __func__, rsc->allowed_nodes, rsc->cluster);
 
     // Assign all containers first, so we know what nodes the bundle will be on
     containers = g_list_sort(pe__bundle_containers(rsc), pcmk__cmp_instance);
     pcmk__assign_instances(rsc, containers, pe__bundle_max(rsc),
                            rsc->fns->max_per_node(rsc));
     g_list_free(containers);
 
     // Then assign remaining replica resources
     pe__foreach_bundle_replica(rsc, assign_replica, (void *) &assign_data);
 
     // Finally, assign the bundled resources to each bundle node
     bundled_resource = pe__bundled_resource(rsc);
     if (bundled_resource != NULL) {
         pcmk_node_t *node = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, bundled_resource->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) {
             if (pe__node_is_bundle_instance(rsc, node)) {
                 node->weight = 0;
             } else {
-                node->weight = -INFINITY;
+                node->weight = -PCMK_SCORE_INFINITY;
             }
         }
         bundled_resource->cmds->assign(bundled_resource, prefer, stop_if_fail);
     }
 
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_assigning|pcmk_rsc_unassigned);
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Create actions for a bundle replica's resources (other than child)
  *
  * \param[in,out] replica    Replica to create actions for
  * \param[in]     user_data  Unused
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 create_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
 {
     if (replica->ip != NULL) {
         replica->ip->cmds->create_actions(replica->ip);
     }
     if (replica->container != NULL) {
         replica->container->cmds->create_actions(replica->container);
     }
     if (replica->remote != NULL) {
         replica->remote->cmds->create_actions(replica->remote);
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Create all actions needed for a given bundle resource
  *
  * \param[in,out] rsc  Bundle resource to create actions for
  */
 void
 pcmk__bundle_create_actions(pcmk_resource_t *rsc)
 {
     pcmk_action_t *action = NULL;
     GList *containers = NULL;
     pcmk_resource_t *bundled_resource = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
 
     pe__foreach_bundle_replica(rsc, create_replica_actions, NULL);
 
     containers = pe__bundle_containers(rsc);
     pcmk__create_instance_actions(rsc, containers);
     g_list_free(containers);
 
     bundled_resource = pe__bundled_resource(rsc);
     if (bundled_resource != NULL) {
         bundled_resource->cmds->create_actions(bundled_resource);
 
         if (pcmk_is_set(bundled_resource->flags, pcmk_rsc_promotable)) {
             pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTE, true, true);
             action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTED,
                                                true, true);
-            action->priority = INFINITY;
+            action->priority = PCMK_SCORE_INFINITY;
 
             pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTE, true, true);
             action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTED,
                                                true, true);
-            action->priority = INFINITY;
+            action->priority = PCMK_SCORE_INFINITY;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Create internal constraints for a bundle replica's resources
  *
  * \param[in,out] replica    Replica to create internal constraints for
  * \param[in,out] user_data  Replica's parent bundle
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 replica_internal_constraints(pcmk__bundle_replica_t *replica, void *user_data)
 {
     pcmk_resource_t *bundle = user_data;
 
     replica->container->cmds->internal_constraints(replica->container);
 
     // Start bundle -> start replica container
     pcmk__order_starts(bundle, replica->container,
                        pcmk__ar_unrunnable_first_blocks
                        |pcmk__ar_then_implies_first_graphed);
 
     // Stop bundle -> stop replica child and container
     if (replica->child != NULL) {
         pcmk__order_stops(bundle, replica->child,
                           pcmk__ar_then_implies_first_graphed);
     }
     pcmk__order_stops(bundle, replica->container,
                       pcmk__ar_then_implies_first_graphed);
 
     // Start replica container -> bundle is started
     pcmk__order_resource_actions(replica->container, PCMK_ACTION_START, bundle,
                                  PCMK_ACTION_RUNNING,
                                  pcmk__ar_first_implies_then_graphed);
 
     // Stop replica container -> bundle is stopped
     pcmk__order_resource_actions(replica->container, PCMK_ACTION_STOP, bundle,
                                  PCMK_ACTION_STOPPED,
                                  pcmk__ar_first_implies_then_graphed);
 
     if (replica->ip != NULL) {
         replica->ip->cmds->internal_constraints(replica->ip);
 
         // Replica IP address -> replica container (symmetric)
         pcmk__order_starts(replica->ip, replica->container,
                            pcmk__ar_unrunnable_first_blocks
                            |pcmk__ar_guest_allowed);
         pcmk__order_stops(replica->container, replica->ip,
                           pcmk__ar_then_implies_first|pcmk__ar_guest_allowed);
 
-        pcmk__new_colocation("#ip-with-container", NULL, INFINITY, replica->ip,
-                             replica->container, NULL, NULL,
+        pcmk__new_colocation("#ip-with-container", NULL, PCMK_SCORE_INFINITY,
+                             replica->ip, replica->container, NULL, NULL,
                              pcmk__coloc_influence);
     }
 
     if (replica->remote != NULL) {
         /* This handles ordering and colocating remote relative to container
          * (via "#resource-with-container"). Since IP is also ordered and
          * colocated relative to the container, we don't need to do anything
          * explicit here with IP.
          */
         replica->remote->cmds->internal_constraints(replica->remote);
     }
 
     if (replica->child != NULL) {
         CRM_ASSERT(replica->remote != NULL);
         // "Start remote then child" is implicit in scheduler's remote logic
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Create implicit constraints needed for a bundle resource
  *
  * \param[in,out] rsc  Bundle resource to create implicit constraints for
  */
 void
 pcmk__bundle_internal_constraints(pcmk_resource_t *rsc)
 {
     pcmk_resource_t *bundled_resource = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
 
     pe__foreach_bundle_replica(rsc, replica_internal_constraints, rsc);
 
     bundled_resource = pe__bundled_resource(rsc);
     if (bundled_resource == NULL) {
         return;
     }
 
     // Start bundle -> start bundled clone
     pcmk__order_resource_actions(rsc, PCMK_ACTION_START, bundled_resource,
                                  PCMK_ACTION_START,
                                  pcmk__ar_then_implies_first_graphed);
 
     // Bundled clone is started -> bundle is started
     pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_RUNNING,
                                  rsc, PCMK_ACTION_RUNNING,
                                  pcmk__ar_first_implies_then_graphed);
 
     // Stop bundle -> stop bundled clone
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP, bundled_resource,
                                  PCMK_ACTION_STOP,
                                  pcmk__ar_then_implies_first_graphed);
 
     // Bundled clone is stopped -> bundle is stopped
     pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_STOPPED,
                                  rsc, PCMK_ACTION_STOPPED,
                                  pcmk__ar_first_implies_then_graphed);
 
     bundled_resource->cmds->internal_constraints(bundled_resource);
 
     if (!pcmk_is_set(bundled_resource->flags, pcmk_rsc_promotable)) {
         return;
     }
     pcmk__promotable_restart_ordering(rsc);
 
     // Demote bundle -> demote bundled clone
     pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTE, bundled_resource,
                                  PCMK_ACTION_DEMOTE,
                                  pcmk__ar_then_implies_first_graphed);
 
     // Bundled clone is demoted -> bundle is demoted
     pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_DEMOTED,
                                  rsc, PCMK_ACTION_DEMOTED,
                                  pcmk__ar_first_implies_then_graphed);
 
     // Promote bundle -> promote bundled clone
     pcmk__order_resource_actions(rsc, PCMK_ACTION_PROMOTE,
                                  bundled_resource, PCMK_ACTION_PROMOTE,
                                  pcmk__ar_then_implies_first_graphed);
 
     // Bundled clone is promoted -> bundle is promoted
     pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_PROMOTED,
                                  rsc, PCMK_ACTION_PROMOTED,
                                  pcmk__ar_first_implies_then_graphed);
 }
 
 struct match_data {
     const pcmk_node_t *node;    // Node to compare against replica
     pcmk_resource_t *container; // Replica container corresponding to node
 };
 
 /*!
  * \internal
  * \brief Check whether a replica container is assigned to a given node
  *
  * \param[in]     replica    Replica to check
  * \param[in,out] user_data  struct match_data with node to compare against
  *
  * \return true if the replica does not match (to indicate further replicas
  *         should be processed), otherwise false
  */
 static bool
 match_replica_container(const pcmk__bundle_replica_t *replica, void *user_data)
 {
     struct match_data *match_data = user_data;
 
     if (pcmk__instance_matches(replica->container, match_data->node,
                                pcmk_role_unknown, false)) {
         match_data->container = replica->container;
         return false; // Match found, don't bother searching further replicas
     }
     return true; // No match, keep searching
 }
 
 /*!
  * \internal
  * \brief Get the host to which a bundle node is assigned
  *
  * \param[in] node  Possible bundle node to check
  *
  * \return Node to which the container for \p node is assigned if \p node is a
  *         bundle node, otherwise \p node itself
  */
 static const pcmk_node_t *
 get_bundle_node_host(const pcmk_node_t *node)
 {
     if (pcmk__is_bundle_node(node)) {
         const pcmk_resource_t *container = node->details->remote_rsc->container;
 
         return container->fns->location(container, NULL, 0);
     }
     return node;
 }
 
 /*!
  * \internal
  * \brief Find a bundle container compatible with a dependent resource
  *
  * \param[in] dependent  Dependent resource in colocation with bundle
  * \param[in] bundle     Bundle that \p dependent is colocated with
  *
  * \return A container from \p bundle assigned to the same node as \p dependent
  *         if assigned, otherwise assigned to any of dependent's allowed nodes,
  *         otherwise NULL.
  */
 static pcmk_resource_t *
 compatible_container(const pcmk_resource_t *dependent,
                      const pcmk_resource_t *bundle)
 {
     GList *scratch = NULL;
     struct match_data match_data = { NULL, NULL };
 
     // If dependent is assigned, only check there
     match_data.node = dependent->fns->location(dependent, NULL, 0);
     match_data.node = get_bundle_node_host(match_data.node);
     if (match_data.node != NULL) {
         pe__foreach_const_bundle_replica(bundle, match_replica_container,
                                          &match_data);
         return match_data.container;
     }
 
     // Otherwise, check for any of the dependent's allowed nodes
     scratch = g_hash_table_get_values(dependent->allowed_nodes);
     scratch = pcmk__sort_nodes(scratch, NULL);
     for (const GList *iter = scratch; iter != NULL; iter = iter->next) {
         match_data.node = iter->data;
         match_data.node = get_bundle_node_host(match_data.node);
         if (match_data.node == NULL) {
             continue;
         }
 
         pe__foreach_const_bundle_replica(bundle, match_replica_container,
                                          &match_data);
         if (match_data.container != NULL) {
             break;
         }
     }
     g_list_free(scratch);
     return match_data.container;
 }
 
 struct coloc_data {
     const pcmk__colocation_t *colocation;
     pcmk_resource_t *dependent;
     GList *container_hosts;
 };
 
 /*!
  * \internal
  * \brief Apply a colocation score to replica node scores or resource priority
  *
  * \param[in]     replica    Replica of primary bundle resource in colocation
  * \param[in,out] user_data  struct coloc_data for colocation being applied
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 replica_apply_coloc_score(const pcmk__bundle_replica_t *replica,
                           void *user_data)
 {
     struct coloc_data *coloc_data = user_data;
     pcmk_node_t *chosen = NULL;
 
-    if (coloc_data->colocation->score < INFINITY) {
+    if (coloc_data->colocation->score < PCMK_SCORE_INFINITY) {
         replica->container->cmds->apply_coloc_score(coloc_data->dependent,
                                                     replica->container,
                                                     coloc_data->colocation,
                                                     false);
         return true;
     }
 
     chosen = replica->container->fns->location(replica->container, NULL, 0);
     if ((chosen == NULL)
         || is_set_recursive(replica->container, pcmk_rsc_blocked, true)) {
         return true;
     }
 
     if ((coloc_data->colocation->primary_role >= pcmk_role_promoted)
         && ((replica->child == NULL)
             || (replica->child->next_role < pcmk_role_promoted))) {
         return true;
     }
 
     pcmk__rsc_trace(pe__const_top_resource(replica->container, true),
                     "Allowing mandatory colocation %s using %s @%d",
                     coloc_data->colocation->id, pcmk__node_name(chosen),
                     chosen->weight);
     coloc_data->container_hosts = g_list_prepend(coloc_data->container_hosts,
                                                  chosen);
     return true;
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint, apply its score to the dependent's
  * allowed node scores (if we are still placing resources) or priority (if
  * we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent resource in colocation
  * \param[in]     primary        Primary resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  * \param[in]     for_dependent  true if called on behalf of dependent
  */
 void
 pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
                                const pcmk_resource_t *primary,
                                const pcmk__colocation_t *colocation,
                                bool for_dependent)
 {
     struct coloc_data coloc_data = { colocation, dependent, NULL };
 
     /* This should never be called for the bundle itself as a dependent.
      * Instead, we add its colocation constraints to its containers and bundled
      * primitive and call the apply_coloc_score() method for them as dependents.
      */
     CRM_ASSERT((primary != NULL)
                && (primary->variant == pcmk_rsc_variant_bundle)
                && (dependent != NULL)
                && (dependent->variant == pcmk_rsc_variant_primitive)
                && (colocation != NULL) && !for_dependent);
 
     if (pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
         pcmk__rsc_trace(primary,
                         "Skipping applying colocation %s "
                         "because %s is still provisional",
                         colocation->id, primary->id);
         return;
     }
     pcmk__rsc_trace(primary, "Applying colocation %s (%s with %s at %s)",
                     colocation->id, dependent->id, primary->id,
                     pcmk_readable_score(colocation->score));
 
     /* If the constraint dependent is a clone or bundle, "dependent" here is one
      * of its instances. Look for a compatible instance of this bundle.
      */
     if (colocation->dependent->variant > pcmk_rsc_variant_group) {
         const pcmk_resource_t *primary_container = NULL;
 
         primary_container = compatible_container(dependent, primary);
         if (primary_container != NULL) { // Success, we found one
             pcmk__rsc_debug(primary, "Pairing %s with %s",
                             dependent->id, primary_container->id);
             dependent->cmds->apply_coloc_score(dependent, primary_container,
                                                colocation, true);
 
-        } else if (colocation->score >= INFINITY) { // Failure, and it's fatal
+        } else if (colocation->score >= PCMK_SCORE_INFINITY) {
+            // Failure, and it's fatal
             crm_notice("%s cannot run because there is no compatible "
                        "instance of %s to colocate with",
                        dependent->id, primary->id);
             pcmk__assign_resource(dependent, NULL, true, true);
 
         } else { // Failure, but we can ignore it
             pcmk__rsc_debug(primary,
                             "%s cannot be colocated with any instance of %s",
                             dependent->id, primary->id);
         }
         return;
     }
 
     pe__foreach_const_bundle_replica(primary, replica_apply_coloc_score,
                                      &coloc_data);
 
-    if (colocation->score >= INFINITY) {
+    if (colocation->score >= PCMK_SCORE_INFINITY) {
         pcmk__colocation_intersect_nodes(dependent, primary, colocation,
                                          coloc_data.container_hosts, false);
     }
     g_list_free(coloc_data.container_hosts);
 }
 
 // Bundle implementation of pcmk_assignment_methods_t:with_this_colocations()
 void
 pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
                               const pcmk_resource_t *orig_rsc, GList **list)
 {
     const pcmk_resource_t *bundled_rsc = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle)
                && (orig_rsc != NULL) && (list != NULL));
 
     // The bundle itself and its containers always get its colocations
     if ((orig_rsc == rsc)
         || pcmk_is_set(orig_rsc->flags, pcmk_rsc_replica_container)) {
 
         pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
         return;
     }
 
     /* The bundled resource gets the colocations if it's promotable and we've
      * begun choosing roles
      */
     bundled_rsc = pe__bundled_resource(rsc);
     if ((bundled_rsc == NULL)
         || !pcmk_is_set(bundled_rsc->flags, pcmk_rsc_promotable)
         || (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
         return;
     }
 
     if (orig_rsc == bundled_rsc) {
         if (pe__clone_flag_is_set(orig_rsc,
                                   pcmk__clone_promotion_constrained)) {
             /* orig_rsc is the clone and we're setting roles (or have already
              * done so)
              */
             pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
         }
 
     } else if (!pcmk_is_set(orig_rsc->flags, pcmk_rsc_unassigned)) {
         /* orig_rsc is an instance and is already assigned. If something
          * requests colocations for orig_rsc now, it's for setting roles.
          */
         pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
     }
 }
 
 // Bundle implementation of pcmk_assignment_methods_t:this_with_colocations()
 void
 pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
                               const pcmk_resource_t *orig_rsc, GList **list)
 {
     const pcmk_resource_t *bundled_rsc = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle)
                && (orig_rsc != NULL) && (list != NULL));
 
     // The bundle itself and its containers always get its colocations
     if ((orig_rsc == rsc)
         || pcmk_is_set(orig_rsc->flags, pcmk_rsc_replica_container)) {
 
         pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
         return;
     }
 
     /* The bundled resource gets the colocations if it's promotable and we've
      * begun choosing roles
      */
     bundled_rsc = pe__bundled_resource(rsc);
     if ((bundled_rsc == NULL)
         || !pcmk_is_set(bundled_rsc->flags, pcmk_rsc_promotable)
         || (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
         return;
     }
 
     if (orig_rsc == bundled_rsc) {
         if (pe__clone_flag_is_set(orig_rsc,
                                   pcmk__clone_promotion_constrained)) {
             /* orig_rsc is the clone and we're setting roles (or have already
              * done so)
              */
             pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
         }
 
     } else if (!pcmk_is_set(orig_rsc->flags, pcmk_rsc_unassigned)) {
         /* orig_rsc is an instance and is already assigned. If something
          * requests colocations for orig_rsc now, it's for setting roles.
          */
         pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Return action flags for a given bundle resource action
  *
  * \param[in,out] action  Bundle resource action to get flags for
  * \param[in]     node    If not NULL, limit effects to this node
  *
  * \return Flags appropriate to \p action on \p node
  */
 uint32_t
 pcmk__bundle_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
 {
     GList *containers = NULL;
     uint32_t flags = 0;
     pcmk_resource_t *bundled_resource = NULL;
 
     CRM_ASSERT((action != NULL) && (action->rsc != NULL)
                && (action->rsc->variant == pcmk_rsc_variant_bundle));
 
     bundled_resource = pe__bundled_resource(action->rsc);
     if (bundled_resource != NULL) {
         // Clone actions are done on the bundled clone resource, not container
         switch (get_complex_task(bundled_resource, action->task)) {
             case pcmk_action_unspecified:
             case pcmk_action_notify:
             case pcmk_action_notified:
             case pcmk_action_promote:
             case pcmk_action_promoted:
             case pcmk_action_demote:
             case pcmk_action_demoted:
                 return pcmk__collective_action_flags(action,
                                                      bundled_resource->children,
                                                      node);
             default:
                 break;
         }
     }
 
     containers = pe__bundle_containers(action->rsc);
     flags = pcmk__collective_action_flags(action, containers, node);
     g_list_free(containers);
     return flags;
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a bundle replica
  *
  * \param[in,out] replica    Replica to apply constraint to
  * \param[in,out] user_data  Location constraint to apply
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 apply_location_to_replica(pcmk__bundle_replica_t *replica, void *user_data)
 {
     pcmk__location_t *location = user_data;
 
     if (replica->container != NULL) {
         replica->container->cmds->apply_location(replica->container, location);
     }
     if (replica->ip != NULL) {
         replica->ip->cmds->apply_location(replica->ip, location);
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a bundle resource's allowed node scores
  *
  * \param[in,out] rsc       Bundle resource to apply constraint to
  * \param[in,out] location  Location constraint to apply
  */
 void
 pcmk__bundle_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
 {
     pcmk_resource_t *bundled_resource = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle)
                && (location != NULL));
 
     pcmk__apply_location(rsc, location);
     pe__foreach_bundle_replica(rsc, apply_location_to_replica, location);
 
     bundled_resource = pe__bundled_resource(rsc);
     if ((bundled_resource != NULL)
         && ((location->role_filter == pcmk_role_unpromoted)
             || (location->role_filter == pcmk_role_promoted))) {
         bundled_resource->cmds->apply_location(bundled_resource, location);
         bundled_resource->rsc_location = g_list_prepend(
             bundled_resource->rsc_location, location);
     }
 }
 
 #define XPATH_REMOTE "//nvpair[@name='" PCMK_REMOTE_RA_ADDR "']"
 
 /*!
  * \internal
  * \brief Add a bundle replica's actions to transition graph
  *
  * \param[in,out] replica    Replica to add to graph
  * \param[in]     user_data  Bundle that replica belongs to (for logging only)
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 add_replica_actions_to_graph(pcmk__bundle_replica_t *replica, void *user_data)
 {
     if ((replica->remote != NULL) && (replica->container != NULL)
         && pe__bundle_needs_remote_name(replica->remote)) {
 
         /* REMOTE_CONTAINER_HACK: Allow remote nodes to run containers that
          * run pacemaker-remoted inside, without needing a separate IP for
          * the container. This is done by configuring the inner remote's
          * connection host as the magic string "#uname", then
          * replacing it with the underlying host when needed.
          */
         xmlNode *nvpair = get_xpath_object(XPATH_REMOTE, replica->remote->xml,
                                            LOG_ERR);
         const char *calculated_addr = NULL;
 
         // Replace the value in replica->remote->xml (if appropriate)
         calculated_addr = pe__add_bundle_remote_name(replica->remote,
                                                      replica->remote->cluster,
                                                      nvpair, PCMK_XA_VALUE);
         if (calculated_addr != NULL) {
             /* Since this is for the bundle as a resource, and not any
              * particular action, replace the value in the default
              * parameters (not evaluated for node). create_graph_action()
              * will grab it from there to replace it in node-evaluated
              * parameters.
              */
             GHashTable *params = pe_rsc_params(replica->remote,
                                                NULL, replica->remote->cluster);
 
             pcmk__insert_dup(params, PCMK_REMOTE_RA_ADDR, calculated_addr);
         } else {
             pcmk_resource_t *bundle = user_data;
 
             /* The only way to get here is if the remote connection is
              * neither currently running nor scheduled to run. That means we
              * won't be doing any operations that require addr (only start
              * requires it; we additionally use it to compare digests when
              * unpacking status, promote, and migrate_from history, but
              * that's already happened by this point).
              */
             pcmk__rsc_info(bundle,
                            "Unable to determine address for bundle %s "
                            "remote connection", bundle->id);
         }
     }
     if (replica->ip != NULL) {
         replica->ip->cmds->add_actions_to_graph(replica->ip);
     }
     if (replica->container != NULL) {
         replica->container->cmds->add_actions_to_graph(replica->container);
     }
     if (replica->remote != NULL) {
         replica->remote->cmds->add_actions_to_graph(replica->remote);
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Add a bundle resource's actions to the transition graph
  *
  * \param[in,out] rsc  Bundle resource whose actions should be added
  */
 void
 pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc)
 {
     pcmk_resource_t *bundled_resource = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
 
     bundled_resource = pe__bundled_resource(rsc);
     if (bundled_resource != NULL) {
         bundled_resource->cmds->add_actions_to_graph(bundled_resource);
     }
     pe__foreach_bundle_replica(rsc, add_replica_actions_to_graph, rsc);
 }
 
 struct probe_data {
     pcmk_resource_t *bundle;    // Bundle being probed
     pcmk_node_t *node;          // Node to create probes on
     bool any_created;           // Whether any probes have been created
 };
 
 /*!
  * \internal
  * \brief Order a bundle replica's start after another replica's probe
  *
  * \param[in,out] replica    Replica to order start for
  * \param[in,out] user_data  Replica with probe to order after
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 order_replica_start_after(pcmk__bundle_replica_t *replica, void *user_data)
 {
     pcmk__bundle_replica_t *probed_replica = user_data;
 
     if ((replica == probed_replica) || (replica->container == NULL)) {
         return true;
     }
     pcmk__new_ordering(probed_replica->container,
                        pcmk__op_key(probed_replica->container->id,
                                     PCMK_ACTION_MONITOR, 0),
                        NULL, replica->container,
                        pcmk__op_key(replica->container->id, PCMK_ACTION_START,
                                     0),
                        NULL, pcmk__ar_ordered|pcmk__ar_if_on_same_node,
                        replica->container->cluster);
     return true;
 }
 
 /*!
  * \internal
  * \brief Create probes for a bundle replica's resources
  *
  * \param[in,out] replica    Replica to create probes for
  * \param[in,out] user_data  struct probe_data
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 create_replica_probes(pcmk__bundle_replica_t *replica, void *user_data)
 {
     struct probe_data *probe_data = user_data;
 
     if ((replica->ip != NULL)
         && replica->ip->cmds->create_probe(replica->ip, probe_data->node)) {
         probe_data->any_created = true;
     }
     if ((replica->child != NULL)
         && pcmk__same_node(probe_data->node, replica->node)
         && replica->child->cmds->create_probe(replica->child,
                                               probe_data->node)) {
         probe_data->any_created = true;
     }
     if ((replica->container != NULL)
         && replica->container->cmds->create_probe(replica->container,
                                                   probe_data->node)) {
         probe_data->any_created = true;
 
         /* If we're limited to one replica per host (due to
          * the lack of an IP range probably), then we don't
          * want any of our peer containers starting until
          * we've established that no other copies are already
          * running.
          *
          * Partly this is to ensure that the maximum replicas per host is
          * observed, but also to ensure that the containers
          * don't fail to start because the necessary port
          * mappings (which won't include an IP for uniqueness)
          * are already taken
          */
         if (probe_data->bundle->fns->max_per_node(probe_data->bundle) == 1) {
             pe__foreach_bundle_replica(probe_data->bundle,
                                        order_replica_start_after, replica);
         }
     }
     if ((replica->container != NULL) && (replica->remote != NULL)
         && replica->remote->cmds->create_probe(replica->remote,
                                                probe_data->node)) {
         /* Do not probe the remote resource until we know where the container is
          * running. This is required for REMOTE_CONTAINER_HACK to correctly
          * probe remote resources.
          */
         char *probe_uuid = pcmk__op_key(replica->remote->id,
                                         PCMK_ACTION_MONITOR, 0);
         pcmk_action_t *probe = find_first_action(replica->remote->actions,
                                                  probe_uuid, NULL,
                                                  probe_data->node);
 
         free(probe_uuid);
         if (probe != NULL) {
             probe_data->any_created = true;
             pcmk__rsc_trace(probe_data->bundle, "Ordering %s probe on %s",
                             replica->remote->id,
                             pcmk__node_name(probe_data->node));
             pcmk__new_ordering(replica->container,
                                pcmk__op_key(replica->container->id,
                                             PCMK_ACTION_START, 0),
                                NULL, replica->remote, NULL, probe,
                                pcmk__ar_nested_remote_probe,
                                probe_data->bundle->cluster);
         }
     }
     return true;
 }
 
 /*!
  * \internal
  *
  * \brief Schedule any probes needed for a bundle resource on a node
  *
  * \param[in,out] rsc   Bundle resource to create probes for
  * \param[in,out] node  Node to create probe on
  *
  * \return true if any probe was created, otherwise false
  */
 bool
 pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     struct probe_data probe_data = { rsc, node, false };
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
     pe__foreach_bundle_replica(rsc, create_replica_probes, &probe_data);
     return probe_data.any_created;
 }
 
 /*!
  * \internal
  * \brief Output actions for one bundle replica
  *
  * \param[in,out] replica    Replica to output actions for
  * \param[in]     user_data  Unused
  *
  * \return true (to indicate that any further replicas should be processed)
  */
 static bool
 output_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
 {
     if (replica->ip != NULL) {
         replica->ip->cmds->output_actions(replica->ip);
     }
     if (replica->container != NULL) {
         replica->container->cmds->output_actions(replica->container);
     }
     if (replica->remote != NULL) {
         replica->remote->cmds->output_actions(replica->remote);
     }
     if (replica->child != NULL) {
         replica->child->cmds->output_actions(replica->child);
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Output a summary of scheduled actions for a bundle resource
  *
  * \param[in,out] rsc  Bundle resource to output actions for
  */
 void
 pcmk__output_bundle_actions(pcmk_resource_t *rsc)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
     pe__foreach_bundle_replica(rsc, output_replica_actions, NULL);
 }
 
 // Bundle implementation of pcmk_assignment_methods_t:add_utilization()
 void
 pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
                              const pcmk_resource_t *orig_rsc, GList *all_rscs,
                              GHashTable *utilization)
 {
     pcmk_resource_t *container = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return;
     }
 
     /* All bundle replicas are identical, so using the utilization of the first
      * is sufficient for any. Only the implicit container resource can have
      * utilization values.
      */
     container = pe__first_container(rsc);
     if (container != NULL) {
         container->cmds->add_utilization(container, orig_rsc, all_rscs,
                                          utilization);
     }
 }
 
 // Bundle implementation of pcmk_assignment_methods_t:shutdown_lock()
 void
 pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_bundle));
     // Bundles currently don't support shutdown locks
 }
diff --git a/lib/pacemaker/pcmk_sched_clone.c b/lib/pacemaker/pcmk_sched_clone.c
index 2c6238bc54..379f666576 100644
--- a/lib/pacemaker/pcmk_sched_clone.c
+++ b/lib/pacemaker/pcmk_sched_clone.c
@@ -1,709 +1,709 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Assign a clone resource's instances to nodes
  *
  * \param[in,out] rsc           Clone resource to assign
  * \param[in]     prefer        Node to prefer, if all else is equal
  * \param[in]     stop_if_fail  If \c true and a primitive descendant of \p rsc
  *                              can't be assigned to a node, set the
  *                              descendant's next role to stopped and update
  *                              existing actions
  *
  * \return NULL (clones are not assigned to a single node)
  *
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 pcmk_node_t *
 pcmk__clone_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                    bool stop_if_fail)
 {
     GList *colocations = NULL;
 
     CRM_ASSERT(pcmk__is_clone(rsc));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return NULL; // Assignment has already been done
     }
 
     // Detect assignment loops
     if (pcmk_is_set(rsc->flags, pcmk_rsc_assigning)) {
         pcmk__rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
         return NULL;
     }
     pcmk__set_rsc_flags(rsc, pcmk_rsc_assigning);
 
     // If this clone is promotable, consider nodes' promotion scores
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__add_promotion_scores(rsc);
     }
 
     // If this clone is colocated with any other resources, assign those first
     colocations = pcmk__this_with_colocations(rsc);
     for (GList *iter = colocations; iter != NULL; iter = iter->next) {
         pcmk__colocation_t *constraint = (pcmk__colocation_t *) iter->data;
 
         pcmk__rsc_trace(rsc, "%s: Assigning colocation %s primary %s first",
                         rsc->id, constraint->id, constraint->primary->id);
         constraint->primary->cmds->assign(constraint->primary, prefer,
                                           stop_if_fail);
     }
     g_list_free(colocations);
 
     // If any resources are colocated with this one, consider their preferences
     colocations = pcmk__with_this_colocations(rsc);
     g_list_foreach(colocations, pcmk__add_dependent_scores, rsc);
     g_list_free(colocations);
 
     pe__show_node_scores(!pcmk_is_set(rsc->cluster->flags,
                                       pcmk_sched_output_scores),
                          rsc, __func__, rsc->allowed_nodes, rsc->cluster);
 
     rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance);
     pcmk__assign_instances(rsc, rsc->children, pe__clone_max(rsc),
                            pe__clone_node_max(rsc));
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__set_instance_roles(rsc);
     }
 
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_unassigned|pcmk_rsc_assigning);
     pcmk__rsc_trace(rsc, "Assigned clone %s", rsc->id);
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Create all actions needed for a given clone resource
  *
  * \param[in,out] rsc  Clone resource to create actions for
  */
 void
 pcmk__clone_create_actions(pcmk_resource_t *rsc)
 {
     CRM_ASSERT(pcmk__is_clone(rsc));
 
     pcmk__rsc_trace(rsc, "Creating actions for clone %s", rsc->id);
     pcmk__create_instance_actions(rsc, rsc->children);
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__create_promotable_actions(rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Create implicit constraints needed for a clone resource
  *
  * \param[in,out] rsc  Clone resource to create implicit constraints for
  */
 void
 pcmk__clone_internal_constraints(pcmk_resource_t *rsc)
 {
     bool ordered = false;
 
     CRM_ASSERT(pcmk__is_clone(rsc));
 
     pcmk__rsc_trace(rsc, "Creating internal constraints for clone %s", rsc->id);
 
     // Restart ordering: Stop -> stopped -> start -> started
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
                                  rsc, PCMK_ACTION_START,
                                  pcmk__ar_ordered);
     pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
                                  rsc, PCMK_ACTION_RUNNING,
                                  pcmk__ar_unrunnable_first_blocks);
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
                                  rsc, PCMK_ACTION_STOPPED,
                                  pcmk__ar_unrunnable_first_blocks);
 
     // Demoted -> stop and started -> promote
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTED,
                                      rsc, PCMK_ACTION_STOP,
                                      pcmk__ar_ordered);
         pcmk__order_resource_actions(rsc, PCMK_ACTION_RUNNING,
                                      rsc, PCMK_ACTION_PROMOTE,
                                      pcmk__ar_unrunnable_first_blocks);
     }
 
     ordered = pe__clone_is_ordered(rsc);
     if (ordered) {
         /* Ordered clone instances must start and stop by instance number. The
          * instances might have been previously shuffled for assignment or
          * promotion purposes, so re-sort them.
          */
         rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number);
     }
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         instance->cmds->internal_constraints(instance);
 
         // Start clone -> start instance -> clone started
         pcmk__order_starts(rsc, instance, pcmk__ar_unrunnable_first_blocks
                                           |pcmk__ar_then_implies_first_graphed);
         pcmk__order_resource_actions(instance, PCMK_ACTION_START,
                                      rsc, PCMK_ACTION_RUNNING,
                                      pcmk__ar_first_implies_then_graphed);
 
         // Stop clone -> stop instance -> clone stopped
         pcmk__order_stops(rsc, instance, pcmk__ar_then_implies_first_graphed);
         pcmk__order_resource_actions(instance, PCMK_ACTION_STOP,
                                      rsc, PCMK_ACTION_STOPPED,
                                      pcmk__ar_first_implies_then_graphed);
 
         /* Instances of ordered clones must be started and stopped by instance
          * number. Since only some instances may be starting or stopping, order
          * each instance relative to every later instance.
          */
         if (ordered) {
             for (GList *later = iter->next;
                  later != NULL; later = later->next) {
                 pcmk__order_starts(instance, (pcmk_resource_t *) later->data,
                                    pcmk__ar_ordered);
                 pcmk__order_stops((pcmk_resource_t *) later->data, instance,
                                   pcmk__ar_ordered);
             }
         }
     }
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__order_promotable_instances(rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether colocated resources can be interleaved
  *
  * \param[in] colocation  Colocation constraint with clone as primary
  *
  * \return true if colocated resources can be interleaved, otherwise false
  */
 static bool
 can_interleave(const pcmk__colocation_t *colocation)
 {
     const pcmk_resource_t *dependent = colocation->dependent;
 
     // Only colocations between clone or bundle resources use interleaving
     if (dependent->variant <= pcmk_rsc_variant_group) {
         return false;
     }
 
     // Only the dependent needs to be marked for interleaving
     if (!crm_is_true(g_hash_table_lookup(dependent->meta,
                                          PCMK_META_INTERLEAVE))) {
         return false;
     }
 
     /* @TODO Do we actually care about multiple primary instances sharing a
      * dependent instance?
      */
     if (dependent->fns->max_per_node(dependent)
         != colocation->primary->fns->max_per_node(colocation->primary)) {
         pcmk__config_err("Cannot interleave %s and %s because they do not "
                          "support the same number of instances per node",
                          dependent->id, colocation->primary->id);
         return false;
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint, apply its score to the dependent's
  * allowed node scores (if we are still placing resources) or priority (if
  * we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent resource in colocation
  * \param[in]     primary        Primary resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  * \param[in]     for_dependent  true if called on behalf of dependent
  */
 void
 pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
                               const pcmk_resource_t *primary,
                               const pcmk__colocation_t *colocation,
                               bool for_dependent)
 {
     const GList *iter = NULL;
 
     /* This should never be called for the clone itself as a dependent. Instead,
      * we add its colocation constraints to its instances and call the
      * apply_coloc_score() method for the instances as dependents.
      */
     CRM_ASSERT(!for_dependent);
 
     CRM_ASSERT((colocation != NULL) && pcmk__is_clone(primary)
                && (dependent != NULL)
                && (dependent->variant == pcmk_rsc_variant_primitive));
 
     if (pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
         pcmk__rsc_trace(primary,
                         "Delaying processing colocation %s "
                         "because cloned primary %s is still provisional",
                         colocation->id, primary->id);
         return;
     }
 
     pcmk__rsc_trace(primary, "Processing colocation %s (%s with clone %s @%s)",
                     colocation->id, dependent->id, primary->id,
                     pcmk_readable_score(colocation->score));
 
     // Apply role-specific colocations
     if (pcmk_is_set(primary->flags, pcmk_rsc_promotable)
         && (colocation->primary_role != pcmk_role_unknown)) {
 
         if (pcmk_is_set(dependent->flags, pcmk_rsc_unassigned)) {
             // We're assigning the dependent to a node
             pcmk__update_dependent_with_promotable(primary, dependent,
                                                    colocation);
             return;
         }
 
         if (colocation->dependent_role == pcmk_role_promoted) {
             // We're choosing a role for the dependent
             pcmk__update_promotable_dependent_priority(primary, dependent,
                                                        colocation);
             return;
         }
     }
 
     // Apply interleaved colocations
     if (can_interleave(colocation)) {
         const pcmk_resource_t *primary_instance = NULL;
 
         primary_instance = pcmk__find_compatible_instance(dependent, primary,
                                                           pcmk_role_unknown,
                                                           false);
         if (primary_instance != NULL) {
             pcmk__rsc_debug(primary, "Interleaving %s with %s",
                             dependent->id, primary_instance->id);
             dependent->cmds->apply_coloc_score(dependent, primary_instance,
                                                colocation, true);
 
-        } else if (colocation->score >= INFINITY) {
+        } else if (colocation->score >= PCMK_SCORE_INFINITY) {
             crm_notice("%s cannot run because it cannot interleave with "
                        "any instance of %s", dependent->id, primary->id);
             pcmk__assign_resource(dependent, NULL, true, true);
 
         } else {
             pcmk__rsc_debug(primary,
                             "%s will not colocate with %s "
                             "because no instance can interleave with it",
                             dependent->id, primary->id);
         }
 
         return;
     }
 
     // Apply mandatory colocations
-    if (colocation->score >= INFINITY) {
+    if (colocation->score >= PCMK_SCORE_INFINITY) {
         GList *primary_nodes = NULL;
 
         // Dependent can run only where primary will have unblocked instances
         for (iter = primary->children; iter != NULL; iter = iter->next) {
             const pcmk_resource_t *instance = iter->data;
             pcmk_node_t *chosen = instance->fns->location(instance, NULL, 0);
 
             if ((chosen != NULL)
                 && !is_set_recursive(instance, pcmk_rsc_blocked, TRUE)) {
                 pcmk__rsc_trace(primary, "Allowing %s: %s %d",
                                 colocation->id, pcmk__node_name(chosen),
                                 chosen->weight);
                 primary_nodes = g_list_prepend(primary_nodes, chosen);
             }
         }
         pcmk__colocation_intersect_nodes(dependent, primary, colocation,
                                          primary_nodes, false);
         g_list_free(primary_nodes);
         return;
     }
 
     // Apply optional colocations
     for (iter = primary->children; iter != NULL; iter = iter->next) {
         const pcmk_resource_t *instance = iter->data;
 
         instance->cmds->apply_coloc_score(dependent, instance, colocation,
                                           false);
     }
 }
 
 // Clone implementation of pcmk_assignment_methods_t:with_this_colocations()
 void
 pcmk__with_clone_colocations(const pcmk_resource_t *rsc,
                              const pcmk_resource_t *orig_rsc, GList **list)
 {
     CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
 
     pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
 
     if (rsc->parent != NULL) {
         rsc->parent->cmds->with_this_colocations(rsc->parent, orig_rsc, list);
     }
 }
 
 // Clone implementation of pcmk_assignment_methods_t:this_with_colocations()
 void
 pcmk__clone_with_colocations(const pcmk_resource_t *rsc,
                              const pcmk_resource_t *orig_rsc, GList **list)
 {
     CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return);
 
     pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
 
     if (rsc->parent != NULL) {
         rsc->parent->cmds->this_with_colocations(rsc->parent, orig_rsc, list);
     }
 }
 
 /*!
  * \internal
  * \brief Return action flags for a given clone resource action
  *
  * \param[in,out] action  Action to get flags for
  * \param[in]     node    If not NULL, limit effects to this node
  *
  * \return Flags appropriate to \p action on \p node
  */
 uint32_t
 pcmk__clone_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
 {
     CRM_ASSERT((action != NULL) && pcmk__is_clone(action->rsc));
 
     return pcmk__collective_action_flags(action, action->rsc->children, node);
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a clone resource's allowed node scores
  *
  * \param[in,out] rsc       Clone resource to apply constraint to
  * \param[in,out] location  Location constraint to apply
  */
 void
 pcmk__clone_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
 {
     CRM_CHECK((location != NULL) && pcmk__is_clone(rsc), return);
 
     pcmk__apply_location(rsc, location);
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         instance->cmds->apply_location(instance, location);
     }
 }
 
 // GFunc wrapper for calling the action_flags() resource method
 static void
 call_action_flags(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = user_data;
 
     rsc->cmds->action_flags((pcmk_action_t *) data, NULL);
 }
 
 /*!
  * \internal
  * \brief Add a clone resource's actions to the transition graph
  *
  * \param[in,out] rsc  Resource whose actions should be added
  */
 void
 pcmk__clone_add_actions_to_graph(pcmk_resource_t *rsc)
 {
     CRM_ASSERT(pcmk__is_clone(rsc));
 
     g_list_foreach(rsc->actions, call_action_flags, rsc);
     pe__create_clone_notifications(rsc);
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
 
         child_rsc->cmds->add_actions_to_graph(child_rsc);
     }
 
     pcmk__add_rsc_actions_to_graph(rsc);
     pe__free_clone_notification_data(rsc);
 }
 
 /*!
  * \internal
  * \brief Check whether a resource or any children have been probed on a node
  *
  * \param[in] rsc   Resource to check
  * \param[in] node  Node to check
  *
  * \return true if \p node is in the known_on table of \p rsc or any of its
  *         children, otherwise false
  */
 static bool
 rsc_probed_on(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     if (rsc->children != NULL) {
         for (GList *child_iter = rsc->children; child_iter != NULL;
              child_iter = child_iter->next) {
 
             pcmk_resource_t *child = (pcmk_resource_t *) child_iter->data;
 
             if (rsc_probed_on(child, node)) {
                 return true;
             }
         }
         return false;
     }
 
     if (rsc->known_on != NULL) {
         GHashTableIter iter;
         pcmk_node_t *known_node = NULL;
 
         g_hash_table_iter_init(&iter, rsc->known_on);
         while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &known_node)) {
             if (pcmk__same_node(node, known_node)) {
                 return true;
             }
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Find clone instance that has been probed on given node
  *
  * \param[in] clone  Clone resource to check
  * \param[in] node   Node to check
  *
  * \return Instance of \p clone that has been probed on \p node if any,
  *         otherwise NULL
  */
 static pcmk_resource_t *
 find_probed_instance_on(const pcmk_resource_t *clone, const pcmk_node_t *node)
 {
     for (GList *iter = clone->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         if (rsc_probed_on(instance, node)) {
             return instance;
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Probe an anonymous clone on a node
  *
  * \param[in,out] clone  Anonymous clone to probe
  * \param[in,out] node   Node to probe \p clone on
  */
 static bool
 probe_anonymous_clone(pcmk_resource_t *clone, pcmk_node_t *node)
 {
     // Check whether we already probed an instance on this node
     pcmk_resource_t *child = find_probed_instance_on(clone, node);
 
     // Otherwise, check if we plan to start an instance on this node
     for (GList *iter = clone->children; (iter != NULL) && (child == NULL);
          iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
         const pcmk_node_t *instance_node = NULL;
 
         instance_node = instance->fns->location(instance, NULL, 0);
         if (pcmk__same_node(instance_node, node)) {
             child = instance;
         }
     }
 
     // Otherwise, use the first clone instance
     if (child == NULL) {
         child = clone->children->data;
     }
 
     // Anonymous clones only need to probe a single instance
     return child->cmds->create_probe(child, node);
 }
 
 /*!
  * \internal
  * \brief Schedule any probes needed for a resource on a node
  *
  * \param[in,out] rsc   Resource to create probe for
  * \param[in,out] node  Node to create probe on
  *
  * \return true if any probe was created, otherwise false
  */
 bool
 pcmk__clone_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     CRM_ASSERT((node != NULL) && pcmk__is_clone(rsc));
 
     if (rsc->exclusive_discover) {
         /* The clone is configured to be probed only where a location constraint
          * exists with PCMK_XA_RESOURCE_DISCOVERY set to exclusive.
          *
          * This check is not strictly necessary here since the instance's
          * create_probe() method would also check, but doing it here is more
          * efficient (especially for unique clones with a large number of
          * instances), and affects the CRM_meta_notify_available_uname variable
          * passed with notify actions.
          */
         pcmk_node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes,
                                                    node->details->id);
 
         if ((allowed == NULL)
             || (allowed->rsc_discover_mode != pcmk_probe_exclusive)) {
             /* This node is not marked for resource discovery. Remove it from
              * allowed_nodes so that notifications contain only nodes that the
              * clone can possibly run on.
              */
             pcmk__rsc_trace(rsc,
                             "Skipping probe for %s on %s because resource has "
                             "exclusive discovery but is not allowed on node",
                             rsc->id, pcmk__node_name(node));
             g_hash_table_remove(rsc->allowed_nodes, node->details->id);
             return false;
         }
     }
 
     rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number);
     if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         return pcmk__probe_resource_list(rsc->children, node);
     } else {
         return probe_anonymous_clone(rsc, node);
     }
 }
 
 /*!
  * \internal
  * \brief Add meta-attributes relevant to transition graph actions to XML
  *
  * Add clone-specific meta-attributes needed for transition graph actions.
  *
  * \param[in]     rsc  Clone resource whose meta-attributes should be added
  * \param[in,out] xml  Transition graph action attributes XML to add to
  */
 void
 pcmk__clone_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml)
 {
     char *name = NULL;
 
     CRM_ASSERT(pcmk__is_clone(rsc) && (xml != NULL));
 
     name = crm_meta_name(PCMK_META_GLOBALLY_UNIQUE);
     crm_xml_add(xml, name, pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
     free(name);
 
     name = crm_meta_name(PCMK_META_NOTIFY);
     crm_xml_add(xml, name, pcmk__flag_text(rsc->flags, pcmk_rsc_notify));
     free(name);
 
     name = crm_meta_name(PCMK_META_CLONE_MAX);
     crm_xml_add_int(xml, name, pe__clone_max(rsc));
     free(name);
 
     name = crm_meta_name(PCMK_META_CLONE_NODE_MAX);
     crm_xml_add_int(xml, name, pe__clone_node_max(rsc));
     free(name);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         int promoted_max = pe__clone_promoted_max(rsc);
         int promoted_node_max = pe__clone_promoted_node_max(rsc);
 
         name = crm_meta_name(PCMK_META_PROMOTED_MAX);
         crm_xml_add_int(xml, name, promoted_max);
         free(name);
 
         name = crm_meta_name(PCMK_META_PROMOTED_NODE_MAX);
         crm_xml_add_int(xml, name, promoted_node_max);
         free(name);
 
         /* @COMPAT Maintain backward compatibility with resource agents that
          * expect the old names (deprecated since 2.0.0).
          */
         name = crm_meta_name(PCMK__META_PROMOTED_MAX_LEGACY);
         crm_xml_add_int(xml, name, promoted_max);
         free(name);
 
         name = crm_meta_name(PCMK__META_PROMOTED_NODE_MAX_LEGACY);
         crm_xml_add_int(xml, name, promoted_node_max);
         free(name);
     }
 }
 
 // Clone implementation of pcmk_assignment_methods_t:add_utilization()
 void
 pcmk__clone_add_utilization(const pcmk_resource_t *rsc,
                             const pcmk_resource_t *orig_rsc, GList *all_rscs,
                             GHashTable *utilization)
 {
     bool existing = false;
     pcmk_resource_t *child = NULL;
 
     CRM_ASSERT(pcmk__is_clone(rsc) && (orig_rsc != NULL)
                && (utilization != NULL));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return;
     }
 
     // Look for any child already existing in the list
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         child = (pcmk_resource_t *) iter->data;
         if (g_list_find(all_rscs, child)) {
             existing = true; // Keep checking remaining children
         } else {
             // If this is a clone of a group, look for group's members
             for (GList *member_iter = child->children; member_iter != NULL;
                  member_iter = member_iter->next) {
 
                 pcmk_resource_t *member = (pcmk_resource_t *) member_iter->data;
 
                 if (g_list_find(all_rscs, member) != NULL) {
                     // Add *child's* utilization, not group member's
                     child->cmds->add_utilization(child, orig_rsc, all_rscs,
                                                  utilization);
                     existing = true;
                     break;
                 }
             }
         }
     }
 
     if (!existing && (rsc->children != NULL)) {
         // If nothing was found, still add first child's utilization
         child = (pcmk_resource_t *) rsc->children->data;
 
         child->cmds->add_utilization(child, orig_rsc, all_rscs, utilization);
     }
 }
 
 // Clone implementation of pcmk_assignment_methods_t:shutdown_lock()
 void
 pcmk__clone_shutdown_lock(pcmk_resource_t *rsc)
 {
     CRM_ASSERT(pcmk__is_clone(rsc));
     return; // Clones currently don't support shutdown locks
 }
diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c
index 9108fffc7b..0aee0c5c54 100644
--- a/lib/pacemaker/pcmk_sched_colocation.c
+++ b/lib/pacemaker/pcmk_sched_colocation.c
@@ -1,1927 +1,1929 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/common/scheduler_internal.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "crm/common/util.h"
 #include "crm/common/xml_internal.h"
 #include "crm/common/xml.h"
 #include "libpacemaker_private.h"
 
 // Used to temporarily mark a node as unusable
-#define INFINITY_HACK   (INFINITY * -100)
+#define INFINITY_HACK   (PCMK_SCORE_INFINITY * -100)
 
 /*!
  * \internal
  * \brief Compare two colocations according to priority
  *
  * Compare two colocations according to the order in which they should be
  * considered, based on either their dependent resources or their primary
  * resources -- preferring (in order):
  *  * Colocation that is not \c NULL
  *  * Colocation whose resource has higher priority
  *  * Colocation whose resource is of a higher-level variant
  *    (bundle > clone > group > primitive)
  *  * Colocation whose resource is promotable, if both are clones
  *  * Colocation whose resource has lower ID in lexicographic order
  *
  * \param[in] colocation1  First colocation to compare
  * \param[in] colocation2  Second colocation to compare
  * \param[in] dependent    If \c true, compare colocations by dependent
  *                         priority; otherwise compare them by primary priority
  *
  * \return A negative number if \p colocation1 should be considered first,
  *         a positive number if \p colocation2 should be considered first,
  *         or 0 if order doesn't matter
  */
 static gint
 cmp_colocation_priority(const pcmk__colocation_t *colocation1,
                         const pcmk__colocation_t *colocation2, bool dependent)
 {
     const pcmk_resource_t *rsc1 = NULL;
     const pcmk_resource_t *rsc2 = NULL;
 
     if (colocation1 == NULL) {
         return 1;
     }
     if (colocation2 == NULL) {
         return -1;
     }
 
     if (dependent) {
         rsc1 = colocation1->dependent;
         rsc2 = colocation2->dependent;
         CRM_ASSERT(colocation1->primary != NULL);
     } else {
         rsc1 = colocation1->primary;
         rsc2 = colocation2->primary;
         CRM_ASSERT(colocation1->dependent != NULL);
     }
     CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
 
     if (rsc1->priority > rsc2->priority) {
         return -1;
     }
     if (rsc1->priority < rsc2->priority) {
         return 1;
     }
 
     // Process clones before primitives and groups
     if (rsc1->variant > rsc2->variant) {
         return -1;
     }
     if (rsc1->variant < rsc2->variant) {
         return 1;
     }
 
     /* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
      * clones (probably unnecessary, but avoids having to update regression
      * tests)
      */
     if (rsc1->variant == pcmk_rsc_variant_clone) {
         if (pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
             && !pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
             return -1;
         }
         if (!pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
             && pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
             return 1;
         }
     }
 
     return strcmp(rsc1->id, rsc2->id);
 }
 
 /*!
  * \internal
  * \brief Compare two colocations according to priority based on dependents
  *
  * Compare two colocations according to the order in which they should be
  * considered, based on their dependent resources -- preferring (in order):
  *  * Colocation that is not \c NULL
  *  * Colocation whose resource has higher priority
  *  * Colocation whose resource is of a higher-level variant
  *    (bundle > clone > group > primitive)
  *  * Colocation whose resource is promotable, if both are clones
  *  * Colocation whose resource has lower ID in lexicographic order
  *
  * \param[in] a  First colocation to compare
  * \param[in] b  Second colocation to compare
  *
  * \return A negative number if \p a should be considered first,
  *         a positive number if \p b should be considered first,
  *         or 0 if order doesn't matter
  */
 static gint
 cmp_dependent_priority(gconstpointer a, gconstpointer b)
 {
     return cmp_colocation_priority(a, b, true);
 }
 
 /*!
  * \internal
  * \brief Compare two colocations according to priority based on primaries
  *
  * Compare two colocations according to the order in which they should be
  * considered, based on their primary resources -- preferring (in order):
  *  * Colocation that is not \c NULL
  *  * Colocation whose primary has higher priority
  *  * Colocation whose primary is of a higher-level variant
  *    (bundle > clone > group > primitive)
  *  * Colocation whose primary is promotable, if both are clones
  *  * Colocation whose primary has lower ID in lexicographic order
  *
  * \param[in] a  First colocation to compare
  * \param[in] b  Second colocation to compare
  *
  * \return A negative number if \p a should be considered first,
  *         a positive number if \p b should be considered first,
  *         or 0 if order doesn't matter
  */
 static gint
 cmp_primary_priority(gconstpointer a, gconstpointer b)
 {
     return cmp_colocation_priority(a, b, false);
 }
 
 /*!
  * \internal
  * \brief Add a "this with" colocation constraint to a sorted list
  *
  * \param[in,out] list        List of constraints to add \p colocation to
  * \param[in]     colocation  Colocation constraint to add to \p list
  * \param[in]     rsc         Resource whose colocations we're getting (for
  *                            logging only)
  *
  * \note The list will be sorted using cmp_primary_priority().
  */
 void
 pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
                     const pcmk_resource_t *rsc)
 {
     CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
 
     pcmk__rsc_trace(rsc,
                     "Adding colocation %s (%s with %s using %s @%s) to "
                     "'this with' list for %s",
                     colocation->id, colocation->dependent->id,
                     colocation->primary->id, colocation->node_attribute,
                     pcmk_readable_score(colocation->score), rsc->id);
     *list = g_list_insert_sorted(*list, (gpointer) colocation,
                                  cmp_primary_priority);
 }
 
 /*!
  * \internal
  * \brief Add a list of "this with" colocation constraints to a list
  *
  * \param[in,out] list      List of constraints to add \p addition to
  * \param[in]     addition  List of colocation constraints to add to \p list
  * \param[in]     rsc       Resource whose colocations we're getting (for
  *                          logging only)
  *
  * \note The lists must be pre-sorted by cmp_primary_priority().
  */
 void
 pcmk__add_this_with_list(GList **list, GList *addition,
                          const pcmk_resource_t *rsc)
 {
     CRM_ASSERT((list != NULL) && (rsc != NULL));
 
     pcmk__if_tracing(
         {}, // Always add each colocation individually if tracing
         {
             if (*list == NULL) {
                 // Trivial case for efficiency if not tracing
                 *list = g_list_copy(addition);
                 return;
             }
         }
     );
 
     for (const GList *iter = addition; iter != NULL; iter = iter->next) {
         pcmk__add_this_with(list, addition->data, rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Add a "with this" colocation constraint to a sorted list
  *
  * \param[in,out] list        List of constraints to add \p colocation to
  * \param[in]     colocation  Colocation constraint to add to \p list
  * \param[in]     rsc         Resource whose colocations we're getting (for
  *                            logging only)
  *
  * \note The list will be sorted using cmp_dependent_priority().
  */
 void
 pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
                     const pcmk_resource_t *rsc)
 {
     CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
 
     pcmk__rsc_trace(rsc,
                     "Adding colocation %s (%s with %s using %s @%s) to "
                     "'with this' list for %s",
                     colocation->id, colocation->dependent->id,
                     colocation->primary->id, colocation->node_attribute,
                     pcmk_readable_score(colocation->score), rsc->id);
     *list = g_list_insert_sorted(*list, (gpointer) colocation,
                                  cmp_dependent_priority);
 }
 
 /*!
  * \internal
  * \brief Add a list of "with this" colocation constraints to a list
  *
  * \param[in,out] list      List of constraints to add \p addition to
  * \param[in]     addition  List of colocation constraints to add to \p list
  * \param[in]     rsc       Resource whose colocations we're getting (for
  *                          logging only)
  *
  * \note The lists must be pre-sorted by cmp_dependent_priority().
  */
 void
 pcmk__add_with_this_list(GList **list, GList *addition,
                          const pcmk_resource_t *rsc)
 {
     CRM_ASSERT((list != NULL) && (rsc != NULL));
 
     pcmk__if_tracing(
         {}, // Always add each colocation individually if tracing
         {
             if (*list == NULL) {
                 // Trivial case for efficiency if not tracing
                 *list = g_list_copy(addition);
                 return;
             }
         }
     );
 
     for (const GList *iter = addition; iter != NULL; iter = iter->next) {
         pcmk__add_with_this(list, addition->data, rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Add orderings necessary for an anti-colocation constraint
  *
  * \param[in,out] first_rsc   One resource in an anti-colocation
  * \param[in]     first_role  Anti-colocation role of \p first_rsc
  * \param[in]     then_rsc    Other resource in the anti-colocation
  * \param[in]     then_role   Anti-colocation role of \p then_rsc
  */
 static void
 anti_colocation_order(pcmk_resource_t *first_rsc, int first_role,
                       pcmk_resource_t *then_rsc, int then_role)
 {
     const char *first_tasks[] = { NULL, NULL };
     const char *then_tasks[] = { NULL, NULL };
 
     /* Actions to make first_rsc lose first_role */
     if (first_role == pcmk_role_promoted) {
         first_tasks[0] = PCMK_ACTION_DEMOTE;
 
     } else {
         first_tasks[0] = PCMK_ACTION_STOP;
 
         if (first_role == pcmk_role_unpromoted) {
             first_tasks[1] = PCMK_ACTION_PROMOTE;
         }
     }
 
     /* Actions to make then_rsc gain then_role */
     if (then_role == pcmk_role_promoted) {
         then_tasks[0] = PCMK_ACTION_PROMOTE;
 
     } else {
         then_tasks[0] = PCMK_ACTION_START;
 
         if (then_role == pcmk_role_unpromoted) {
             then_tasks[1] = PCMK_ACTION_DEMOTE;
         }
     }
 
     for (int first_lpc = 0;
          (first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) {
 
         for (int then_lpc = 0;
              (then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) {
 
             pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc],
                                          then_rsc, then_tasks[then_lpc],
                                          pcmk__ar_if_required_on_same_node);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Add a new colocation constraint to scheduler data
  *
  * \param[in]     id              XML ID for this constraint
  * \param[in]     node_attr       Colocate by this attribute (NULL for #uname)
  * \param[in]     score           Constraint score
  * \param[in,out] dependent       Resource to be colocated
  * \param[in,out] primary         Resource to colocate \p dependent with
  * \param[in]     dependent_role  Current role of \p dependent
  * \param[in]     primary_role    Current role of \p primary
  * \param[in]     flags           Group of enum pcmk__coloc_flags
  */
 void
 pcmk__new_colocation(const char *id, const char *node_attr, int score,
                      pcmk_resource_t *dependent, pcmk_resource_t *primary,
                      const char *dependent_role, const char *primary_role,
                      uint32_t flags)
 {
     pcmk__colocation_t *new_con = NULL;
 
     CRM_CHECK(id != NULL, return);
 
     if ((dependent == NULL) || (primary == NULL)) {
         pcmk__config_err("Ignoring colocation '%s' because resource "
                          "does not exist", id);
         return;
     }
 
     if (score == 0) {
         pcmk__rsc_trace(dependent,
                         "Ignoring colocation '%s' (%s with %s) because score is 0",
                         id, dependent->id, primary->id);
         return;
     }
 
     new_con = calloc(1, sizeof(pcmk__colocation_t));
     CRM_ASSERT(new_con != NULL);
 
     if (pcmk__str_eq(dependent_role, PCMK__ROLE_STARTED,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         dependent_role = PCMK__ROLE_UNKNOWN;
     }
 
     if (pcmk__str_eq(primary_role, PCMK__ROLE_STARTED,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         primary_role = PCMK__ROLE_UNKNOWN;
     }
 
     new_con->id = id;
     new_con->dependent = dependent;
     new_con->primary = primary;
     new_con->score = score;
     new_con->dependent_role = pcmk_parse_role(dependent_role);
     new_con->primary_role = pcmk_parse_role(primary_role);
     new_con->node_attribute = pcmk__s(node_attr, CRM_ATTR_UNAME);
     new_con->flags = flags;
 
     pcmk__add_this_with(&(dependent->rsc_cons), new_con, dependent);
     pcmk__add_with_this(&(primary->rsc_cons_lhs), new_con, primary);
 
     dependent->cluster->colocation_constraints = g_list_prepend(
         dependent->cluster->colocation_constraints, new_con);
 
-    if (score <= -INFINITY) {
+    if (score <= -PCMK_SCORE_INFINITY) {
         anti_colocation_order(dependent, new_con->dependent_role, primary,
                               new_con->primary_role);
         anti_colocation_order(primary, new_con->primary_role, dependent,
                               new_con->dependent_role);
     }
 }
 
 /*!
  * \internal
  * \brief Return the boolean influence corresponding to configuration
  *
  * \param[in] coloc_id     Colocation XML ID (for error logging)
  * \param[in] rsc          Resource involved in constraint (for default)
  * \param[in] influence_s  String value of \c PCMK_XA_INFLUENCE option
  *
  * \return \c pcmk__coloc_influence if string evaluates true, or string is
  *         \c NULL or invalid and resource's \c PCMK_META_CRITICAL option
  *         evaluates true, otherwise \c pcmk__coloc_none
  */
 static uint32_t
 unpack_influence(const char *coloc_id, const pcmk_resource_t *rsc,
                  const char *influence_s)
 {
     if (influence_s != NULL) {
         int influence_i = 0;
 
         if (crm_str_to_boolean(influence_s, &influence_i) < 0) {
             pcmk__config_err("Constraint '%s' has invalid value for "
                              PCMK_XA_INFLUENCE " (using default)",
                              coloc_id);
         } else {
             return (influence_i == 0)? pcmk__coloc_none : pcmk__coloc_influence;
         }
     }
     if (pcmk_is_set(rsc->flags, pcmk_rsc_critical)) {
         return pcmk__coloc_influence;
     }
     return pcmk__coloc_none;
 }
 
 static void
 unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
                       const char *influence_s, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_rsc = NULL;
     pcmk_resource_t *other = NULL;
     pcmk_resource_t *resource = NULL;
     const char *set_id = pcmk__xe_id(set);
     const char *role = crm_element_value(set, PCMK_XA_ROLE);
     bool with_previous = false;
     int local_score = score;
     bool sequential = false;
     uint32_t flags = pcmk__coloc_none;
     const char *xml_rsc_id = NULL;
     const char *score_s = crm_element_value(set, PCMK_XA_SCORE);
 
     if (score_s) {
         local_score = char2score(score_s);
     }
     if (local_score == 0) {
         crm_trace("Ignoring colocation '%s' for set '%s' because score is 0",
                   coloc_id, set_id);
         return;
     }
 
     /* @COMPAT The deprecated PCMK__XA_ORDERING attribute specifies whether
      * resources in a positive-score set are colocated with the previous or next
      * resource.
      */
     if (pcmk__str_eq(crm_element_value(set, PCMK__XA_ORDERING),
                      PCMK__VALUE_GROUP,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         with_previous = true;
     } else {
         pcmk__warn_once(pcmk__wo_set_ordering,
                         "Support for '" PCMK__XA_ORDERING "' other than"
                         " '" PCMK__VALUE_GROUP "' in " PCMK_XE_RESOURCE_SET
                         " (such as %s) is deprecated and will be removed in a"
                         " future release",
                         set_id);
     }
 
     if ((pcmk__xe_get_bool_attr(set, PCMK_XA_SEQUENTIAL,
                                 &sequential) == pcmk_rc_ok)
         && !sequential) {
         return;
     }
 
     if (local_score > 0) {
         for (xml_rsc = first_named_child(set, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             resource = pcmk__find_constraint_resource(scheduler->resources,
                                                       xml_rsc_id);
             if (resource == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring %s and later resources in set %s: "
                                  "No such resource", xml_rsc_id, set_id);
                 return;
             }
             if (other != NULL) {
                 flags = pcmk__coloc_explicit
                         | unpack_influence(coloc_id, resource, influence_s);
                 if (with_previous) {
                     pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
                                     resource->id, other->id, set_id);
                     pcmk__new_colocation(set_id, NULL, local_score, resource,
                                          other, role, role, flags);
                 } else {
                     pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
                                     other->id, resource->id, set_id);
                     pcmk__new_colocation(set_id, NULL, local_score, other,
                                          resource, role, role, flags);
                 }
             }
             other = resource;
         }
 
     } else {
         /* Anti-colocating with every prior resource is
          * the only way to ensure the intuitive result
          * (i.e. that no one in the set can run with anyone else in the set)
          */
 
         for (xml_rsc = first_named_child(set, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xmlNode *xml_rsc_with = NULL;
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             resource = pcmk__find_constraint_resource(scheduler->resources,
                                                       xml_rsc_id);
             if (resource == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring %s and later resources in set %s: "
                                  "No such resource", xml_rsc_id, set_id);
                 return;
             }
             flags = pcmk__coloc_explicit
                     | unpack_influence(coloc_id, resource, influence_s);
             for (xml_rsc_with = first_named_child(set, PCMK_XE_RESOURCE_REF);
                  xml_rsc_with != NULL;
                  xml_rsc_with = crm_next_same_xml(xml_rsc_with)) {
 
                 xml_rsc_id = pcmk__xe_id(xml_rsc_with);
                 if (pcmk__str_eq(resource->id, xml_rsc_id, pcmk__str_none)) {
                     break;
                 }
                 other = pcmk__find_constraint_resource(scheduler->resources,
                                                        xml_rsc_id);
                 CRM_ASSERT(other != NULL); // We already processed it
                 pcmk__new_colocation(set_id, NULL, local_score,
                                      resource, other, role, role, flags);
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Colocate two resource sets relative to each other
  *
  * \param[in]     id           Colocation XML ID
  * \param[in]     set1         Dependent set
  * \param[in]     set2         Primary set
  * \param[in]     score        Colocation score
  * \param[in]     influence_s  Value of colocation's \c PCMK_XA_INFLUENCE
  *                             attribute
  * \param[in,out] scheduler    Scheduler data
  */
 static void
 colocate_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
                   int score, const char *influence_s,
                   pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_rsc = NULL;
     pcmk_resource_t *rsc_1 = NULL;
     pcmk_resource_t *rsc_2 = NULL;
 
     const char *xml_rsc_id = NULL;
     const char *role_1 = crm_element_value(set1, PCMK_XA_ROLE);
     const char *role_2 = crm_element_value(set2, PCMK_XA_ROLE);
 
     int rc = pcmk_rc_ok;
     bool sequential = false;
     uint32_t flags = pcmk__coloc_none;
 
     if (score == 0) {
         crm_trace("Ignoring colocation '%s' between sets %s and %s "
                   "because score is 0",
                   id, pcmk__xe_id(set1), pcmk__xe_id(set2));
         return;
     }
 
     rc = pcmk__xe_get_bool_attr(set1, PCMK_XA_SEQUENTIAL, &sequential);
     if ((rc != pcmk_rc_ok) || sequential) {
         // Get the first one
         xml_rsc = first_named_child(set1, PCMK_XE_RESOURCE_REF);
         if (xml_rsc != NULL) {
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
                                                    xml_rsc_id);
             if (rsc_1 == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring colocation of set %s with set %s "
                                  "because first resource %s not found",
                                  pcmk__xe_id(set1), pcmk__xe_id(set2),
                                  xml_rsc_id);
                 return;
             }
         }
     }
 
     rc = pcmk__xe_get_bool_attr(set2, PCMK_XA_SEQUENTIAL, &sequential);
     if ((rc != pcmk_rc_ok) || sequential) {
         // Get the last one
         for (xml_rsc = first_named_child(set2, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
         }
         rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
                                                xml_rsc_id);
         if (rsc_2 == NULL) {
             // Should be possible only with validation disabled
             pcmk__config_err("Ignoring colocation of set %s with set %s "
                              "because last resource %s not found",
                              pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id);
             return;
         }
     }
 
     if ((rsc_1 != NULL) && (rsc_2 != NULL)) { // Both sets are sequential
         flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
         pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
                              flags);
 
     } else if (rsc_1 != NULL) { // Only set1 is sequential
         flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
         for (xml_rsc = first_named_child(set2, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
                                                    xml_rsc_id);
             if (rsc_2 == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring set %s colocation with resource %s "
                                  "in set %s: No such resource",
                                  pcmk__xe_id(set1), xml_rsc_id,
                                  pcmk__xe_id(set2));
                 continue;
             }
             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
                                  role_2, flags);
         }
 
     } else if (rsc_2 != NULL) { // Only set2 is sequential
         for (xml_rsc = first_named_child(set1, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
                                                    xml_rsc_id);
             if (rsc_1 == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring colocation of set %s resource %s "
                                  "with set %s: No such resource",
                                  pcmk__xe_id(set1), xml_rsc_id,
                                  pcmk__xe_id(set2));
                 continue;
             }
             flags = pcmk__coloc_explicit
                     | unpack_influence(id, rsc_1, influence_s);
             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
                                  role_2, flags);
         }
 
     } else { // Neither set is sequential
         for (xml_rsc = first_named_child(set1, PCMK_XE_RESOURCE_REF);
              xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
             xmlNode *xml_rsc_2 = NULL;
 
             xml_rsc_id = pcmk__xe_id(xml_rsc);
             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
                                                    xml_rsc_id);
             if (rsc_1 == NULL) {
                 // Should be possible only with validation disabled
                 pcmk__config_err("Ignoring colocation of set %s resource %s "
                                  "with set %s: No such resource",
                                  pcmk__xe_id(set1), xml_rsc_id,
                                  pcmk__xe_id(set2));
                 continue;
             }
 
             flags = pcmk__coloc_explicit
                     | unpack_influence(id, rsc_1, influence_s);
             for (xml_rsc_2 = first_named_child(set2, PCMK_XE_RESOURCE_REF);
                  xml_rsc_2 != NULL;
                  xml_rsc_2 = crm_next_same_xml(xml_rsc_2)) {
 
                 xml_rsc_id = pcmk__xe_id(xml_rsc_2);
                 rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
                                                        xml_rsc_id);
                 if (rsc_2 == NULL) {
                     // Should be possible only with validation disabled
                     pcmk__config_err("Ignoring colocation of set %s resource "
                                      "%s with set %s resource %s: No such "
                                      "resource",
                                      pcmk__xe_id(set1), pcmk__xe_id(xml_rsc),
                                      pcmk__xe_id(set2), xml_rsc_id);
                     continue;
                 }
                 pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
                                      role_1, role_2, flags);
             }
         }
     }
 }
 
 static void
 unpack_simple_colocation(xmlNode *xml_obj, const char *id,
                          const char *influence_s, pcmk_scheduler_t *scheduler)
 {
     int score_i = 0;
     uint32_t flags = pcmk__coloc_none;
 
     const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
     const char *dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     const char *primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
     const char *dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
     const char *primary_role = crm_element_value(xml_obj,
                                                  PCMK_XA_WITH_RSC_ROLE);
     const char *attr = crm_element_value(xml_obj, PCMK_XA_NODE_ATTRIBUTE);
 
     const char *primary_instance = NULL;
     const char *dependent_instance = NULL;
     pcmk_resource_t *primary = NULL;
     pcmk_resource_t *dependent = NULL;
 
     primary = pcmk__find_constraint_resource(scheduler->resources, primary_id);
     dependent = pcmk__find_constraint_resource(scheduler->resources,
                                                dependent_id);
 
     // @COMPAT: Deprecated since 2.1.5
     primary_instance = crm_element_value(xml_obj, PCMK__XA_WITH_RSC_INSTANCE);
     dependent_instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
     if (dependent_instance != NULL) {
         pcmk__warn_once(pcmk__wo_coloc_inst,
                         "Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
                         "and will be removed in a future release");
     }
     if (primary_instance != NULL) {
         pcmk__warn_once(pcmk__wo_coloc_inst,
                         "Support for " PCMK__XA_WITH_RSC_INSTANCE " is "
                         "deprecated and will be removed in a future release");
     }
 
     if (dependent == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, dependent_id);
         return;
 
     } else if (primary == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, primary_id);
         return;
 
     } else if ((dependent_instance != NULL) && !pcmk__is_clone(dependent)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, dependent_id, dependent_instance);
         return;
 
     } else if ((primary_instance != NULL) && !pcmk__is_clone(primary)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, primary_id, primary_instance);
         return;
     }
 
     if (dependent_instance != NULL) {
         dependent = find_clone_instance(dependent, dependent_instance);
         if (dependent == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               id, dependent_id, dependent_instance);
             return;
         }
     }
 
     if (primary_instance != NULL) {
         primary = find_clone_instance(primary, primary_instance);
         if (primary == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               "'%s'", id, primary_id, primary_instance);
             return;
         }
     }
 
     if (pcmk__xe_attr_is_true(xml_obj, PCMK_XA_SYMMETRICAL)) {
         pcmk__config_warn("The colocation constraint "
                           "'" PCMK_XA_SYMMETRICAL "' attribute has been "
                           "removed");
     }
 
     if (score) {
         score_i = char2score(score);
     }
 
     flags = pcmk__coloc_explicit | unpack_influence(id, dependent, influence_s);
     pcmk__new_colocation(id, attr, score_i, dependent, primary,
                          dependent_role, primary_role, flags);
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                        pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *dependent_id = NULL;
     const char *primary_id = NULL;
     const char *dependent_role = NULL;
     const char *primary_role = NULL;
 
     pcmk_resource_t *dependent = NULL;
     pcmk_resource_t *primary = NULL;
 
     pcmk_tag_t *dependent_tag = NULL;
     pcmk_tag_t *primary_tag = NULL;
 
     xmlNode *dependent_set = NULL;
     xmlNode *primary_set = NULL;
     bool any_sets = false;
 
     *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = pcmk__xe_id(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
                          xml_obj->name);
         return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
         return pcmk_rc_ok;
     }
 
     dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
     if ((dependent_id == NULL) || (primary_id == NULL)) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(scheduler, dependent_id, &dependent,
                                      &dependent_tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, dependent_id);
         return pcmk_rc_unpack_error;
     }
 
     if (!pcmk__valid_resource_or_tag(scheduler, primary_id, &primary,
                                      &primary_tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, primary_id);
         return pcmk_rc_unpack_error;
     }
 
     if ((dependent != NULL) && (primary != NULL)) {
         /* Neither side references any template/tag. */
         return pcmk_rc_ok;
     }
 
     if ((dependent_tag != NULL) && (primary_tag != NULL)) {
         // A colocation constraint between two templates/tags makes no sense
         pcmk__config_err("Ignoring constraint '%s' because two templates or "
                          "tags cannot be colocated", id);
         return pcmk_rc_unpack_error;
     }
 
     dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
     primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     /* Convert dependent's template/tag reference into constraint
      * PCMK_XE_RESOURCE_SET
      */
     if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, PCMK_XA_RSC, true,
                           scheduler)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
         return pcmk_rc_unpack_error;
     }
 
     if (dependent_set != NULL) {
         if (dependent_role != NULL) {
             /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
              * PCMK_XA_ROLE
              */
             crm_xml_add(dependent_set, PCMK_XA_ROLE, dependent_role);
             xml_remove_prop(*expanded_xml, PCMK_XA_RSC_ROLE);
         }
         any_sets = true;
     }
 
     /* Convert primary's template/tag reference into constraint
      * PCMK_XE_RESOURCE_SET
      */
     if (!pcmk__tag_to_set(*expanded_xml, &primary_set, PCMK_XA_WITH_RSC, true,
                           scheduler)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
         return pcmk_rc_unpack_error;
     }
 
     if (primary_set != NULL) {
         if (primary_role != NULL) {
             /* Move PCMK_XA_WITH_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
              * PCMK_XA_ROLE
              */
             crm_xml_add(primary_set, PCMK_XA_ROLE, primary_role);
             xml_remove_prop(*expanded_xml, PCMK_XA_WITH_RSC_ROLE);
         }
         any_sets = true;
     }
 
     if (any_sets) {
         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
     } else {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Parse a colocation constraint from XML into scheduler data
  *
  * \param[in,out] xml_obj    Colocation constraint XML to unpack
  * \param[in,out] scheduler  Scheduler data to add constraint to
  */
 void
 pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     int score_i = 0;
     xmlNode *set = NULL;
     xmlNode *last = NULL;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
     const char *score = NULL;
     const char *influence_s = NULL;
 
     if (pcmk__str_empty(id)) {
         pcmk__config_err("Ignoring " PCMK_XE_RSC_COLOCATION
                          " without " CRM_ATTR_ID);
         return;
     }
 
     if (unpack_colocation_tags(xml_obj, &expanded_xml,
                                scheduler) != pcmk_rc_ok) {
         return;
     }
     if (expanded_xml != NULL) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     score = crm_element_value(xml_obj, PCMK_XA_SCORE);
     if (score != NULL) {
         score_i = char2score(score);
     }
     influence_s = crm_element_value(xml_obj, PCMK_XA_INFLUENCE);
 
     for (set = first_named_child(xml_obj, PCMK_XE_RESOURCE_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         set = expand_idref(set, scheduler->input);
         if (set == NULL) { // Configuration error, message already logged
             if (expanded_xml != NULL) {
                 free_xml(expanded_xml);
             }
             return;
         }
 
         if (pcmk__str_empty(pcmk__xe_id(set))) {
             pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET
                              " without " CRM_ATTR_ID);
             continue;
         }
         unpack_colocation_set(set, score_i, id, influence_s, scheduler);
 
         if (last != NULL) {
             colocate_rsc_sets(id, last, set, score_i, influence_s, scheduler);
         }
         last = set;
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (last == NULL) {
         unpack_simple_colocation(xml_obj, id, influence_s, scheduler);
     }
 }
 
 /*!
  * \internal
  * \brief Make actions of a given type unrunnable for a given resource
  *
  * \param[in,out] rsc     Resource whose actions should be blocked
  * \param[in]     task    Name of action to block
  * \param[in]     reason  Unrunnable start action causing the block
  */
 static void
 mark_action_blocked(pcmk_resource_t *rsc, const char *task,
                     const pcmk_resource_t *reason)
 {
     GList *iter = NULL;
     char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
 
     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = iter->data;
 
         if (pcmk_is_set(action->flags, pcmk_action_runnable)
             && pcmk__str_eq(action->task, task, pcmk__str_none)) {
 
             pcmk__clear_action_flags(action, pcmk_action_runnable);
             pe_action_set_reason(action, reason_text, false);
             pcmk__block_colocation_dependents(action);
             pcmk__update_action_for_orderings(action, rsc->cluster);
         }
     }
 
     // If parent resource can't perform an action, neither can any children
     for (iter = rsc->children; iter != NULL; iter = iter->next) {
         mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason);
     }
     free(reason_text);
 }
 
 /*!
  * \internal
  * \brief If an action is unrunnable, block any relevant dependent actions
  *
  * If a given action is an unrunnable start or promote, block the start or
  * promote actions of resources colocated with it, as appropriate to the
  * colocations' configured roles.
  *
  * \param[in,out] action  Action to check
  */
 void
 pcmk__block_colocation_dependents(pcmk_action_t *action)
 {
     GList *iter = NULL;
     GList *colocations = NULL;
     pcmk_resource_t *rsc = NULL;
     bool is_start = false;
 
     if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
         return; // Only unrunnable actions block dependents
     }
 
     is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none);
     if (!is_start
         && !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
         return; // Only unrunnable starts and promotes block dependents
     }
 
     CRM_ASSERT(action->rsc != NULL); // Start and promote are resource actions
 
     /* If this resource is part of a collective resource, dependents are blocked
      * only if all instances of the collective are unrunnable, so check the
      * collective resource.
      */
     rsc = uber_parent(action->rsc);
     if (rsc->parent != NULL) {
         rsc = rsc->parent; // Bundle
     }
 
     // Colocation fails only if entire primary can't reach desired role
     for (iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child = iter->data;
         pcmk_action_t *child_action = find_first_action(child->actions, NULL,
                                                         action->task, NULL);
 
         if ((child_action == NULL)
             || pcmk_is_set(child_action->flags, pcmk_action_runnable)) {
             crm_trace("Not blocking %s colocation dependents because "
                       "at least %s has runnable %s",
                       rsc->id, child->id, action->task);
             return; // At least one child can reach desired role
         }
     }
 
     crm_trace("Blocking %s colocation dependents due to unrunnable %s %s",
               rsc->id, action->rsc->id, action->task);
 
     // Check each colocation where this resource is primary
     colocations = pcmk__with_this_colocations(rsc);
     for (iter = colocations; iter != NULL; iter = iter->next) {
         pcmk__colocation_t *colocation = iter->data;
 
-        if (colocation->score < INFINITY) {
+        if (colocation->score < PCMK_SCORE_INFINITY) {
             continue; // Only mandatory colocations block dependent
         }
 
         /* If the primary can't start, the dependent can't reach its colocated
          * role, regardless of what the primary or dependent colocation role is.
          *
          * If the primary can't be promoted, the dependent can't reach its
          * colocated role if the primary's colocation role is promoted.
          */
         if (!is_start && (colocation->primary_role != pcmk_role_promoted)) {
             continue;
         }
 
         // Block the dependent from reaching its colocated role
         if (colocation->dependent_role == pcmk_role_promoted) {
             mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE,
                                 action->rsc);
         } else {
             mark_action_blocked(colocation->dependent, PCMK_ACTION_START,
                                 action->rsc);
         }
     }
     g_list_free(colocations);
 }
 
 /*!
  * \internal
  * \brief Get the resource to use for role comparisons
  *
  * A bundle replica includes a container and possibly an instance of the bundled
  * resource. The dependent in a "with bundle" colocation is colocated with a
  * particular bundle container. However, if the colocation includes a role, then
  * the role must be checked on the bundled resource instance inside the
  * container. The container itself will never be promoted; the bundled resource
  * may be.
  *
  * If the given resource is a bundle replica container, return the resource
  * inside it, if any. Otherwise, return the resource itself.
  *
  * \param[in] rsc  Resource to check
  *
  * \return Resource to use for role comparisons
  */
 static const pcmk_resource_t *
 get_resource_for_role(const pcmk_resource_t *rsc)
 {
     if (pcmk_is_set(rsc->flags, pcmk_rsc_replica_container)) {
         const pcmk_resource_t *child = pe__get_rsc_in_container(rsc);
 
         if (child != NULL) {
             return child;
         }
     }
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Determine how a colocation constraint should affect a resource
  *
  * Colocation constraints have different effects at different points in the
  * scheduler sequence. Initially, they affect a resource's location; once that
  * is determined, then for promotable clones they can affect a resource
  * instance's role; after both are determined, the constraints no longer matter.
  * Given a specific colocation constraint, check what has been done so far to
  * determine what should be affected at the current point in the scheduler.
  *
  * \param[in] dependent   Dependent resource in colocation
  * \param[in] primary     Primary resource in colocation
  * \param[in] colocation  Colocation constraint
  * \param[in] preview     If true, pretend resources have already been assigned
  *
  * \return How colocation constraint should be applied at this point
  */
 enum pcmk__coloc_affects
 pcmk__colocation_affects(const pcmk_resource_t *dependent,
                          const pcmk_resource_t *primary,
                          const pcmk__colocation_t *colocation, bool preview)
 {
     const pcmk_resource_t *dependent_role_rsc = NULL;
     const pcmk_resource_t *primary_role_rsc = NULL;
 
     CRM_ASSERT((dependent != NULL) && (primary != NULL)
                && (colocation != NULL));
 
     if (!preview && pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
         // Primary resource has not been assigned yet, so we can't do anything
         return pcmk__coloc_affects_nothing;
     }
 
     dependent_role_rsc = get_resource_for_role(dependent);
     primary_role_rsc = get_resource_for_role(primary);
 
     if ((colocation->dependent_role >= pcmk_role_unpromoted)
         && (dependent_role_rsc->parent != NULL)
         && pcmk_is_set(dependent_role_rsc->parent->flags, pcmk_rsc_promotable)
         && !pcmk_is_set(dependent_role_rsc->flags, pcmk_rsc_unassigned)) {
 
         /* This is a colocation by role, and the dependent is a promotable clone
          * that has already been assigned, so the colocation should now affect
          * the role.
          */
         return pcmk__coloc_affects_role;
     }
 
     if (!preview && !pcmk_is_set(dependent->flags, pcmk_rsc_unassigned)) {
         /* The dependent resource has already been through assignment, so the
          * constraint no longer has any effect. Log an error if a mandatory
          * colocation constraint has been violated.
          */
 
         const pcmk_node_t *primary_node = primary->allocated_to;
 
         if (dependent->allocated_to == NULL) {
             crm_trace("Skipping colocation '%s': %s will not run anywhere",
                       colocation->id, dependent->id);
 
-        } else if (colocation->score >= INFINITY) {
+        } else if (colocation->score >= PCMK_SCORE_INFINITY) {
             // Dependent resource must colocate with primary resource
 
             if (!pcmk__same_node(primary_node, dependent->allocated_to)) {
                 pcmk__sched_err("%s must be colocated with %s but is not "
                                 "(%s vs. %s)",
                                 dependent->id, primary->id,
                                 pcmk__node_name(dependent->allocated_to),
                                 pcmk__node_name(primary_node));
             }
 
-        } else if (colocation->score <= -CRM_SCORE_INFINITY) {
+        } else if (colocation->score <= -PCMK_SCORE_INFINITY) {
             // Dependent resource must anti-colocate with primary resource
 
             if (pcmk__same_node(dependent->allocated_to, primary_node)) {
                 pcmk__sched_err("%s and %s must be anti-colocated but are "
                                 "assigned to the same node (%s)",
                                 dependent->id, primary->id,
                                 pcmk__node_name(primary_node));
             }
         }
         return pcmk__coloc_affects_nothing;
     }
 
     if ((colocation->dependent_role != pcmk_role_unknown)
         && (colocation->dependent_role != dependent_role_rsc->next_role)) {
         crm_trace("Skipping %scolocation '%s': dependent limited to %s role "
 
                   "but %s next role is %s",
                   ((colocation->score < 0)? "anti-" : ""),
                   colocation->id, pcmk_role_text(colocation->dependent_role),
                   dependent_role_rsc->id,
                   pcmk_role_text(dependent_role_rsc->next_role));
         return pcmk__coloc_affects_nothing;
     }
 
     if ((colocation->primary_role != pcmk_role_unknown)
         && (colocation->primary_role != primary_role_rsc->next_role)) {
         crm_trace("Skipping %scolocation '%s': primary limited to %s role "
                   "but %s next role is %s",
                   ((colocation->score < 0)? "anti-" : ""),
                   colocation->id, pcmk_role_text(colocation->primary_role),
                   primary_role_rsc->id,
                   pcmk_role_text(primary_role_rsc->next_role));
         return pcmk__coloc_affects_nothing;
     }
 
     return pcmk__coloc_affects_location;
 }
 
 /*!
  * \internal
  * \brief Apply colocation to dependent for assignment purposes
  *
  * Update the allowed node scores of the dependent resource in a colocation,
  * for the purposes of assigning it to a node.
  *
  * \param[in,out] dependent   Dependent resource in colocation
  * \param[in]     primary     Primary resource in colocation
  * \param[in]     colocation  Colocation constraint
  */
 void
 pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
                             const pcmk_resource_t *primary,
                             const pcmk__colocation_t *colocation)
 {
     const char *attr = colocation->node_attribute;
     const char *value = NULL;
     GHashTable *work = NULL;
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
 
     if (primary->allocated_to != NULL) {
         value = pcmk__colocation_node_attr(primary->allocated_to, attr,
                                            primary);
 
     } else if (colocation->score < 0) {
         // Nothing to do (anti-colocation with something that is not running)
         return;
     }
 
     work = pcmk__copy_node_table(dependent->allowed_nodes);
 
     g_hash_table_iter_init(&iter, work);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
         if (primary->allocated_to == NULL) {
             node->weight = pcmk__add_scores(-colocation->score, node->weight);
             pcmk__rsc_trace(dependent,
                             "Applied %s to %s score on %s (now %s after "
                             "subtracting %s because primary %s inactive)",
                             colocation->id, dependent->id,
                             pcmk__node_name(node),
                             pcmk_readable_score(node->weight),
                             pcmk_readable_score(colocation->score), primary->id);
             continue;
         }
 
         if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent),
                          value, pcmk__str_casei)) {
 
             /* Add colocation score only if optional (or minus infinity). A
              * mandatory colocation is a requirement rather than a preference,
              * so we don't need to consider it for relative assignment purposes.
              * The resource will simply be forbidden from running on the node if
              * the primary isn't active there (via the condition above).
              */
-            if (colocation->score < CRM_SCORE_INFINITY) {
+            if (colocation->score < PCMK_SCORE_INFINITY) {
                 node->weight = pcmk__add_scores(colocation->score,
                                                 node->weight);
                 pcmk__rsc_trace(dependent,
                                 "Applied %s to %s score on %s (now %s after "
                                 "adding %s)",
                                 colocation->id, dependent->id,
                                 pcmk__node_name(node),
                                 pcmk_readable_score(node->weight),
                                 pcmk_readable_score(colocation->score));
             }
             continue;
         }
 
-        if (colocation->score >= CRM_SCORE_INFINITY) {
+        if (colocation->score >= PCMK_SCORE_INFINITY) {
             /* Only mandatory colocations are relevant when the colocation
              * attribute doesn't match, because an attribute not matching is not
              * a negative preference -- the colocation is simply relevant only
              * where it matches.
              */
-            node->weight = -CRM_SCORE_INFINITY;
+            node->weight = -PCMK_SCORE_INFINITY;
             pcmk__rsc_trace(dependent,
                             "Banned %s from %s because colocation %s attribute %s "
                             "does not match",
                             dependent->id, pcmk__node_name(node),
                             colocation->id, attr);
         }
     }
 
-    if ((colocation->score <= -INFINITY) || (colocation->score >= INFINITY)
+    if ((colocation->score <= -PCMK_SCORE_INFINITY)
+        || (colocation->score >= PCMK_SCORE_INFINITY)
         || pcmk__any_node_available(work)) {
 
         g_hash_table_destroy(dependent->allowed_nodes);
         dependent->allowed_nodes = work;
         work = NULL;
 
     } else {
         pcmk__rsc_info(dependent,
                        "%s: Rolling back scores from %s (no available nodes)",
                        dependent->id, primary->id);
     }
 
     if (work != NULL) {
         g_hash_table_destroy(work);
     }
 }
 
 /*!
  * \internal
  * \brief Apply colocation to dependent for role purposes
  *
  * Update the priority of the dependent resource in a colocation, for the
  * purposes of selecting its role
  *
  * \param[in,out] dependent   Dependent resource in colocation
  * \param[in]     primary     Primary resource in colocation
  * \param[in]     colocation  Colocation constraint
  */
 void
 pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
                               const pcmk_resource_t *primary,
                               const pcmk__colocation_t *colocation)
 {
     const char *dependent_value = NULL;
     const char *primary_value = NULL;
     const char *attr = colocation->node_attribute;
     int score_multiplier = 1;
 
     const pcmk_resource_t *primary_role_rsc = NULL;
 
     CRM_ASSERT((dependent != NULL) && (primary != NULL) &&
                (colocation != NULL));
 
     if ((primary->allocated_to == NULL) || (dependent->allocated_to == NULL)) {
         return;
     }
 
     dependent_value = pcmk__colocation_node_attr(dependent->allocated_to, attr,
                                                  dependent);
     primary_value = pcmk__colocation_node_attr(primary->allocated_to, attr,
                                                primary);
 
     primary_role_rsc = get_resource_for_role(primary);
 
     if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
-        if ((colocation->score == INFINITY)
+        if ((colocation->score == PCMK_SCORE_INFINITY)
             && (colocation->dependent_role == pcmk_role_promoted)) {
-            dependent->priority = -INFINITY;
+            dependent->priority = -PCMK_SCORE_INFINITY;
         }
         return;
     }
 
     if ((colocation->primary_role != pcmk_role_unknown)
         && (colocation->primary_role != primary_role_rsc->next_role)) {
         return;
     }
 
     if (colocation->dependent_role == pcmk_role_unpromoted) {
         score_multiplier = -1;
     }
 
     dependent->priority = pcmk__add_scores(score_multiplier * colocation->score,
                                            dependent->priority);
     pcmk__rsc_trace(dependent,
                     "Applied %s to %s promotion priority (now %s after %s %s)",
                     colocation->id, dependent->id,
                     pcmk_readable_score(dependent->priority),
                     ((score_multiplier == 1)? "adding" : "subtracting"),
                     pcmk_readable_score(colocation->score));
 }
 
 /*!
  * \internal
  * \brief Find score of highest-scored node that matches colocation attribute
  *
  * \param[in] rsc    Resource whose allowed nodes should be searched
  * \param[in] attr   Colocation attribute name (must not be NULL)
  * \param[in] value  Colocation attribute value to require
  */
 static int
 best_node_score_matching_attr(const pcmk_resource_t *rsc, const char *attr,
                               const char *value)
 {
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
-    int best_score = -INFINITY;
+    int best_score = -PCMK_SCORE_INFINITY;
     const char *best_node = NULL;
 
     // Find best allowed node with matching attribute
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
 
         if ((node->weight > best_score)
             && pcmk__node_available(node, false, false)
             && pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc),
                             pcmk__str_casei)) {
 
             best_score = node->weight;
             best_node = node->details->uname;
         }
     }
 
     if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) {
         if (best_node == NULL) {
             crm_info("No allowed node for %s matches node attribute %s=%s",
                      rsc->id, attr, value);
         } else {
             crm_info("Allowed node %s for %s had best score (%d) "
                      "of those matching node attribute %s=%s",
                      best_node, rsc->id, best_score, attr, value);
         }
     }
     return best_score;
 }
 
 /*!
  * \internal
  * \brief Check whether a resource is allowed only on a single node
  *
  * \param[in] rsc   Resource to check
  *
  * \return \c true if \p rsc is allowed only on one node, otherwise \c false
  */
 static bool
 allowed_on_one(const pcmk_resource_t *rsc)
 {
     GHashTableIter iter;
     pcmk_node_t *allowed_node = NULL;
     int allowed_nodes = 0;
 
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) {
         if ((allowed_node->weight >= 0) && (++allowed_nodes > 1)) {
             pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id);
             return false;
         }
     }
     pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id,
                     ((allowed_nodes == 1)? "on a single node" : "nowhere"));
     return (allowed_nodes == 1);
 }
 
 /*!
  * \internal
  * \brief Add resource's colocation matches to current node assignment scores
  *
  * For each node in a given table, if any of a given resource's allowed nodes
  * have a matching value for the colocation attribute, add the highest of those
  * nodes' scores to the node's score.
  *
  * \param[in,out] nodes          Table of nodes with assignment scores so far
  * \param[in]     source_rsc     Resource whose node scores to add
  * \param[in]     target_rsc     Resource on whose behalf to update \p nodes
  * \param[in]     colocation     Original colocation constraint (used to get
  *                               configured primary resource's stickiness, and
  *                               to get colocation node attribute; pass NULL to
  *                               ignore stickiness and use default attribute)
  * \param[in]     factor         Factor by which to multiply scores being added
  * \param[in]     only_positive  Whether to add only positive scores
  */
 static void
 add_node_scores_matching_attr(GHashTable *nodes,
                               const pcmk_resource_t *source_rsc,
                               const pcmk_resource_t *target_rsc,
                               const pcmk__colocation_t *colocation,
                               float factor, bool only_positive)
 {
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
     const char *attr = colocation->node_attribute;
 
     // Iterate through each node
     g_hash_table_iter_init(&iter, nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
         float delta_f = 0;
         int delta = 0;
         int score = 0;
         int new_score = 0;
         const char *value = pcmk__colocation_node_attr(node, attr, target_rsc);
 
         score = best_node_score_matching_attr(source_rsc, attr, value);
 
         if ((factor < 0) && (score < 0)) {
             /* If the dependent is anti-colocated, we generally don't want the
              * primary to prefer nodes that the dependent avoids. That could
              * lead to unnecessary shuffling of the primary when the dependent
              * hits its migration threshold somewhere, for example.
              *
              * However, there are cases when it is desirable. If the dependent
              * can't run anywhere but where the primary is, it would be
              * worthwhile to move the primary for the sake of keeping the
              * dependent active.
              *
              * We can't know that exactly at this point since we don't know
              * where the primary will be assigned, but we can limit considering
              * the preference to when the dependent is allowed only on one node.
              * This is less than ideal for multiple reasons:
              *
              * - the dependent could be allowed on more than one node but have
              *   anti-colocation primaries on each;
              * - the dependent could be a clone or bundle with multiple
              *   instances, and the dependent as a whole is allowed on multiple
              *   nodes but some instance still can't run
              * - the dependent has considered node-specific criteria such as
              *   location constraints and stickiness by this point, but might
              *   have other factors that end up disallowing a node
              *
              * but the alternative is making the primary move when it doesn't
              * need to.
              *
              * We also consider the primary's stickiness and influence, so the
              * user has some say in the matter. (This is the configured primary,
              * not a particular instance of the primary, but that doesn't matter
              * unless stickiness uses a rule to vary by node, and that seems
              * acceptable to ignore.)
              */
             if ((colocation->primary->stickiness >= -score)
                 || !pcmk__colocation_has_influence(colocation, NULL)
                 || !allowed_on_one(colocation->dependent)) {
                 crm_trace("%s: Filtering %d + %f * %d "
                           "(double negative disallowed)",
                           pcmk__node_name(node), node->weight, factor, score);
                 continue;
             }
         }
 
         if (node->weight == INFINITY_HACK) {
             crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
                       pcmk__node_name(node), node->weight, factor, score);
             continue;
         }
 
         delta_f = factor * score;
 
         // Round the number; see http://c-faq.com/fp/round.html
         delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5));
 
         /* Small factors can obliterate the small scores that are often actually
          * used in configurations. If the score and factor are nonzero, ensure
          * that the result is nonzero as well.
          */
         if ((delta == 0) && (score != 0)) {
             if (factor > 0.0) {
                 delta = 1;
             } else if (factor < 0.0) {
                 delta = -1;
             }
         }
 
         new_score = pcmk__add_scores(delta, node->weight);
 
         if (only_positive && (new_score < 0) && (node->weight > 0)) {
             crm_trace("%s: Filtering %d + %f * %d = %d "
                       "(negative disallowed, marking node unusable)",
                       pcmk__node_name(node), node->weight, factor, score,
                       new_score);
             node->weight = INFINITY_HACK;
             continue;
         }
 
         if (only_positive && (new_score < 0) && (node->weight == 0)) {
             crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
                       pcmk__node_name(node), node->weight, factor, score,
                       new_score);
             continue;
         }
 
         crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node),
                   node->weight, factor, score, new_score);
         node->weight = new_score;
     }
 }
 
 /*!
  * \internal
  * \brief Update nodes with scores of colocated resources' nodes
  *
  * Given a table of nodes and a resource, update the nodes' scores with the
  * scores of the best nodes matching the attribute used for each of the
  * resource's relevant colocations.
  *
  * \param[in,out] source_rsc  Resource whose node scores to add
  * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
  * \param[in]     log_id      Resource ID for logs (if \c NULL, use
  *                            \p source_rsc ID)
  * \param[in,out] nodes       Nodes to update (set initial contents to \c NULL
  *                            to copy allowed nodes from \p source_rsc)
  * \param[in]     colocation  Original colocation constraint (used to get
  *                            configured primary resource's stickiness, and
  *                            to get colocation node attribute; if \c NULL,
  *                            <tt>source_rsc</tt>'s own matching node scores
  *                            will not be added, and \p *nodes must be \c NULL
  *                            as well)
  * \param[in]     factor      Incorporate scores multiplied by this factor
  * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
  *
  * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
  *       the \c pcmk__coloc_select_this_with flag are used together (and only by
  *       \c cmp_resources()).
  * \note The caller remains responsible for freeing \p *nodes.
  * \note This is the shared implementation of
  *       \c pcmk_assignment_methods_t:add_colocated_node_scores().
  */
 void
 pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
                                 const pcmk_resource_t *target_rsc,
                                 const char *log_id,
                                 GHashTable **nodes,
                                 const pcmk__colocation_t *colocation,
                                 float factor, uint32_t flags)
 {
     GHashTable *work = NULL;
 
     CRM_ASSERT((source_rsc != NULL) && (nodes != NULL)
                && ((colocation != NULL)
                    || ((target_rsc == NULL) && (*nodes == NULL))));
 
     if (log_id == NULL) {
         log_id = source_rsc->id;
     }
 
     // Avoid infinite recursion
     if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
         pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
                        log_id, source_rsc->id);
         return;
     }
     pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
 
     if (*nodes == NULL) {
         work = pcmk__copy_node_table(source_rsc->allowed_nodes);
         target_rsc = source_rsc;
     } else {
         const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative);
 
         pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)",
                         log_id, (pos? "positive" : "all"), source_rsc->id, factor);
         work = pcmk__copy_node_table(*nodes);
         add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation,
                                       factor, pos);
     }
 
     if (work == NULL) {
         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
         return;
     }
 
     if (pcmk__any_node_available(work)) {
         GList *colocations = NULL;
 
         if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
             colocations = pcmk__this_with_colocations(source_rsc);
             pcmk__rsc_trace(source_rsc,
                             "Checking additional %d optional '%s with' "
                             "constraints",
                             g_list_length(colocations), source_rsc->id);
         } else {
             colocations = pcmk__with_this_colocations(source_rsc);
             pcmk__rsc_trace(source_rsc,
                             "Checking additional %d optional 'with %s' "
                             "constraints",
                             g_list_length(colocations), source_rsc->id);
         }
         flags |= pcmk__coloc_select_active;
 
         for (GList *iter = colocations; iter != NULL; iter = iter->next) {
             pcmk__colocation_t *constraint = iter->data;
 
             pcmk_resource_t *other = NULL;
-            float other_factor = factor * constraint->score / (float) INFINITY;
+            float other_factor = factor * constraint->score
+                                 / (float) PCMK_SCORE_INFINITY;
 
             if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
                 other = constraint->primary;
             } else if (!pcmk__colocation_has_influence(constraint, NULL)) {
                 continue;
             } else {
                 other = constraint->dependent;
             }
 
             pcmk__rsc_trace(source_rsc,
                             "Optionally merging score of '%s' constraint "
                             "(%s with %s)",
                             constraint->id, constraint->dependent->id,
                             constraint->primary->id);
             other->cmds->add_colocated_node_scores(other, target_rsc, log_id,
                                                    &work, constraint,
                                                    other_factor, flags);
             pe__show_node_scores(true, NULL, log_id, work, source_rsc->cluster);
         }
         g_list_free(colocations);
 
     } else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
         pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s",
                        log_id, source_rsc->id);
         g_hash_table_destroy(work);
         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
         return;
     }
 
 
     if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
         pcmk_node_t *node = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, work);
         while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
             if (node->weight == INFINITY_HACK) {
                 node->weight = 1;
             }
         }
     }
 
     if (*nodes != NULL) {
        g_hash_table_destroy(*nodes);
     }
     *nodes = work;
 
     pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
 }
 
 /*!
  * \internal
  * \brief Apply a "with this" colocation to a resource's allowed node scores
  *
  * \param[in,out] data       Colocation to apply
  * \param[in,out] user_data  Resource being assigned
  */
 void
 pcmk__add_dependent_scores(gpointer data, gpointer user_data)
 {
     pcmk__colocation_t *colocation = data;
     pcmk_resource_t *target_rsc = user_data;
 
     pcmk_resource_t *source_rsc = colocation->dependent;
-    const float factor = colocation->score / (float) INFINITY;
+    const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
     uint32_t flags = pcmk__coloc_select_active;
 
     if (!pcmk__colocation_has_influence(colocation, NULL)) {
         return;
     }
     if (target_rsc->variant == pcmk_rsc_variant_clone) {
         flags |= pcmk__coloc_select_nonnegative;
     }
     pcmk__rsc_trace(target_rsc,
                     "%s: Incorporating attenuated %s assignment scores due "
                     "to colocation %s",
                     target_rsc->id, source_rsc->id, colocation->id);
     source_rsc->cmds->add_colocated_node_scores(source_rsc, target_rsc,
                                                 source_rsc->id,
                                                 &target_rsc->allowed_nodes,
                                                 colocation, factor, flags);
 }
 
 /*!
  * \internal
  * \brief Exclude nodes from a dependent's node table if not in a given list
  *
  * Given a dependent resource in a colocation and a list of nodes where the
  * primary resource will run, set a node's score to \c -INFINITY in the
  * dependent's node table if not found in the primary nodes list.
  *
  * \param[in,out] dependent      Dependent resource
  * \param[in]     primary        Primary resource (for logging only)
  * \param[in]     colocation     Colocation constraint (for logging only)
  * \param[in]     primary_nodes  List of nodes where the primary will have
  *                               unblocked instances in a suitable role
  * \param[in]     merge_scores   If \c true and a node is found in both \p table
  *                               and \p list, add the node's score in \p list to
  *                               the node's score in \p table
  */
 void
 pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
                                  const pcmk_resource_t *primary,
                                  const pcmk__colocation_t *colocation,
                                  const GList *primary_nodes, bool merge_scores)
 {
     GHashTableIter iter;
     pcmk_node_t *dependent_node = NULL;
 
     CRM_ASSERT((dependent != NULL) && (primary != NULL)
                && (colocation != NULL));
 
     g_hash_table_iter_init(&iter, dependent->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) {
         const pcmk_node_t *primary_node = NULL;
 
         primary_node = pe_find_node_id(primary_nodes,
                                        dependent_node->details->id);
         if (primary_node == NULL) {
-            dependent_node->weight = -INFINITY;
+            dependent_node->weight = -PCMK_SCORE_INFINITY;
             pcmk__rsc_trace(dependent,
                             "Banning %s from %s (no primary instance) for %s",
                             dependent->id, pcmk__node_name(dependent_node),
                             colocation->id);
 
         } else if (merge_scores) {
             dependent_node->weight = pcmk__add_scores(dependent_node->weight,
                                                       primary_node->weight);
             pcmk__rsc_trace(dependent,
                             "Added %s's score %s to %s's score for %s (now %s) "
                             "for colocation %s",
                             primary->id, pcmk_readable_score(primary_node->weight),
                             dependent->id, pcmk__node_name(dependent_node),
                             pcmk_readable_score(dependent_node->weight),
                             colocation->id);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Get all colocations affecting a resource as the primary
  *
  * \param[in] rsc  Resource to get colocations for
  *
  * \return Newly allocated list of colocations affecting \p rsc as primary
  *
  * \note This is a convenience wrapper for the with_this_colocations() method.
  */
 GList *
 pcmk__with_this_colocations(const pcmk_resource_t *rsc)
 {
     GList *list = NULL;
 
     rsc->cmds->with_this_colocations(rsc, rsc, &list);
     return list;
 }
 
 /*!
  * \internal
  * \brief Get all colocations affecting a resource as the dependent
  *
  * \param[in] rsc  Resource to get colocations for
  *
  * \return Newly allocated list of colocations affecting \p rsc as dependent
  *
  * \note This is a convenience wrapper for the this_with_colocations() method.
  */
 GList *
 pcmk__this_with_colocations(const pcmk_resource_t *rsc)
 {
     GList *list = NULL;
 
     rsc->cmds->this_with_colocations(rsc, rsc, &list);
     return list;
 }
diff --git a/lib/pacemaker/pcmk_sched_group.c b/lib/pacemaker/pcmk_sched_group.c
index 64fa361060..e2aede26e6 100644
--- a/lib/pacemaker/pcmk_sched_group.c
+++ b/lib/pacemaker/pcmk_sched_group.c
@@ -1,951 +1,952 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 
 #include <crm/common/xml.h>
 
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Assign a group resource to a node
  *
  * \param[in,out] rsc           Group resource to assign to a node
  * \param[in]     prefer        Node to prefer, if all else is equal
  * \param[in]     stop_if_fail  If \c true and a child of \p rsc can't be
  *                              assigned to a node, set the child's next role to
  *                              stopped and update existing actions
  *
  * \return Node that \p rsc is assigned to, if assigned entirely to one node
  *
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 pcmk_node_t *
 pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                    bool stop_if_fail)
 {
     pcmk_node_t *first_assigned_node = NULL;
     pcmk_resource_t *first_member = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return rsc->allocated_to; // Assignment already done
     }
     if (pcmk_is_set(rsc->flags, pcmk_rsc_assigning)) {
         pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s",
                         rsc->id);
         return NULL;
     }
 
     if (rsc->children == NULL) {
         // No members to assign
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_unassigned);
         return NULL;
     }
 
     pcmk__set_rsc_flags(rsc, pcmk_rsc_assigning);
     first_member = (pcmk_resource_t *) rsc->children->data;
     rsc->role = first_member->role;
 
     pe__show_node_scores(!pcmk_is_set(rsc->cluster->flags,
                                       pcmk_sched_output_scores),
                          rsc, __func__, rsc->allowed_nodes, rsc->cluster);
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
         pcmk_node_t *node = NULL;
 
         pcmk__rsc_trace(rsc, "Assigning group %s member %s",
                         rsc->id, member->id);
         node = member->cmds->assign(member, prefer, stop_if_fail);
         if (first_assigned_node == NULL) {
             first_assigned_node = node;
         }
     }
 
     pe__set_next_role(rsc, first_member->next_role, "first group member");
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_assigning|pcmk_rsc_unassigned);
 
     if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
         return NULL;
     }
     return first_assigned_node;
 }
 
 /*!
  * \internal
  * \brief Create a pseudo-operation for a group as an ordering point
  *
  * \param[in,out] group   Group resource to create action for
  * \param[in]     action  Action name
  *
  * \return Newly created pseudo-operation
  */
 static pcmk_action_t *
 create_group_pseudo_op(pcmk_resource_t *group, const char *action)
 {
     pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
                                       action, NULL, TRUE, group->cluster);
 
     pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
     return op;
 }
 
 /*!
  * \internal
  * \brief Create all actions needed for a given group resource
  *
  * \param[in,out] rsc  Group resource to create actions for
  */
 void
 pcmk__group_create_actions(pcmk_resource_t *rsc)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
 
     pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id);
 
     // Create actions for individual group members
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 
         member->cmds->create_actions(member);
     }
 
     // Create pseudo-actions for group itself to serve as ordering points
     create_group_pseudo_op(rsc, PCMK_ACTION_START);
     create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING);
     create_group_pseudo_op(rsc, PCMK_ACTION_STOP);
     create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED);
     if (crm_is_true(g_hash_table_lookup(rsc->meta, PCMK_META_PROMOTABLE))) {
         create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE);
         create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED);
         create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE);
         create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED);
     }
 }
 
 // User data for member_internal_constraints()
 struct member_data {
     // These could be derived from member but this avoids some function calls
     bool ordered;
     bool colocated;
     bool promotable;
 
     pcmk_resource_t *last_active;
     pcmk_resource_t *previous_member;
 };
 
 /*!
  * \internal
  * \brief Create implicit constraints needed for a group member
  *
  * \param[in,out] data       Group member to create implicit constraints for
  * \param[in,out] user_data  Member data (struct member_data *)
  */
 static void
 member_internal_constraints(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *member = (pcmk_resource_t *) data;
     struct member_data *member_data = (struct member_data *) user_data;
 
     // For ordering demote vs demote or stop vs stop
     uint32_t down_flags = pcmk__ar_then_implies_first_graphed;
 
     // For ordering demote vs demoted or stop vs stopped
     uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed;
 
     // Create the individual member's implicit constraints
     member->cmds->internal_constraints(member);
 
     if (member_data->previous_member == NULL) {
         // This is first member
         if (member_data->ordered) {
             pcmk__set_relation_flags(down_flags, pcmk__ar_ordered);
             post_down_flags = pcmk__ar_first_implies_then;
         }
 
     } else if (member_data->colocated) {
         uint32_t flags = pcmk__coloc_none;
 
         if (pcmk_is_set(member->flags, pcmk_rsc_critical)) {
             flags |= pcmk__coloc_influence;
         }
 
         // Colocate this member with the previous one
-        pcmk__new_colocation("#group-members", NULL, INFINITY, member,
-                             member_data->previous_member, NULL, NULL, flags);
+        pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY,
+                             member, member_data->previous_member, NULL, NULL,
+                             flags);
     }
 
     if (member_data->promotable) {
         // Demote group -> demote member -> group is demoted
         pcmk__order_resource_actions(member->parent, PCMK_ACTION_DEMOTE,
                                      member, PCMK_ACTION_DEMOTE, down_flags);
         pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
                                      member->parent, PCMK_ACTION_DEMOTED,
                                      post_down_flags);
 
         // Promote group -> promote member -> group is promoted
         pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE,
                                      member->parent, PCMK_ACTION_PROMOTED,
                                      pcmk__ar_unrunnable_first_blocks
                                      |pcmk__ar_first_implies_then
                                      |pcmk__ar_first_implies_then_graphed);
         pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
                                      member, PCMK_ACTION_PROMOTE,
                                      pcmk__ar_then_implies_first_graphed);
     }
 
     // Stop group -> stop member -> group is stopped
     pcmk__order_stops(member->parent, member, down_flags);
     pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
                                  member->parent, PCMK_ACTION_STOPPED,
                                  post_down_flags);
 
     // Start group -> start member -> group is started
     pcmk__order_starts(member->parent, member,
                        pcmk__ar_then_implies_first_graphed);
     pcmk__order_resource_actions(member, PCMK_ACTION_START,
                                  member->parent, PCMK_ACTION_RUNNING,
                                  pcmk__ar_unrunnable_first_blocks
                                  |pcmk__ar_first_implies_then
                                  |pcmk__ar_first_implies_then_graphed);
 
     if (!member_data->ordered) {
         pcmk__order_starts(member->parent, member,
                            pcmk__ar_first_implies_then
                            |pcmk__ar_unrunnable_first_blocks
                            |pcmk__ar_then_implies_first_graphed);
         if (member_data->promotable) {
             pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
                                          member, PCMK_ACTION_PROMOTE,
                                          pcmk__ar_first_implies_then
                                          |pcmk__ar_unrunnable_first_blocks
                                          |pcmk__ar_then_implies_first_graphed);
         }
 
     } else if (member_data->previous_member == NULL) {
         pcmk__order_starts(member->parent, member, pcmk__ar_none);
         if (member_data->promotable) {
             pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
                                          member, PCMK_ACTION_PROMOTE,
                                          pcmk__ar_none);
         }
 
     } else {
         // Order this member relative to the previous one
 
         pcmk__order_starts(member_data->previous_member, member,
                            pcmk__ar_first_implies_then
                            |pcmk__ar_unrunnable_first_blocks);
         pcmk__order_stops(member, member_data->previous_member,
                           pcmk__ar_ordered|pcmk__ar_intermediate_stop);
 
         /* In unusual circumstances (such as adding a new member to the middle
          * of a group with unmanaged later members), this member may be active
          * while the previous (new) member is inactive. In this situation, the
          * usual restart orderings will be irrelevant, so we need to order this
          * member's stop before the previous member's start.
          */
         if ((member->running_on != NULL)
             && (member_data->previous_member->running_on == NULL)) {
             pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
                                          member_data->previous_member,
                                          PCMK_ACTION_START,
                                          pcmk__ar_then_implies_first
                                          |pcmk__ar_unrunnable_first_blocks);
         }
 
         if (member_data->promotable) {
             pcmk__order_resource_actions(member_data->previous_member,
                                          PCMK_ACTION_PROMOTE, member,
                                          PCMK_ACTION_PROMOTE,
                                          pcmk__ar_first_implies_then
                                          |pcmk__ar_unrunnable_first_blocks);
             pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
                                          member_data->previous_member,
                                          PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
         }
     }
 
     // Make sure partially active groups shut down in sequence
     if (member->running_on != NULL) {
         if (member_data->ordered && (member_data->previous_member != NULL)
             && (member_data->previous_member->running_on == NULL)
             && (member_data->last_active != NULL)
             && (member_data->last_active->running_on != NULL)) {
             pcmk__order_stops(member, member_data->last_active,
                               pcmk__ar_ordered);
         }
         member_data->last_active = member;
     }
 
     member_data->previous_member = member;
 }
 
 /*!
  * \internal
  * \brief Create implicit constraints needed for a group resource
  *
  * \param[in,out] rsc  Group resource to create implicit constraints for
  */
 void
 pcmk__group_internal_constraints(pcmk_resource_t *rsc)
 {
     struct member_data member_data = { false, };
     const pcmk_resource_t *top = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
 
     /* Order group pseudo-actions relative to each other for restarting:
      * stop group -> group is stopped -> start group -> group is started
      */
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
                                  rsc, PCMK_ACTION_STOPPED,
                                  pcmk__ar_unrunnable_first_blocks);
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
                                  rsc, PCMK_ACTION_START,
                                  pcmk__ar_ordered);
     pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
                                  rsc, PCMK_ACTION_RUNNING,
                                  pcmk__ar_unrunnable_first_blocks);
 
     top = pe__const_top_resource(rsc, false);
 
     member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered);
     member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated);
     member_data.promotable = pcmk_is_set(top->flags, pcmk_rsc_promotable);
     g_list_foreach(rsc->children, member_internal_constraints, &member_data);
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint for a group with some other resource, apply the
  * score to the dependent's allowed node scores (if we are still placing
  * resources) or priority (if we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent group resource in colocation
  * \param[in]     primary        Primary resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  */
 static void
 colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
                     const pcmk__colocation_t *colocation)
 {
     pcmk_resource_t *member = NULL;
 
     if (dependent->children == NULL) {
         return;
     }
 
     pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
                     colocation->id, dependent->id, primary->id);
 
     if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) {
         // Colocate first member (internal colocations will handle the rest)
         member = (pcmk_resource_t *) dependent->children->data;
         member->cmds->apply_coloc_score(member, primary, colocation, true);
         return;
     }
 
-    if (colocation->score >= INFINITY) {
+    if (colocation->score >= PCMK_SCORE_INFINITY) {
         pcmk__config_err("%s: Cannot perform mandatory colocation between "
                          "non-colocated group and %s",
                          dependent->id, primary->id);
         return;
     }
 
     // Colocate each member individually
     for (GList *iter = dependent->children; iter != NULL; iter = iter->next) {
         member = (pcmk_resource_t *) iter->data;
         member->cmds->apply_coloc_score(member, primary, colocation, true);
     }
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint for some other resource with a group, apply the
  * score to the dependent's allowed node scores (if we are still placing
  * resources) or priority (if we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent resource in colocation
  * \param[in]     primary        Primary group resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  */
 static void
 colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
                     const pcmk__colocation_t *colocation)
 {
     const pcmk_resource_t *member = NULL;
 
     pcmk__rsc_trace(primary,
                     "Processing colocation %s (%s with group %s) for primary",
                     colocation->id, dependent->id, primary->id);
 
     if (pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
         return;
     }
 
     if (pe__group_flag_is_set(primary, pcmk__group_colocated)) {
 
-        if (colocation->score >= INFINITY) {
+        if (colocation->score >= PCMK_SCORE_INFINITY) {
             /* For mandatory colocations, the entire group must be assignable
              * (and in the specified role if any), so apply the colocation based
              * on the last member.
              */
             member = pe__last_group_member(primary);
         } else if (primary->children != NULL) {
             /* For optional colocations, whether the group is partially or fully
              * up doesn't matter, so apply the colocation based on the first
              * member.
              */
             member = (pcmk_resource_t *) primary->children->data;
         }
         if (member == NULL) {
             return; // Nothing to colocate with
         }
 
         member->cmds->apply_coloc_score(dependent, member, colocation, false);
         return;
     }
 
-    if (colocation->score >= INFINITY) {
+    if (colocation->score >= PCMK_SCORE_INFINITY) {
         pcmk__config_err("%s: Cannot perform mandatory colocation with"
                          " non-colocated group %s",
                          dependent->id, primary->id);
         return;
     }
 
     // Colocate dependent with each member individually
     for (const GList *iter = primary->children; iter != NULL;
          iter = iter->next) {
         member = iter->data;
         member->cmds->apply_coloc_score(dependent, member, colocation, false);
     }
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint, apply its score to the dependent's
  * allowed node scores (if we are still placing resources) or priority (if
  * we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent resource in colocation
  * \param[in]     primary        Primary resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  * \param[in]     for_dependent  true if called on behalf of dependent
  */
 void
 pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
                               const pcmk_resource_t *primary,
                               const pcmk__colocation_t *colocation,
                               bool for_dependent)
 {
     CRM_ASSERT((dependent != NULL) && (primary != NULL)
                && (colocation != NULL));
 
     if (for_dependent) {
         colocate_group_with(dependent, primary, colocation);
 
     } else {
         // Method should only be called for primitive dependents
         CRM_ASSERT(dependent->variant == pcmk_rsc_variant_primitive);
 
         colocate_with_group(dependent, primary, colocation);
     }
 }
 
 /*!
  * \internal
  * \brief Return action flags for a given group resource action
  *
  * \param[in,out] action  Group action to get flags for
  * \param[in]     node    If not NULL, limit effects to this node
  *
  * \return Flags appropriate to \p action on \p node
  */
 uint32_t
 pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
 {
     // Default flags for a group action
     uint32_t flags = pcmk_action_optional
                      |pcmk_action_runnable
                      |pcmk_action_pseudo;
 
     CRM_ASSERT(action != NULL);
 
     // Update flags considering each member's own flags for same action
     for (GList *iter = action->rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 
         // Check whether member has the same action
         enum action_tasks task = get_complex_task(member, action->task);
         const char *task_s = pcmk_action_text(task);
         pcmk_action_t *member_action = find_first_action(member->actions, NULL,
                                                          task_s, node);
 
         if (member_action != NULL) {
             uint32_t member_flags = member->cmds->action_flags(member_action,
                                                                node);
 
             // Group action is mandatory if any member action is
             if (pcmk_is_set(flags, pcmk_action_optional)
                 && !pcmk_is_set(member_flags, pcmk_action_optional)) {
                 pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is",
                                 action->uuid, member_action->uuid);
                 pcmk__clear_raw_action_flags(flags, "group action",
                                              pcmk_action_optional);
                 pcmk__clear_action_flags(action, pcmk_action_optional);
             }
 
             // Group action is unrunnable if any member action is
             if (!pcmk__str_eq(task_s, action->task, pcmk__str_none)
                 && pcmk_is_set(flags, pcmk_action_runnable)
                 && !pcmk_is_set(member_flags, pcmk_action_runnable)) {
 
                 pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is",
                                 action->uuid, member_action->uuid);
                 pcmk__clear_raw_action_flags(flags, "group action",
                                              pcmk_action_runnable);
                 pcmk__clear_action_flags(action, pcmk_action_runnable);
             }
 
         /* Group (pseudo-)actions other than stop or demote are unrunnable
          * unless every member will do it.
          */
         } else if ((task != pcmk_action_stop) && (task != pcmk_action_demote)) {
             pcmk__rsc_trace(action->rsc,
                             "%s is not runnable because %s will not %s",
                             action->uuid, member->id, task_s);
             pcmk__clear_raw_action_flags(flags, "group action",
                                          pcmk_action_runnable);
         }
     }
 
     return flags;
 }
 
 /*!
  * \internal
  * \brief Update two actions according to an ordering between them
  *
  * Given information about an ordering of two actions, update the actions' flags
  * (and runnable_before members if appropriate) as appropriate for the ordering.
  * Effects may cascade to other orderings involving the actions as well.
  *
  * \param[in,out] first      'First' action in an ordering
  * \param[in,out] then       'Then' action in an ordering
  * \param[in]     node       If not NULL, limit scope of ordering to this node
  *                           (only used when interleaving instances)
  * \param[in]     flags      Action flags for \p first for ordering purposes
  * \param[in]     filter     Action flags to limit scope of certain updates (may
  *                           include pcmk_action_optional to affect only
  *                           mandatory actions, and pcmk_action_runnable to
  *                           affect only runnable actions)
  * \param[in]     type       Group of enum pcmk__action_relation_flags to apply
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Group of enum pcmk__updated flags indicating what was updated
  */
 uint32_t
 pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
                                    const pcmk_node_t *node, uint32_t flags,
                                    uint32_t filter, uint32_t type,
                                    pcmk_scheduler_t *scheduler)
 {
     uint32_t changed = pcmk__updated_none;
 
     // Group method can be called only on behalf of "then" action
     CRM_ASSERT((first != NULL) && (then != NULL) && (then->rsc != NULL)
                && (scheduler != NULL));
 
     // Update the actions for the group itself
     changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
                                             type, scheduler);
 
     // Update the actions for each group member
     for (GList *iter = then->rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 
         pcmk_action_t *member_action = find_first_action(member->actions, NULL,
                                                          then->task, node);
 
         if (member_action != NULL) {
             changed |= member->cmds->update_ordered_actions(first,
                                                             member_action, node,
                                                             flags, filter, type,
                                                             scheduler);
         }
     }
     return changed;
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a group's allowed node scores
  *
  * \param[in,out] rsc       Group resource to apply constraint to
  * \param[in,out] location  Location constraint to apply
  */
 void
 pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
 {
     GList *node_list_orig = NULL;
     GList *node_list_copy = NULL;
     bool reset_scores = true;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
                && (location != NULL));
 
     node_list_orig = location->nodes;
     node_list_copy = pcmk__copy_node_list(node_list_orig, true);
     reset_scores = pe__group_flag_is_set(rsc, pcmk__group_colocated);
 
     // Apply the constraint for the group itself (updates node scores)
     pcmk__apply_location(rsc, location);
 
     // Apply the constraint for each member
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 
         member->cmds->apply_location(member, location);
 
         if (reset_scores) {
             /* The first member of colocated groups needs to use the original
              * node scores, but subsequent members should work on a copy, since
              * the first member's scores already incorporate theirs.
              */
             reset_scores = false;
             location->nodes = node_list_copy;
         }
     }
 
     location->nodes = node_list_orig;
     g_list_free_full(node_list_copy, free);
 }
 
 // Group implementation of pcmk_assignment_methods_t:colocated_resources()
 GList *
 pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
                                 const pcmk_resource_t *orig_rsc,
                                 GList *colocated_rscs)
 {
     const pcmk_resource_t *member = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
 
     if (orig_rsc == NULL) {
         orig_rsc = rsc;
     }
 
     if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
         || pcmk__is_clone(rsc->parent)) {
         /* This group has colocated members and/or is cloned -- either way,
          * add every child's colocated resources to the list. The first and last
          * members will include the group's own colocations.
          */
         colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
         for (const GList *iter = rsc->children;
              iter != NULL; iter = iter->next) {
 
             member = (const pcmk_resource_t *) iter->data;
             colocated_rscs = member->cmds->colocated_resources(member, orig_rsc,
                                                                colocated_rscs);
         }
 
     } else if (rsc->children != NULL) {
         /* This group's members are not colocated, and the group is not cloned,
          * so just add the group's own colocations to the list.
          */
         colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc,
                                                    colocated_rscs);
     }
 
     return colocated_rscs;
 }
 
 // Group implementation of pcmk_assignment_methods_t:with_this_colocations()
 void
 pcmk__with_group_colocations(const pcmk_resource_t *rsc,
                              const pcmk_resource_t *orig_rsc, GList **list)
 
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
                && (orig_rsc != NULL) && (list != NULL));
 
     // Ignore empty groups
     if (rsc->children == NULL) {
         return;
     }
 
     /* "With this" colocations are needed only for the group itself and for its
      * last member. (Previous members will chain via the group internal
      * colocations.)
      */
     if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) {
         return;
     }
 
     pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s",
                     rsc->id, orig_rsc->id);
 
     // Add the group's own colocations
     pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
 
     // If cloned, add any relevant colocations with the clone
     if (rsc->parent != NULL) {
         rsc->parent->cmds->with_this_colocations(rsc->parent, orig_rsc,
                                                  list);
     }
 
     if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
         // @COMPAT Non-colocated groups are deprecated
         return;
     }
 
     // Add explicit colocations with the group's (other) children
     for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         const pcmk_resource_t *member = iter->data;
 
         if (member != orig_rsc) {
             member->cmds->with_this_colocations(member, orig_rsc, list);
         }
     }
 }
 
 // Group implementation of pcmk_assignment_methods_t:this_with_colocations()
 void
 pcmk__group_with_colocations(const pcmk_resource_t *rsc,
                              const pcmk_resource_t *orig_rsc, GList **list)
 {
     const pcmk_resource_t *member = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
                && (orig_rsc != NULL) && (list != NULL));
 
     // Ignore empty groups
     if (rsc->children == NULL) {
         return;
     }
 
     /* "This with" colocations are normally needed only for the group itself and
      * for its first member.
      */
     if ((rsc == orig_rsc)
         || (orig_rsc == (const pcmk_resource_t *) rsc->children->data)) {
         pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s",
                         rsc->id, orig_rsc->id);
 
         // Add the group's own colocations
         pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
 
         // If cloned, add any relevant colocations involving the clone
         if (rsc->parent != NULL) {
             rsc->parent->cmds->this_with_colocations(rsc->parent, orig_rsc,
                                                      list);
         }
 
         if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
             // @COMPAT Non-colocated groups are deprecated
             return;
         }
 
         // Add explicit colocations involving the group's (other) children
         for (const GList *iter = rsc->children;
              iter != NULL; iter = iter->next) {
             member = iter->data;
             if (member != orig_rsc) {
                 member->cmds->this_with_colocations(member, orig_rsc, list);
             }
         }
         return;
     }
 
     /* Later group members honor the group's colocations indirectly, due to the
      * internal group colocations that chain everything from the first member.
      * However, if an earlier group member is unmanaged, this chaining will not
      * happen, so the group's mandatory colocations must be explicitly added.
      */
     for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         member = iter->data;
         if (orig_rsc == member) {
             break; // We've seen all earlier members, and none are unmanaged
         }
 
         if (!pcmk_is_set(member->flags, pcmk_rsc_managed)) {
             crm_trace("Adding mandatory '%s with' colocations to list for "
                       "member %s because earlier member %s is unmanaged",
                       rsc->id, orig_rsc->id, member->id);
             for (const GList *cons_iter = rsc->rsc_cons; cons_iter != NULL;
                  cons_iter = cons_iter->next) {
                 const pcmk__colocation_t *colocation = NULL;
 
                 colocation = (const pcmk__colocation_t *) cons_iter->data;
-                if (colocation->score == INFINITY) {
+                if (colocation->score == PCMK_SCORE_INFINITY) {
                     pcmk__add_this_with(list, colocation, orig_rsc);
                 }
             }
             // @TODO Add mandatory (or all?) clone constraints if cloned
             break;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Update nodes with scores of colocated resources' nodes
  *
  * Given a table of nodes and a resource, update the nodes' scores with the
  * scores of the best nodes matching the attribute used for each of the
  * resource's relevant colocations.
  *
  * \param[in,out] source_rsc  Group resource whose node scores to add
  * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
  * \param[in]     log_id      Resource ID for logs (if \c NULL, use
  *                            \p source_rsc ID)
  * \param[in,out] nodes       Nodes to update (set initial contents to \c NULL
  *                            to copy allowed nodes from \p source_rsc)
  * \param[in]     colocation  Original colocation constraint (used to get
  *                            configured primary resource's stickiness, and
  *                            to get colocation node attribute; if \c NULL,
  *                            <tt>source_rsc</tt>'s own matching node scores will
  *                            not be added, and \p *nodes must be \c NULL as
  *                            well)
  * \param[in]     factor      Incorporate scores multiplied by this factor
  * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
  *
  * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
  *       the \c pcmk__coloc_select_this_with flag are used together (and only by
  *       \c cmp_resources()).
  * \note The caller remains responsible for freeing \p *nodes.
  * \note This is the group implementation of
  *       \c pcmk_assignment_methods_t:add_colocated_node_scores().
  */
 void
 pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
                                       const pcmk_resource_t *target_rsc,
                                       const char *log_id, GHashTable **nodes,
                                       const pcmk__colocation_t *colocation,
                                       float factor, uint32_t flags)
 {
     pcmk_resource_t *member = NULL;
 
     CRM_ASSERT((source_rsc != NULL)
                && (source_rsc->variant == pcmk_rsc_variant_group)
                && (nodes != NULL)
                && ((colocation != NULL)
                    || ((target_rsc == NULL) && (*nodes == NULL))));
 
     if (log_id == NULL) {
         log_id = source_rsc->id;
     }
 
     // Avoid infinite recursion
     if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
         pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
                        log_id, source_rsc->id);
         return;
     }
     pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
 
     // Ignore empty groups (only possible with schema validation disabled)
     if (source_rsc->children == NULL) {
         return;
     }
 
     /* Refer the operation to the first or last member as appropriate.
      *
      * cmp_resources() is the only caller that passes a NULL nodes table,
      * and is also the only caller using pcmk__coloc_select_this_with.
      * For "this with" colocations, the last member will recursively incorporate
      * all the other members' "this with" colocations via the internal group
      * colocations (and via the first member, the group's own colocations).
      *
      * For "with this" colocations, the first member works similarly.
      */
     if (*nodes == NULL) {
         member = pe__last_group_member(source_rsc);
     } else {
         member = source_rsc->children->data;
     }
     pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s "
                     "(at %.6f)", log_id, source_rsc->id, member->id, factor);
     member->cmds->add_colocated_node_scores(member, target_rsc, log_id, nodes,
                                             colocation, factor, flags);
     pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
 }
 
 // Group implementation of pcmk_assignment_methods_t:add_utilization()
 void
 pcmk__group_add_utilization(const pcmk_resource_t *rsc,
                             const pcmk_resource_t *orig_rsc, GList *all_rscs,
                             GHashTable *utilization)
 {
     pcmk_resource_t *member = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
                && (orig_rsc != NULL) && (utilization != NULL));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return;
     }
 
     pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
                     orig_rsc->id, rsc->id);
     if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
         || pcmk__is_clone(rsc->parent)) {
         // Every group member will be on same node, so sum all members
         for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
             member = (pcmk_resource_t *) iter->data;
 
             if (pcmk_is_set(member->flags, pcmk_rsc_unassigned)
                 && (g_list_find(all_rscs, member) == NULL)) {
                 member->cmds->add_utilization(member, orig_rsc, all_rscs,
                                               utilization);
             }
         }
 
     } else if (rsc->children != NULL) {
         // Just add first member's utilization
         member = (pcmk_resource_t *) rsc->children->data;
         if ((member != NULL)
             && pcmk_is_set(member->flags, pcmk_rsc_unassigned)
             && (g_list_find(all_rscs, member) == NULL)) {
 
             member->cmds->add_utilization(member, orig_rsc, all_rscs,
                                           utilization);
         }
     }
 }
 
 void
 pcmk__group_shutdown_lock(pcmk_resource_t *rsc)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
 
         member->cmds->shutdown_lock(member);
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_instances.c b/lib/pacemaker/pcmk_sched_instances.c
index 544f1e3e0b..eba34deea7 100644
--- a/lib/pacemaker/pcmk_sched_instances.c
+++ b/lib/pacemaker/pcmk_sched_instances.c
@@ -1,1689 +1,1689 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 /* This file is intended for code usable with both clone instances and bundle
  * replica containers.
  */
 
 #include <crm_internal.h>
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Check whether a node is allowed to run an instance
  *
  * \param[in] instance      Clone instance or bundle container to check
  * \param[in] node          Node to check
  * \param[in] max_per_node  Maximum number of instances allowed to run on a node
  *
  * \return true if \p node is allowed to run \p instance, otherwise false
  */
 static bool
 can_run_instance(const pcmk_resource_t *instance, const pcmk_node_t *node,
                  int max_per_node)
 {
     pcmk_node_t *allowed_node = NULL;
 
     if (pcmk_is_set(instance->flags, pcmk_rsc_removed)) {
         pcmk__rsc_trace(instance, "%s cannot run on %s: orphaned",
                         instance->id, pcmk__node_name(node));
         return false;
     }
 
     if (!pcmk__node_available(node, false, false)) {
         pcmk__rsc_trace(instance,
                         "%s cannot run on %s: node cannot run resources",
                         instance->id, pcmk__node_name(node));
         return false;
     }
 
     allowed_node = pcmk__top_allowed_node(instance, node);
     if (allowed_node == NULL) {
         crm_warn("%s cannot run on %s: node not allowed",
                  instance->id, pcmk__node_name(node));
         return false;
     }
 
     if (allowed_node->weight < 0) {
         pcmk__rsc_trace(instance,
                         "%s cannot run on %s: parent score is %s there",
                         instance->id, pcmk__node_name(node),
                         pcmk_readable_score(allowed_node->weight));
         return false;
     }
 
     if (allowed_node->count >= max_per_node) {
         pcmk__rsc_trace(instance,
                         "%s cannot run on %s: node already has %d instance%s",
                         instance->id, pcmk__node_name(node), max_per_node,
                         pcmk__plural_s(max_per_node));
         return false;
     }
 
     pcmk__rsc_trace(instance, "%s can run on %s (%d already running)",
                     instance->id, pcmk__node_name(node), allowed_node->count);
     return true;
 }
 
 /*!
  * \internal
  * \brief Ban a clone instance or bundle replica from unavailable allowed nodes
  *
  * \param[in,out] instance      Clone instance or bundle replica to ban
  * \param[in]     max_per_node  Maximum instances allowed to run on a node
  */
 static void
 ban_unavailable_allowed_nodes(pcmk_resource_t *instance, int max_per_node)
 {
     if (instance->allowed_nodes != NULL) {
         GHashTableIter iter;
         pcmk_node_t *node = NULL;
 
         g_hash_table_iter_init(&iter, instance->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             if (!can_run_instance(instance, node, max_per_node)) {
                 pcmk__rsc_trace(instance, "Banning %s from unavailable node %s",
                                 instance->id, pcmk__node_name(node));
-                node->weight = -INFINITY;
+                node->weight = -PCMK_SCORE_INFINITY;
                 for (GList *child_iter = instance->children;
                      child_iter != NULL; child_iter = child_iter->next) {
                     pcmk_resource_t *child = child_iter->data;
                     pcmk_node_t *child_node = NULL;
 
                     child_node = g_hash_table_lookup(child->allowed_nodes,
                                                      node->details->id);
                     if (child_node != NULL) {
                         pcmk__rsc_trace(instance,
                                         "Banning %s child %s "
                                         "from unavailable node %s",
                                         instance->id, child->id,
                                         pcmk__node_name(node));
-                        child_node->weight = -INFINITY;
+                        child_node->weight = -PCMK_SCORE_INFINITY;
                     }
                 }
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Create a hash table with a single node in it
  *
  * \param[in] node  Node to copy into new table
  *
  * \return Newly created hash table containing a copy of \p node
  * \note The caller is responsible for freeing the result with
  *       g_hash_table_destroy().
  */
 static GHashTable *
 new_node_table(pcmk_node_t *node)
 {
     GHashTable *table = pcmk__strkey_table(NULL, free);
 
     node = pe__copy_node(node);
     g_hash_table_insert(table, (gpointer) node->details->id, node);
     return table;
 }
 
 /*!
  * \internal
  * \brief Apply a resource's parent's colocation scores to a node table
  *
  * \param[in]     rsc    Resource whose colocations should be applied
  * \param[in,out] nodes  Node table to apply colocations to
  */
 static void
 apply_parent_colocations(const pcmk_resource_t *rsc, GHashTable **nodes)
 {
     GList *colocations = pcmk__this_with_colocations(rsc);
 
     for (const GList *iter = colocations; iter != NULL; iter = iter->next) {
         const pcmk__colocation_t *colocation = iter->data;
         pcmk_resource_t *other = colocation->primary;
-        float factor = colocation->score / (float) INFINITY;
+        float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
 
         other->cmds->add_colocated_node_scores(other, rsc, rsc->id, nodes,
                                                colocation, factor,
                                                pcmk__coloc_select_default);
     }
     g_list_free(colocations);
     colocations = pcmk__with_this_colocations(rsc);
 
     for (const GList *iter = colocations; iter != NULL; iter = iter->next) {
         const pcmk__colocation_t *colocation = iter->data;
         pcmk_resource_t *other = colocation->dependent;
-        float factor = colocation->score / (float) INFINITY;
+        float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
 
         if (!pcmk__colocation_has_influence(colocation, rsc)) {
             continue;
         }
         other->cmds->add_colocated_node_scores(other, rsc, rsc->id, nodes,
                                                colocation, factor,
                                                pcmk__coloc_select_nonnegative);
     }
     g_list_free(colocations);
 }
 
 /*!
  * \internal
  * \brief Compare clone or bundle instances based on colocation scores
  *
  * Determine the relative order in which two clone or bundle instances should be
  * assigned to nodes, considering the scores of colocation constraints directly
  * or indirectly involving them.
  *
  * \param[in] instance1  First instance to compare
  * \param[in] instance2  Second instance to compare
  *
  * \return A negative number if \p instance1 should be assigned first,
  *         a positive number if \p instance2 should be assigned first,
  *         or 0 if assignment order doesn't matter
  */
 static int
 cmp_instance_by_colocation(const pcmk_resource_t *instance1,
                            const pcmk_resource_t *instance2)
 {
     int rc = 0;
     pcmk_node_t *node1 = NULL;
     pcmk_node_t *node2 = NULL;
     pcmk_node_t *current_node1 = pcmk__current_node(instance1);
     pcmk_node_t *current_node2 = pcmk__current_node(instance2);
     GHashTable *colocated_scores1 = NULL;
     GHashTable *colocated_scores2 = NULL;
 
     CRM_ASSERT((instance1 != NULL) && (instance1->parent != NULL)
                && (instance2 != NULL) && (instance2->parent != NULL)
                && (current_node1 != NULL) && (current_node2 != NULL));
 
     // Create node tables initialized with each node
     colocated_scores1 = new_node_table(current_node1);
     colocated_scores2 = new_node_table(current_node2);
 
     // Apply parental colocations
     apply_parent_colocations(instance1, &colocated_scores1);
     apply_parent_colocations(instance2, &colocated_scores2);
 
     // Find original nodes again, with scores updated for colocations
     node1 = g_hash_table_lookup(colocated_scores1, current_node1->details->id);
     node2 = g_hash_table_lookup(colocated_scores2, current_node2->details->id);
 
     // Compare nodes by updated scores
     if (node1->weight < node2->weight) {
         crm_trace("Assign %s (%d on %s) after %s (%d on %s)",
                   instance1->id, node1->weight, pcmk__node_name(node1),
                   instance2->id, node2->weight, pcmk__node_name(node2));
         rc = 1;
 
     } else if (node1->weight > node2->weight) {
         crm_trace("Assign %s (%d on %s) before %s (%d on %s)",
                   instance1->id, node1->weight, pcmk__node_name(node1),
                   instance2->id, node2->weight, pcmk__node_name(node2));
         rc = -1;
     }
 
     g_hash_table_destroy(colocated_scores1);
     g_hash_table_destroy(colocated_scores2);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Check whether a resource or any of its children are failed
  *
  * \param[in] rsc  Resource to check
  *
  * \return true if \p rsc or any of its children are failed, otherwise false
  */
 static bool
 did_fail(const pcmk_resource_t *rsc)
 {
     if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         return true;
     }
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         if (did_fail((const pcmk_resource_t *) iter->data)) {
             return true;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Check whether a node is allowed to run a resource
  *
  * \param[in]     rsc   Resource to check
  * \param[in,out] node  Node to check (will be set NULL if not allowed)
  *
  * \return true if *node is either NULL or allowed for \p rsc, otherwise false
  */
 static bool
 node_is_allowed(const pcmk_resource_t *rsc, pcmk_node_t **node)
 {
     if (*node != NULL) {
         pcmk_node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes,
                                                    (*node)->details->id);
 
         if ((allowed == NULL) || (allowed->weight < 0)) {
             pcmk__rsc_trace(rsc, "%s: current location (%s) is unavailable",
                             rsc->id, pcmk__node_name(*node));
             *node = NULL;
             return false;
         }
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Compare two clone or bundle instances' instance numbers
  *
  * \param[in] a  First instance to compare
  * \param[in] b  Second instance to compare
  *
  * \return A negative number if \p a's instance number is lower,
  *         a positive number if \p b's instance number is lower,
  *         or 0 if their instance numbers are the same
  */
 gint
 pcmk__cmp_instance_number(gconstpointer a, gconstpointer b)
 {
     const pcmk_resource_t *instance1 = (const pcmk_resource_t *) a;
     const pcmk_resource_t *instance2 = (const pcmk_resource_t *) b;
     char *div1 = NULL;
     char *div2 = NULL;
 
     CRM_ASSERT((instance1 != NULL) && (instance2 != NULL));
 
     // Clone numbers are after a colon, bundle numbers after a dash
     div1 = strrchr(instance1->id, ':');
     if (div1 == NULL) {
         div1 = strrchr(instance1->id, '-');
     }
     div2 = strrchr(instance2->id, ':');
     if (div2 == NULL) {
         div2 = strrchr(instance2->id, '-');
     }
     CRM_ASSERT((div1 != NULL) && (div2 != NULL));
 
     return (gint) (strtol(div1 + 1, NULL, 10) - strtol(div2 + 1, NULL, 10));
 }
 
 /*!
  * \internal
  * \brief Compare clone or bundle instances according to assignment order
  *
  * Compare two clone or bundle instances according to the order they should be
  * assigned to nodes, preferring (in order):
  *
  *  - Active instance that is less multiply active
  *  - Instance that is not active on a disallowed node
  *  - Instance with higher configured priority
  *  - Active instance whose current node can run resources
  *  - Active instance whose parent is allowed on current node
  *  - Active instance whose current node has fewer other instances
  *  - Active instance
  *  - Instance that isn't failed
  *  - Instance whose colocations result in higher score on current node
  *  - Instance with lower ID in lexicographic order
  *
  * \param[in] a          First instance to compare
  * \param[in] b          Second instance to compare
  *
  * \return A negative number if \p a should be assigned first,
  *         a positive number if \p b should be assigned first,
  *         or 0 if assignment order doesn't matter
  */
 gint
 pcmk__cmp_instance(gconstpointer a, gconstpointer b)
 {
     int rc = 0;
     pcmk_node_t *node1 = NULL;
     pcmk_node_t *node2 = NULL;
     unsigned int nnodes1 = 0;
     unsigned int nnodes2 = 0;
 
     bool can1 = true;
     bool can2 = true;
 
     const pcmk_resource_t *instance1 = (const pcmk_resource_t *) a;
     const pcmk_resource_t *instance2 = (const pcmk_resource_t *) b;
 
     CRM_ASSERT((instance1 != NULL) && (instance2 != NULL));
 
     node1 = instance1->fns->active_node(instance1, &nnodes1, NULL);
     node2 = instance2->fns->active_node(instance2, &nnodes2, NULL);
 
     /* If both instances are running and at least one is multiply
      * active, prefer instance that's running on fewer nodes.
      */
     if ((nnodes1 > 0) && (nnodes2 > 0)) {
         if (nnodes1 < nnodes2) {
             crm_trace("Assign %s (active on %d) before %s (active on %d): "
                       "less multiply active",
                       instance1->id, nnodes1, instance2->id, nnodes2);
             return -1;
 
         } else if (nnodes1 > nnodes2) {
             crm_trace("Assign %s (active on %d) after %s (active on %d): "
                       "more multiply active",
                       instance1->id, nnodes1, instance2->id, nnodes2);
             return 1;
         }
     }
 
     /* An instance that is either inactive or active on an allowed node is
      * preferred over an instance that is active on a no-longer-allowed node.
      */
     can1 = node_is_allowed(instance1, &node1);
     can2 = node_is_allowed(instance2, &node2);
     if (can1 && !can2) {
         crm_trace("Assign %s before %s: not active on a disallowed node",
                   instance1->id, instance2->id);
         return -1;
 
     } else if (!can1 && can2) {
         crm_trace("Assign %s after %s: active on a disallowed node",
                   instance1->id, instance2->id);
         return 1;
     }
 
     // Prefer instance with higher configured priority
     if (instance1->priority > instance2->priority) {
         crm_trace("Assign %s before %s: priority (%d > %d)",
                   instance1->id, instance2->id,
                   instance1->priority, instance2->priority);
         return -1;
 
     } else if (instance1->priority < instance2->priority) {
         crm_trace("Assign %s after %s: priority (%d < %d)",
                   instance1->id, instance2->id,
                   instance1->priority, instance2->priority);
         return 1;
     }
 
     // Prefer active instance
     if ((node1 == NULL) && (node2 == NULL)) {
         crm_trace("No assignment preference for %s vs. %s: inactive",
                   instance1->id, instance2->id);
         return 0;
 
     } else if (node1 == NULL) {
         crm_trace("Assign %s after %s: active", instance1->id, instance2->id);
         return 1;
 
     } else if (node2 == NULL) {
         crm_trace("Assign %s before %s: active", instance1->id, instance2->id);
         return -1;
     }
 
     // Prefer instance whose current node can run resources
     can1 = pcmk__node_available(node1, false, false);
     can2 = pcmk__node_available(node2, false, false);
     if (can1 && !can2) {
         crm_trace("Assign %s before %s: current node can run resources",
                   instance1->id, instance2->id);
         return -1;
 
     } else if (!can1 && can2) {
         crm_trace("Assign %s after %s: current node can't run resources",
                   instance1->id, instance2->id);
         return 1;
     }
 
     // Prefer instance whose parent is allowed to run on instance's current node
     node1 = pcmk__top_allowed_node(instance1, node1);
     node2 = pcmk__top_allowed_node(instance2, node2);
     if ((node1 == NULL) && (node2 == NULL)) {
         crm_trace("No assignment preference for %s vs. %s: "
                   "parent not allowed on either instance's current node",
                   instance1->id, instance2->id);
         return 0;
 
     } else if (node1 == NULL) {
         crm_trace("Assign %s after %s: parent not allowed on current node",
                   instance1->id, instance2->id);
         return 1;
 
     } else if (node2 == NULL) {
         crm_trace("Assign %s before %s: parent allowed on current node",
                   instance1->id, instance2->id);
         return -1;
     }
 
     // Prefer instance whose current node is running fewer other instances
     if (node1->count < node2->count) {
         crm_trace("Assign %s before %s: fewer active instances on current node",
                   instance1->id, instance2->id);
         return -1;
 
     } else if (node1->count > node2->count) {
         crm_trace("Assign %s after %s: more active instances on current node",
                   instance1->id, instance2->id);
         return 1;
     }
 
     // Prefer instance that isn't failed
     can1 = did_fail(instance1);
     can2 = did_fail(instance2);
     if (!can1 && can2) {
         crm_trace("Assign %s before %s: not failed",
                   instance1->id, instance2->id);
         return -1;
     } else if (can1 && !can2) {
         crm_trace("Assign %s after %s: failed",
                   instance1->id, instance2->id);
         return 1;
     }
 
     // Prefer instance with higher cumulative colocation score on current node
     rc = cmp_instance_by_colocation(instance1, instance2);
     if (rc != 0) {
         return rc;
     }
 
     // Prefer instance with lower instance number
     rc = pcmk__cmp_instance_number(instance1, instance2);
     if (rc < 0) {
         crm_trace("Assign %s before %s: instance number",
                   instance1->id, instance2->id);
     } else if (rc > 0) {
         crm_trace("Assign %s after %s: instance number",
                   instance1->id, instance2->id);
     } else {
         crm_trace("No assignment preference for %s vs. %s",
                   instance1->id, instance2->id);
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Increment the parent's instance count after assigning an instance
  *
  * An instance's parent tracks how many instances have been assigned to each
  * node via its pcmk_node_t:count member. After assigning an instance to a node,
  * find the corresponding node in the parent's allowed table and increment it.
  *
  * \param[in,out] instance     Instance whose parent to update
  * \param[in]     assigned_to  Node to which the instance was assigned
  */
 static void
 increment_parent_count(pcmk_resource_t *instance,
                        const pcmk_node_t *assigned_to)
 {
     pcmk_node_t *allowed = NULL;
 
     if (assigned_to == NULL) {
         return;
     }
     allowed = pcmk__top_allowed_node(instance, assigned_to);
 
     if (allowed == NULL) {
         /* The instance is allowed on the node, but its parent isn't. This
          * shouldn't be possible if the resource is managed, and we won't be
          * able to limit the number of instances assigned to the node.
          */
         CRM_LOG_ASSERT(!pcmk_is_set(instance->flags, pcmk_rsc_managed));
 
     } else {
         allowed->count++;
     }
 }
 
 /*!
  * \internal
  * \brief Assign an instance to a node
  *
  * \param[in,out] instance      Clone instance or bundle replica container
  * \param[in]     prefer        If not NULL, attempt early assignment to this
  *                              node, if still the best choice; otherwise,
  *                              perform final assignment
  * \param[in]     max_per_node  Assign at most this many instances to one node
  *
  * \return Node to which \p instance is assigned
  */
 static const pcmk_node_t *
 assign_instance(pcmk_resource_t *instance, const pcmk_node_t *prefer,
                 int max_per_node)
 {
     pcmk_node_t *chosen = NULL;
 
     pcmk__rsc_trace(instance, "Assigning %s (preferring %s)", instance->id,
                     ((prefer == NULL)? "no node" : prefer->details->uname));
 
     if (pcmk_is_set(instance->flags, pcmk_rsc_assigning)) {
         pcmk__rsc_debug(instance,
                         "Assignment loop detected involving %s colocations",
                         instance->id);
         return NULL;
     }
     ban_unavailable_allowed_nodes(instance, max_per_node);
 
     // Failed early assignments are reversible (stop_if_fail=false)
     chosen = instance->cmds->assign(instance, prefer, (prefer == NULL));
     increment_parent_count(instance, chosen);
     return chosen;
 }
 
 /*!
  * \internal
  * \brief Try to assign an instance to its current node early
  *
  * \param[in] rsc           Clone or bundle being assigned (for logs only)
  * \param[in] instance      Clone instance or bundle replica container
  * \param[in] current       Instance's current node
  * \param[in] max_per_node  Maximum number of instances per node
  * \param[in] available     Number of instances still available for assignment
  *
  * \return \c true if \p instance was successfully assigned to its current node,
  *         or \c false otherwise
  */
 static bool
 assign_instance_early(const pcmk_resource_t *rsc, pcmk_resource_t *instance,
                       const pcmk_node_t *current, int max_per_node,
                       int available)
 {
     const pcmk_node_t *chosen = NULL;
     int reserved = 0;
 
     pcmk_resource_t *parent = instance->parent;
     GHashTable *allowed_orig = NULL;
     GHashTable *allowed_orig_parent = parent->allowed_nodes;
     const pcmk_node_t *allowed_node = NULL;
 
     pcmk__rsc_trace(instance, "Trying to assign %s to its current node %s",
                     instance->id, pcmk__node_name(current));
 
     allowed_node = g_hash_table_lookup(instance->allowed_nodes,
                                        current->details->id);
     if (!pcmk__node_available(allowed_node, true, false)) {
         pcmk__rsc_info(instance,
                        "Not assigning %s to current node %s: unavailable",
                        instance->id, pcmk__node_name(current));
         return false;
     }
 
     /* On each iteration, if instance gets assigned to a node other than its
      * current one, we reserve one instance for the chosen node, unassign
      * instance, restore instance's original node tables, and try again. This
      * way, instances are proportionally assigned to nodes based on preferences,
      * but shuffling of specific instances is minimized. If a node will be
      * assigned instances at all, it preferentially receives instances that are
      * currently active there.
      *
      * parent->allowed_nodes tracks the number of instances assigned to each
      * node. If a node already has max_per_node instances assigned,
      * ban_unavailable_allowed_nodes() marks it as unavailable.
      *
      * In the end, we restore the original parent->allowed_nodes to undo the
      * changes to counts during tentative assignments. If we successfully
      * assigned instance to its current node, we increment that node's counter.
      */
 
     // Back up the allowed node tables of instance and its children recursively
     pcmk__copy_node_tables(instance, &allowed_orig);
 
     // Update instances-per-node counts in a scratch table
     parent->allowed_nodes = pcmk__copy_node_table(parent->allowed_nodes);
 
     while (reserved < available) {
         chosen = assign_instance(instance, current, max_per_node);
 
         if (pcmk__same_node(chosen, current)) {
             // Successfully assigned to current node
             break;
         }
 
         // Assignment updates scores, so restore to original state
         pcmk__rsc_debug(instance, "Rolling back node scores for %s",
                         instance->id);
         pcmk__restore_node_tables(instance, allowed_orig);
 
         if (chosen == NULL) {
             // Assignment failed, so give up
             pcmk__rsc_info(instance,
                            "Not assigning %s to current node %s: unavailable",
                            instance->id, pcmk__node_name(current));
             pcmk__set_rsc_flags(instance, pcmk_rsc_unassigned);
             break;
         }
 
         // We prefer more strongly to assign an instance to the chosen node
         pcmk__rsc_debug(instance,
                         "Not assigning %s to current node %s: %s is better",
                         instance->id, pcmk__node_name(current),
                         pcmk__node_name(chosen));
 
         // Reserve one instance for the chosen node and try again
         if (++reserved >= available) {
             pcmk__rsc_info(instance,
                            "Not assigning %s to current node %s: "
                            "other assignments are more important",
                            instance->id, pcmk__node_name(current));
 
         } else {
             pcmk__rsc_debug(instance,
                             "Reserved an instance of %s for %s. Retrying "
                             "assignment of %s to %s",
                             rsc->id, pcmk__node_name(chosen), instance->id,
                             pcmk__node_name(current));
         }
 
         // Clear this assignment (frees chosen); leave instance counts in parent
         pcmk__unassign_resource(instance);
         chosen = NULL;
     }
 
     g_hash_table_destroy(allowed_orig);
 
     // Restore original instances-per-node counts
     g_hash_table_destroy(parent->allowed_nodes);
     parent->allowed_nodes = allowed_orig_parent;
 
     if (chosen == NULL) {
         // Couldn't assign instance to current node
         return false;
     }
     pcmk__rsc_trace(instance, "Assigned %s to current node %s",
                     instance->id, pcmk__node_name(current));
     increment_parent_count(instance, chosen);
     return true;
 }
 
 /*!
  * \internal
  * \brief Reset the node counts of a resource's allowed nodes to zero
  *
  * \param[in,out] rsc  Resource to reset
  *
  * \return Number of nodes that are available to run resources
  */
 static unsigned int
 reset_allowed_node_counts(pcmk_resource_t *rsc)
 {
     unsigned int available_nodes = 0;
     pcmk_node_t *node = NULL;
     GHashTableIter iter;
 
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
         node->count = 0;
         if (pcmk__node_available(node, false, false)) {
             available_nodes++;
         }
     }
     return available_nodes;
 }
 
 /*!
  * \internal
  * \brief Check whether an instance has a preferred node
  *
  * \param[in] instance          Clone instance or bundle replica container
  * \param[in] optimal_per_node  Optimal number of instances per node
  *
  * \return Instance's current node if still available, otherwise NULL
  */
 static const pcmk_node_t *
 preferred_node(const pcmk_resource_t *instance, int optimal_per_node)
 {
     const pcmk_node_t *node = NULL;
     const pcmk_node_t *parent_node = NULL;
 
     // Check whether instance is active, healthy, and not yet assigned
     if ((instance->running_on == NULL)
         || !pcmk_is_set(instance->flags, pcmk_rsc_unassigned)
         || pcmk_is_set(instance->flags, pcmk_rsc_failed)) {
         return NULL;
     }
 
     // Check whether instance's current node can run resources
     node = pcmk__current_node(instance);
     if (!pcmk__node_available(node, true, false)) {
         pcmk__rsc_trace(instance, "Not assigning %s to %s early (unavailable)",
                         instance->id, pcmk__node_name(node));
         return NULL;
     }
 
     // Check whether node already has optimal number of instances assigned
     parent_node = pcmk__top_allowed_node(instance, node);
     if ((parent_node != NULL) && (parent_node->count >= optimal_per_node)) {
         pcmk__rsc_trace(instance,
                         "Not assigning %s to %s early "
                         "(optimal instances already assigned)",
                         instance->id, pcmk__node_name(node));
         return NULL;
     }
 
     return node;
 }
 
 /*!
  * \internal
  * \brief Assign collective instances to nodes
  *
  * \param[in,out] collective    Clone or bundle resource being assigned
  * \param[in,out] instances     List of clone instances or bundle containers
  * \param[in]     max_total     Maximum instances to assign in total
  * \param[in]     max_per_node  Maximum instances to assign to any one node
  */
 void
 pcmk__assign_instances(pcmk_resource_t *collective, GList *instances,
                        int max_total, int max_per_node)
 {
     // Reuse node count to track number of assigned instances
     unsigned int available_nodes = reset_allowed_node_counts(collective);
 
     int optimal_per_node = 0;
     int assigned = 0;
     GList *iter = NULL;
     pcmk_resource_t *instance = NULL;
     const pcmk_node_t *current = NULL;
 
     if (available_nodes > 0) {
         optimal_per_node = max_total / available_nodes;
     }
     if (optimal_per_node < 1) {
         optimal_per_node = 1;
     }
 
     pcmk__rsc_debug(collective,
                     "Assigning up to %d %s instance%s to up to %u node%s "
                     "(at most %d per host, %d optimal)",
                     max_total, collective->id, pcmk__plural_s(max_total),
                     available_nodes, pcmk__plural_s(available_nodes),
                     max_per_node, optimal_per_node);
 
     // Assign as many instances as possible to their current location
     for (iter = instances; (iter != NULL) && (assigned < max_total);
          iter = iter->next) {
         int available = max_total - assigned;
 
         instance = iter->data;
         if (!pcmk_is_set(instance->flags, pcmk_rsc_unassigned)) {
             continue;   // Already assigned
         }
 
         current = preferred_node(instance, optimal_per_node);
         if ((current != NULL)
             && assign_instance_early(collective, instance, current,
                                      max_per_node, available)) {
             assigned++;
         }
     }
 
     pcmk__rsc_trace(collective, "Assigned %d of %d instance%s to current node",
                     assigned, max_total, pcmk__plural_s(max_total));
 
     for (iter = instances; iter != NULL; iter = iter->next) {
         instance = (pcmk_resource_t *) iter->data;
 
         if (!pcmk_is_set(instance->flags, pcmk_rsc_unassigned)) {
             continue; // Already assigned
         }
 
         if (instance->running_on != NULL) {
             current = pcmk__current_node(instance);
             if (pcmk__top_allowed_node(instance, current) == NULL) {
                 const char *unmanaged = "";
 
                 if (!pcmk_is_set(instance->flags, pcmk_rsc_managed)) {
                     unmanaged = "Unmanaged resource ";
                 }
                 crm_notice("%s%s is running on %s which is no longer allowed",
                            unmanaged, instance->id, pcmk__node_name(current));
             }
         }
 
         if (assigned >= max_total) {
             pcmk__rsc_debug(collective,
                             "Not assigning %s because maximum %d instances "
                             "already assigned",
                             instance->id, max_total);
-            resource_location(instance, NULL, -INFINITY,
+            resource_location(instance, NULL, -PCMK_SCORE_INFINITY,
                               "collective_limit_reached", collective->cluster);
 
         } else if (assign_instance(instance, NULL, max_per_node) != NULL) {
             assigned++;
         }
     }
 
     pcmk__rsc_debug(collective, "Assigned %d of %d possible instance%s of %s",
                     assigned, max_total, pcmk__plural_s(max_total),
                     collective->id);
 }
 
 enum instance_state {
     instance_starting   = (1 << 0),
     instance_stopping   = (1 << 1),
 
     /* This indicates that some instance is restarting. It's not the same as
      * instance_starting|instance_stopping, which would indicate that some
      * instance is starting, and some instance (not necessarily the same one) is
      * stopping.
      */
     instance_restarting = (1 << 2),
 
     instance_active     = (1 << 3),
 
     instance_all        = instance_starting|instance_stopping
                           |instance_restarting|instance_active,
 };
 
 /*!
  * \internal
  * \brief Check whether an instance is active, starting, and/or stopping
  *
  * \param[in]     instance  Clone instance or bundle replica container
  * \param[in,out] state     Whether any instance is starting, stopping, etc.
  */
 static void
 check_instance_state(const pcmk_resource_t *instance, uint32_t *state)
 {
     const GList *iter = NULL;
     uint32_t instance_state = 0; // State of just this instance
 
     // No need to check further if all conditions have already been detected
     if (pcmk_all_flags_set(*state, instance_all)) {
         return;
     }
 
     // If instance is a collective (a cloned group), check its children instead
     if (instance->variant > pcmk_rsc_variant_primitive) {
         for (iter = instance->children;
              (iter != NULL) && !pcmk_all_flags_set(*state, instance_all);
              iter = iter->next) {
             check_instance_state((const pcmk_resource_t *) iter->data, state);
         }
         return;
     }
 
     // If we get here, instance is a primitive
 
     if (instance->running_on != NULL) {
         instance_state |= instance_active;
     }
 
     // Check each of the instance's actions for runnable start or stop
     for (iter = instance->actions;
          (iter != NULL) && !pcmk_all_flags_set(instance_state,
                                                instance_starting
                                                |instance_stopping);
          iter = iter->next) {
 
         const pcmk_action_t *action = (const pcmk_action_t *) iter->data;
         const bool optional = pcmk_is_set(action->flags, pcmk_action_optional);
 
         if (pcmk__str_eq(PCMK_ACTION_START, action->task, pcmk__str_none)) {
             if (!optional
                 && pcmk_is_set(action->flags, pcmk_action_runnable)) {
 
                 pcmk__rsc_trace(instance, "Instance is starting due to %s",
                                 action->uuid);
                 instance_state |= instance_starting;
             } else {
                 pcmk__rsc_trace(instance, "%s doesn't affect %s state (%s)",
                                 action->uuid, instance->id,
                                 (optional? "optional" : "unrunnable"));
             }
 
         } else if (pcmk__str_eq(PCMK_ACTION_STOP, action->task,
                                 pcmk__str_none)) {
             /* Only stop actions can be pseudo-actions for primitives. That
              * indicates that the node they are on is being fenced, so the stop
              * is implied rather than actually executed.
              */
             if (!optional
                 && pcmk_any_flags_set(action->flags, pcmk_action_pseudo
                                                      |pcmk_action_runnable)) {
                 pcmk__rsc_trace(instance, "Instance is stopping due to %s",
                                 action->uuid);
                 instance_state |= instance_stopping;
             } else {
                 pcmk__rsc_trace(instance, "%s doesn't affect %s state (%s)",
                                 action->uuid, instance->id,
                                 (optional? "optional" : "unrunnable"));
             }
         }
     }
 
     if (pcmk_all_flags_set(instance_state,
                            instance_starting|instance_stopping)) {
         instance_state |= instance_restarting;
     }
     *state |= instance_state;
 }
 
 /*!
  * \internal
  * \brief Create actions for collective resource instances
  *
  * \param[in,out] collective    Clone or bundle resource to create actions for
  * \param[in,out] instances     List of clone instances or bundle containers
  */
 void
 pcmk__create_instance_actions(pcmk_resource_t *collective, GList *instances)
 {
     uint32_t state = 0;
 
     pcmk_action_t *stop = NULL;
     pcmk_action_t *stopped = NULL;
 
     pcmk_action_t *start = NULL;
     pcmk_action_t *started = NULL;
 
     pcmk__rsc_trace(collective, "Creating collective instance actions for %s",
                     collective->id);
 
     // Create actions for each instance appropriate to its variant
     for (GList *iter = instances; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         instance->cmds->create_actions(instance);
         check_instance_state(instance, &state);
     }
 
     // Create pseudo-actions for rsc start and started
     start = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_START,
                                       !pcmk_is_set(state, instance_starting),
                                       true);
     started = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_RUNNING,
                                         !pcmk_is_set(state, instance_starting),
                                         false);
-    started->priority = INFINITY;
+    started->priority = PCMK_SCORE_INFINITY;
     if (pcmk_any_flags_set(state, instance_active|instance_starting)) {
         pcmk__set_action_flags(started, pcmk_action_runnable);
     }
 
     // Create pseudo-actions for rsc stop and stopped
     stop = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_STOP,
                                      !pcmk_is_set(state, instance_stopping),
                                      true);
     stopped = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_STOPPED,
                                         !pcmk_is_set(state, instance_stopping),
                                         true);
-    stopped->priority = INFINITY;
+    stopped->priority = PCMK_SCORE_INFINITY;
     if (!pcmk_is_set(state, instance_restarting)) {
         pcmk__set_action_flags(stop, pcmk_action_migratable);
     }
 
     if (collective->variant == pcmk_rsc_variant_clone) {
         pe__create_clone_notif_pseudo_ops(collective, start, started, stop,
                                           stopped);
     }
 }
 
 /*!
  * \internal
  * \brief Get a list of clone instances or bundle replica containers
  *
  * \param[in] rsc  Clone or bundle resource
  *
  * \return Clone instances if \p rsc is a clone, or a newly created list of
  *         \p rsc's replica containers if \p rsc is a bundle
  * \note The caller must call free_instance_list() on the result when the list
  *       is no longer needed.
  */
 static inline GList *
 get_instance_list(const pcmk_resource_t *rsc)
 {
     if (rsc->variant == pcmk_rsc_variant_bundle) {
         return pe__bundle_containers(rsc);
     } else {
         return rsc->children;
     }
 }
 
 /*!
  * \internal
  * \brief Free any memory created by get_instance_list()
  *
  * \param[in]     rsc   Clone or bundle resource passed to get_instance_list()
  * \param[in,out] list  Return value of get_instance_list() for \p rsc
  */
 static inline void
 free_instance_list(const pcmk_resource_t *rsc, GList *list)
 {
     if (list != rsc->children) {
         g_list_free(list);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether an instance is compatible with a role and node
  *
  * \param[in] instance  Clone instance or bundle replica container
  * \param[in] node      Instance must match this node
  * \param[in] role      If not pcmk_role_unknown, instance must match this role
  * \param[in] current   If true, compare instance's original node and role,
  *                      otherwise compare assigned next node and role
  *
  * \return true if \p instance is compatible with \p node and \p role,
  *         otherwise false
  */
 bool
 pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node,
                        enum rsc_role_e role, bool current)
 {
     pcmk_node_t *instance_node = NULL;
 
     CRM_CHECK((instance != NULL) && (node != NULL), return false);
 
     if ((role != pcmk_role_unknown)
         && (role != instance->fns->state(instance, current))) {
         pcmk__rsc_trace(instance,
                         "%s is not a compatible instance (role is not %s)",
                         instance->id, pcmk_role_text(role));
         return false;
     }
 
     if (!is_set_recursive(instance, pcmk_rsc_blocked, true)) {
         // We only want instances that haven't failed
         instance_node = instance->fns->location(instance, NULL, current);
     }
 
     if (instance_node == NULL) {
         pcmk__rsc_trace(instance,
                         "%s is not a compatible instance "
                         "(not assigned to a node)",
                         instance->id);
         return false;
     }
 
     if (!pcmk__same_node(instance_node, node)) {
         pcmk__rsc_trace(instance,
                         "%s is not a compatible instance "
                         "(assigned to %s not %s)",
                         instance->id, pcmk__node_name(instance_node),
                         pcmk__node_name(node));
         return false;
     }
 
     return true;
 }
 
 #define display_role(r) \
     (((r) == pcmk_role_unknown)? "matching" : pcmk_role_text(r))
 
 /*!
  * \internal
  * \brief Find an instance that matches a given resource by node and role
  *
  * \param[in] match_rsc  Resource that instance must match (for logging only)
  * \param[in] rsc        Clone or bundle resource to check for matching instance
  * \param[in] node       Instance must match this node
  * \param[in] role       If not pcmk_role_unknown, instance must match this role
  * \param[in] current    If true, compare instance's original node and role,
  *                       otherwise compare assigned next node and role
  *
  * \return \p rsc instance matching \p node and \p role if any, otherwise NULL
  */
 static pcmk_resource_t *
 find_compatible_instance_on_node(const pcmk_resource_t *match_rsc,
                                  const pcmk_resource_t *rsc,
                                  const pcmk_node_t *node, enum rsc_role_e role,
                                  bool current)
 {
     GList *instances = NULL;
 
     instances = get_instance_list(rsc);
     for (GList *iter = instances; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         if (pcmk__instance_matches(instance, node, role, current)) {
             pcmk__rsc_trace(match_rsc,
                             "Found %s %s instance %s compatible with %s on %s",
                             display_role(role), rsc->id, instance->id,
                             match_rsc->id, pcmk__node_name(node));
             free_instance_list(rsc, instances); // Only frees list, not contents
             return instance;
         }
     }
     free_instance_list(rsc, instances);
 
     pcmk__rsc_trace(match_rsc,
                     "No %s %s instance found compatible with %s on %s",
                     display_role(role), rsc->id, match_rsc->id,
                     pcmk__node_name(node));
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Find a clone instance or bundle container compatible with a resource
  *
  * \param[in] match_rsc  Resource that instance must match
  * \param[in] rsc        Clone or bundle resource to check for matching instance
  * \param[in] role       If not pcmk_role_unknown, instance must match this role
  * \param[in] current    If true, compare instance's original node and role,
  *                       otherwise compare assigned next node and role
  *
  * \return Compatible (by \p role and \p match_rsc location) instance of \p rsc
  *         if any, otherwise NULL
  */
 pcmk_resource_t *
 pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc,
                                const pcmk_resource_t *rsc, enum rsc_role_e role,
                                bool current)
 {
     pcmk_resource_t *instance = NULL;
     GList *nodes = NULL;
     const pcmk_node_t *node = NULL;
 
     // If match_rsc has a node, check only that node
     node = match_rsc->fns->location(match_rsc, NULL, current);
     if (node != NULL) {
         return find_compatible_instance_on_node(match_rsc, rsc, node, role,
                                                 current);
     }
 
     // Otherwise check for an instance matching any of match_rsc's allowed nodes
     nodes = pcmk__sort_nodes(g_hash_table_get_values(match_rsc->allowed_nodes),
                              NULL);
     for (GList *iter = nodes; (iter != NULL) && (instance == NULL);
          iter = iter->next) {
         instance = find_compatible_instance_on_node(match_rsc, rsc,
                                                     (pcmk_node_t *) iter->data,
                                                     role, current);
     }
 
     if (instance == NULL) {
         pcmk__rsc_debug(rsc, "No %s instance found compatible with %s",
                         rsc->id, match_rsc->id);
     }
     g_list_free(nodes);
     return instance;
 }
 
 /*!
  * \internal
  * \brief Unassign an instance if mandatory ordering has no interleave match
  *
  * \param[in]     first          'First' action in an ordering
  * \param[in]     then           'Then' action in an ordering
  * \param[in,out] then_instance  'Then' instance that has no interleave match
  * \param[in]     type           Group of enum pcmk__action_relation_flags
  * \param[in]     current        If true, "then" action is stopped or demoted
  *
  * \return true if \p then_instance was unassigned, otherwise false
  */
 static bool
 unassign_if_mandatory(const pcmk_action_t *first, const pcmk_action_t *then,
                       pcmk_resource_t *then_instance, uint32_t type,
                       bool current)
 {
     // Allow "then" instance to go down even without an interleave match
     if (current) {
         pcmk__rsc_trace(then->rsc,
                         "%s has no instance to order before stopping "
                         "or demoting %s",
                         first->rsc->id, then_instance->id);
 
     /* If the "first" action must be runnable, but there is no "first"
      * instance, the "then" instance must not be allowed to come up.
      */
     } else if (pcmk_any_flags_set(type, pcmk__ar_unrunnable_first_blocks
                                         |pcmk__ar_first_implies_then)) {
         pcmk__rsc_info(then->rsc,
                        "Inhibiting %s from being active "
                        "because there is no %s instance to interleave",
                        then_instance->id, first->rsc->id);
         return pcmk__assign_resource(then_instance, NULL, true, true);
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Find first matching action for a clone instance or bundle container
  *
  * \param[in] action       Action in an interleaved ordering
  * \param[in] instance     Clone instance or bundle container being interleaved
  * \param[in] action_name  Action to look for
  * \param[in] node         If not NULL, require action to be on this node
  * \param[in] for_first    If true, \p instance is the 'first' resource in the
  *                         ordering, otherwise it is the 'then' resource
  *
  * \return First action for \p instance (or in some cases if \p instance is a
  *         bundle container, its containerized resource) that matches
  *         \p action_name and \p node if any, otherwise NULL
  */
 static pcmk_action_t *
 find_instance_action(const pcmk_action_t *action, const pcmk_resource_t *instance,
                      const char *action_name, const pcmk_node_t *node,
                      bool for_first)
 {
     const pcmk_resource_t *rsc = NULL;
     pcmk_action_t *matching_action = NULL;
 
     /* If instance is a bundle container, sometimes we should interleave the
      * action for the container itself, and sometimes for the containerized
      * resource.
      *
      * For example, given "start bundle A then bundle B", B likely requires the
      * service inside A's container to be active, rather than just the
      * container, so we should interleave the action for A's containerized
      * resource. On the other hand, it's possible B's container itself requires
      * something from A, so we should interleave the action for B's container.
      *
      * Essentially, for 'first', we should use the containerized resource for
      * everything except stop, and for 'then', we should use the container for
      * everything except promote and demote (which can only be performed on the
      * containerized resource).
      */
     if ((for_first && !pcmk__str_any_of(action->task, PCMK_ACTION_STOP,
                                         PCMK_ACTION_STOPPED, NULL))
 
         || (!for_first && pcmk__str_any_of(action->task, PCMK_ACTION_PROMOTE,
                                            PCMK_ACTION_PROMOTED,
                                            PCMK_ACTION_DEMOTE,
                                            PCMK_ACTION_DEMOTED, NULL))) {
 
         rsc = pe__get_rsc_in_container(instance);
     }
     if (rsc == NULL) {
         rsc = instance; // No containerized resource, use instance itself
     } else {
         node = NULL; // Containerized actions are on bundle-created guest
     }
 
     matching_action = find_first_action(rsc->actions, NULL, action_name, node);
     if (matching_action != NULL) {
         return matching_action;
     }
 
     if (pcmk_is_set(instance->flags, pcmk_rsc_removed)
         || pcmk__str_any_of(action_name, PCMK_ACTION_STOP, PCMK_ACTION_DEMOTE,
                             NULL)) {
         crm_trace("No %s action found for %s%s",
                   action_name,
                   pcmk_is_set(instance->flags, pcmk_rsc_removed)? "orphan " : "",
                   instance->id);
     } else {
         crm_err("No %s action found for %s to interleave (bug?)",
                 action_name, instance->id);
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get the original action name of a bundle or clone action
  *
  * Given an action for a bundle or clone, get the original action name,
  * mapping notify to the action being notified, and if the instances are
  * primitives, mapping completion actions to the action that was completed
  * (for example, stopped to stop).
  *
  * \param[in] action  Clone or bundle action to check
  *
  * \return Original action name for \p action
  */
 static const char *
 orig_action_name(const pcmk_action_t *action)
 {
     // Any instance will do
     const pcmk_resource_t *instance = action->rsc->children->data;
 
     char *action_type = NULL;
     const char *action_name = action->task;
     enum action_tasks orig_task = pcmk_action_unspecified;
 
     if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
                              PCMK_ACTION_NOTIFIED, NULL)) {
         // action->uuid is RSC_(confirmed-){pre,post}_notify_ACTION_INTERVAL
         CRM_CHECK(parse_op_key(action->uuid, NULL, &action_type, NULL),
                   return pcmk_action_text(pcmk_action_unspecified));
         action_name = strstr(action_type, "_notify_");
         CRM_CHECK(action_name != NULL,
                   return pcmk_action_text(pcmk_action_unspecified));
         action_name += strlen("_notify_");
     }
     orig_task = get_complex_task(instance, action_name);
     free(action_type);
     return pcmk_action_text(orig_task);
 }
 
 /*!
  * \internal
  * \brief Update two interleaved actions according to an ordering between them
  *
  * Given information about an ordering of two interleaved actions, update the
  * actions' flags (and runnable_before members if appropriate) as appropriate
  * for the ordering. Effects may cascade to other orderings involving the
  * actions as well.
  *
  * \param[in,out] first     'First' action in an ordering
  * \param[in,out] then      'Then' action in an ordering
  * \param[in]     node      If not NULL, limit scope of ordering to this node
  * \param[in]     filter    Action flags to limit scope of certain updates (may
  *                          include pcmk_action_optional to affect only
  *                          mandatory actions, and pcmk_action_runnable to
  *                          affect only runnable actions)
  * \param[in]     type      Group of enum pcmk__action_relation_flags to apply
  *
  * \return Group of enum pcmk__updated flags indicating what was updated
  */
 static uint32_t
 update_interleaved_actions(pcmk_action_t *first, pcmk_action_t *then,
                            const pcmk_node_t *node, uint32_t filter,
                            uint32_t type)
 {
     GList *instances = NULL;
     uint32_t changed = pcmk__updated_none;
     const char *orig_first_task = orig_action_name(first);
 
     // Stops and demotes must be interleaved with instance on current node
     bool current = pcmk__ends_with(first->uuid, "_" PCMK_ACTION_STOPPED "_0")
                    || pcmk__ends_with(first->uuid,
                                       "_" PCMK_ACTION_DEMOTED "_0");
 
     // Update the specified actions for each "then" instance individually
     instances = get_instance_list(then->rsc);
     for (GList *iter = instances; iter != NULL; iter = iter->next) {
         pcmk_resource_t *first_instance = NULL;
         pcmk_resource_t *then_instance = iter->data;
 
         pcmk_action_t *first_action = NULL;
         pcmk_action_t *then_action = NULL;
 
         // Find a "first" instance to interleave with this "then" instance
         first_instance = pcmk__find_compatible_instance(then_instance,
                                                         first->rsc,
                                                         pcmk_role_unknown,
                                                         current);
 
         if (first_instance == NULL) { // No instance can be interleaved
             if (unassign_if_mandatory(first, then, then_instance, type,
                                       current)) {
                 pcmk__set_updated_flags(changed, first, pcmk__updated_then);
             }
             continue;
         }
 
         first_action = find_instance_action(first, first_instance,
                                             orig_first_task, node, true);
         if (first_action == NULL) {
             continue;
         }
 
         then_action = find_instance_action(then, then_instance, then->task,
                                            node, false);
         if (then_action == NULL) {
             continue;
         }
 
         if (order_actions(first_action, then_action, type)) {
             pcmk__set_updated_flags(changed, first,
                                     pcmk__updated_first|pcmk__updated_then);
         }
 
         changed |= then_instance->cmds->update_ordered_actions(
             first_action, then_action, node,
             first_instance->cmds->action_flags(first_action, node), filter,
             type, then->rsc->cluster);
     }
     free_instance_list(then->rsc, instances);
     return changed;
 }
 
 /*!
  * \internal
  * \brief Check whether two actions in an ordering can be interleaved
  *
  * \param[in] first  'First' action in the ordering
  * \param[in] then   'Then' action in the ordering
  *
  * \return true if \p first and \p then can be interleaved, otherwise false
  */
 static bool
 can_interleave_actions(const pcmk_action_t *first, const pcmk_action_t *then)
 {
     bool interleave = false;
     pcmk_resource_t *rsc = NULL;
 
     if ((first->rsc == NULL) || (then->rsc == NULL)) {
         crm_trace("Not interleaving %s with %s: not resource actions",
                   first->uuid, then->uuid);
         return false;
     }
 
     if (first->rsc == then->rsc) {
         crm_trace("Not interleaving %s with %s: same resource",
                   first->uuid, then->uuid);
         return false;
     }
 
     if ((first->rsc->variant < pcmk_rsc_variant_clone)
         || (then->rsc->variant < pcmk_rsc_variant_clone)) {
         crm_trace("Not interleaving %s with %s: not clones or bundles",
                   first->uuid, then->uuid);
         return false;
     }
 
     if (pcmk__ends_with(then->uuid, "_stop_0")
         || pcmk__ends_with(then->uuid, "_demote_0")) {
         rsc = first->rsc;
     } else {
         rsc = then->rsc;
     }
 
     interleave = crm_is_true(g_hash_table_lookup(rsc->meta,
                                                  PCMK_META_INTERLEAVE));
     pcmk__rsc_trace(rsc, "'%s then %s' will %sbe interleaved (based on %s)",
                     first->uuid, then->uuid, (interleave? "" : "not "),
                     rsc->id);
     return interleave;
 }
 
 /*!
  * \internal
  * \brief Update non-interleaved instance actions according to an ordering
  *
  * Given information about an ordering of two non-interleaved actions, update
  * the actions' flags (and runnable_before members if appropriate) as
  * appropriate for the ordering. Effects may cascade to other orderings
  * involving the actions as well.
  *
  * \param[in,out] instance  Clone instance or bundle container
  * \param[in,out] first     "First" action in ordering
  * \param[in]     then      "Then" action in ordering (for \p instance's parent)
  * \param[in]     node      If not NULL, limit scope of ordering to this node
  * \param[in]     flags     Action flags for \p first for ordering purposes
  * \param[in]     filter    Action flags to limit scope of certain updates (may
  *                          include pcmk_action_optional to affect only
  *                          mandatory actions, and pcmk_action_runnable to
  *                          affect only runnable actions)
  * \param[in]     type      Group of enum pcmk__action_relation_flags to apply
  *
  * \return Group of enum pcmk__updated flags indicating what was updated
  */
 static uint32_t
 update_noninterleaved_actions(pcmk_resource_t *instance, pcmk_action_t *first,
                               const pcmk_action_t *then, const pcmk_node_t *node,
                               uint32_t flags, uint32_t filter, uint32_t type)
 {
     pcmk_action_t *instance_action = NULL;
     uint32_t instance_flags = 0;
     uint32_t changed = pcmk__updated_none;
 
     // Check whether instance has an equivalent of "then" action
     instance_action = find_first_action(instance->actions, NULL, then->task,
                                         node);
     if (instance_action == NULL) {
         return changed;
     }
 
     // Check whether action is runnable
     instance_flags = instance->cmds->action_flags(instance_action, node);
     if (!pcmk_is_set(instance_flags, pcmk_action_runnable)) {
         return changed;
     }
 
     // If so, update actions for the instance
     changed = instance->cmds->update_ordered_actions(first, instance_action,
                                                      node, flags, filter, type,
                                                      instance->cluster);
 
     // Propagate any changes to later actions
     if (pcmk_is_set(changed, pcmk__updated_then)) {
         for (GList *after_iter = instance_action->actions_after;
              after_iter != NULL; after_iter = after_iter->next) {
             pcmk__related_action_t *after = after_iter->data;
 
             pcmk__update_action_for_orderings(after->action, instance->cluster);
         }
     }
 
     return changed;
 }
 
 /*!
  * \internal
  * \brief Update two actions according to an ordering between them
  *
  * Given information about an ordering of two clone or bundle actions, update
  * the actions' flags (and runnable_before members if appropriate) as
  * appropriate for the ordering. Effects may cascade to other orderings
  * involving the actions as well.
  *
  * \param[in,out] first      'First' action in an ordering
  * \param[in,out] then       'Then' action in an ordering
  * \param[in]     node       If not NULL, limit scope of ordering to this node
  *                           (only used when interleaving instances)
  * \param[in]     flags      Action flags for \p first for ordering purposes
  * \param[in]     filter     Action flags to limit scope of certain updates (may
  *                           include pcmk_action_optional to affect only
  *                           mandatory actions, and pcmk_action_runnable to
  *                           affect only runnable actions)
  * \param[in]     type       Group of enum pcmk__action_relation_flags to apply
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Group of enum pcmk__updated flags indicating what was updated
  */
 uint32_t
 pcmk__instance_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
                                       const pcmk_node_t *node, uint32_t flags,
                                       uint32_t filter, uint32_t type,
                                       pcmk_scheduler_t *scheduler)
 {
     CRM_ASSERT((first != NULL) && (then != NULL) && (scheduler != NULL));
 
     if (then->rsc == NULL) {
         return pcmk__updated_none;
 
     } else if (can_interleave_actions(first, then)) {
         return update_interleaved_actions(first, then, node, filter, type);
 
     } else {
         uint32_t changed = pcmk__updated_none;
         GList *instances = get_instance_list(then->rsc);
 
         // Update actions for the clone or bundle resource itself
         changed |= pcmk__update_ordered_actions(first, then, node, flags,
                                                 filter, type, scheduler);
 
         // Update the 'then' clone instances or bundle containers individually
         for (GList *iter = instances; iter != NULL; iter = iter->next) {
             pcmk_resource_t *instance = iter->data;
 
             changed |= update_noninterleaved_actions(instance, first, then,
                                                      node, flags, filter, type);
         }
         free_instance_list(then->rsc, instances);
         return changed;
     }
 }
 
 #define pe__clear_action_summary_flags(flags, action, flag) do {        \
         flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE,     \
                                      "Action summary", action->rsc->id, \
                                      flags, flag, #flag);               \
     } while (0)
 
 /*!
  * \internal
  * \brief Return action flags for a given clone or bundle action
  *
  * \param[in,out] action     Action for a clone or bundle
  * \param[in]     instances  Clone instances or bundle containers
  * \param[in]     node       If not NULL, limit effects to this node
  *
  * \return Flags appropriate to \p action on \p node
  */
 uint32_t
 pcmk__collective_action_flags(pcmk_action_t *action, const GList *instances,
                               const pcmk_node_t *node)
 {
     bool any_runnable = false;
     const char *action_name = orig_action_name(action);
 
     // Set original assumptions (optional and runnable may be cleared below)
     uint32_t flags = pcmk_action_optional
                      |pcmk_action_runnable
                      |pcmk_action_pseudo;
 
     for (const GList *iter = instances; iter != NULL; iter = iter->next) {
         const pcmk_resource_t *instance = iter->data;
         const pcmk_node_t *instance_node = NULL;
         pcmk_action_t *instance_action = NULL;
         uint32_t instance_flags;
 
         // Node is relevant only to primitive instances
         if (instance->variant == pcmk_rsc_variant_primitive) {
             instance_node = node;
         }
 
         instance_action = find_first_action(instance->actions, NULL,
                                             action_name, instance_node);
         if (instance_action == NULL) {
             pcmk__rsc_trace(action->rsc, "%s has no %s action on %s",
                             instance->id, action_name, pcmk__node_name(node));
             continue;
         }
 
         pcmk__rsc_trace(action->rsc, "%s has %s for %s on %s",
                         instance->id, instance_action->uuid, action_name,
                         pcmk__node_name(node));
 
         instance_flags = instance->cmds->action_flags(instance_action, node);
 
         // If any instance action is mandatory, so is the collective action
         if (pcmk_is_set(flags, pcmk_action_optional)
             && !pcmk_is_set(instance_flags, pcmk_action_optional)) {
             pcmk__rsc_trace(instance, "%s is mandatory because %s is",
                             action->uuid, instance_action->uuid);
             pe__clear_action_summary_flags(flags, action,
                                            pcmk_action_optional);
             pcmk__clear_action_flags(action, pcmk_action_optional);
         }
 
         // If any instance action is runnable, so is the collective action
         if (pcmk_is_set(instance_flags, pcmk_action_runnable)) {
             any_runnable = true;
         }
     }
 
     if (!any_runnable) {
         pcmk__rsc_trace(action->rsc,
                         "%s is not runnable because no instance can run %s",
                         action->uuid, action_name);
         pe__clear_action_summary_flags(flags, action, pcmk_action_runnable);
         if (node == NULL) {
             pcmk__clear_action_flags(action, pcmk_action_runnable);
         }
     }
 
     return flags;
 }
diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c
index 7db4182055..dd9884b6ac 100644
--- a/lib/pacemaker/pcmk_sched_location.c
+++ b/lib/pacemaker/pcmk_sched_location.c
@@ -1,731 +1,731 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/rules.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 static int
 get_node_score(const char *rule, const char *score, bool raw,
                pcmk_node_t *node, pcmk_resource_t *rsc)
 {
     int score_f = 0;
 
     if (score == NULL) {
         pcmk__config_warn("Rule %s: no score specified (assuming 0)", rule);
 
     } else if (raw) {
         score_f = char2score(score);
 
     } else {
         const char *target = NULL;
         const char *attr_score = NULL;
 
         target = g_hash_table_lookup(rsc->meta,
                                      PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
 
         attr_score = pcmk__node_attr(node, score, target,
                                      pcmk__rsc_node_current);
         if (attr_score == NULL) {
             crm_debug("Rule %s: %s did not have a value for %s",
                       rule, pcmk__node_name(node), score);
-            score_f = -INFINITY;
+            score_f = -PCMK_SCORE_INFINITY;
 
         } else {
             crm_debug("Rule %s: %s had value %s for %s",
                       rule, pcmk__node_name(node), attr_score, score);
             score_f = char2score(attr_score);
         }
     }
     return score_f;
 }
 
 /*!
  * \internal
  * \brief Parse a role configuration for a location constraint
  *
  * \param[in]  role_spec  Role specification
  * \param[out] role       Where to store parsed role
  *
  * \return true if role specification is valid, otherwise false
  */
 static bool
 parse_location_role(const char *role_spec, enum rsc_role_e *role)
 {
     if (role_spec == NULL) {
         *role = pcmk_role_unknown;
         return true;
     }
 
     *role = pcmk_parse_role(role_spec);
     switch (*role) {
         case pcmk_role_unknown:
             return false;
 
         case pcmk_role_started:
         case pcmk_role_unpromoted:
             /* Any promotable clone instance cannot be promoted without being in
              * the unpromoted role first. Therefore, any constraint for the
              * started or unpromoted role applies to every role.
              */
             *role = pcmk_role_unknown;
             break;
 
         default:
             break;
     }
     return true;
 }
 
 /*!
  * \internal
  * \brief Generate a location constraint from a rule
  *
  * \param[in,out] rsc            Resource that constraint is for
  * \param[in]     rule_xml       Rule XML (sub-element of location constraint)
  * \param[in]     discovery      Value of \c PCMK_XA_RESOURCE_DISCOVERY for
  *                               constraint
  * \param[out]    next_change    Where to set when rule evaluation will change
  * \param[in]     re_match_data  Regular expression submatches
  *
  * \return New location constraint if rule is valid, otherwise NULL
  */
 static pcmk__location_t *
 generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml,
                        const char *discovery, crm_time_t *next_change,
                        pe_re_match_data_t *re_match_data)
 {
     const char *rule_id = NULL;
     const char *score = NULL;
     const char *boolean = NULL;
     const char *role_spec = NULL;
 
     GList *iter = NULL;
     GList *nodes = NULL;
 
     bool do_and = true;
     bool accept = true;
     bool raw_score = true;
     bool score_allocated = false;
 
     pcmk__location_t *location_rule = NULL;
     enum rsc_role_e role = pcmk_role_unknown;
 
     rule_xml = expand_idref(rule_xml, rsc->cluster->input);
     if (rule_xml == NULL) {
         return NULL; // Error already logged
     }
 
     rule_id = crm_element_value(rule_xml, PCMK_XA_ID);
     boolean = crm_element_value(rule_xml, PCMK_XA_BOOLEAN_OP);
     role_spec = crm_element_value(rule_xml, PCMK_XA_ROLE);
 
     if (parse_location_role(role_spec, &role)) {
         crm_trace("Setting rule %s role filter to %s", rule_id, role_spec);
     } else {
         pcmk__config_err("Ignoring rule %s: Invalid " PCMK_XA_ROLE " '%s'",
                          rule_id, role_spec);
         return NULL;
     }
 
     crm_trace("Processing location constraint rule %s", rule_id);
 
     score = crm_element_value(rule_xml, PCMK_XA_SCORE);
     if (score == NULL) {
         score = crm_element_value(rule_xml, PCMK_XA_SCORE_ATTRIBUTE);
         if (score != NULL) {
             raw_score = false;
         }
     }
 
     if (pcmk__str_eq(boolean, PCMK_VALUE_OR, pcmk__str_casei)) {
         do_and = false;
 
     } else if (!pcmk__str_eq(boolean, PCMK_VALUE_AND,
                              pcmk__str_null_matches|pcmk__str_casei)) {
         pcmk__config_warn("Location constraint rule %s has invalid "
                           PCMK_XA_BOOLEAN_OP " value '%s', using default "
                           "('" PCMK_VALUE_AND "')",
                           rule_id, boolean);
     }
 
     location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL);
     if (location_rule == NULL) {
         return NULL; // Error already logged
     }
     location_rule->role_filter = role;
 
     if ((re_match_data != NULL) && (re_match_data->nregs > 0)
         && (re_match_data->pmatch[0].rm_so != -1) && !raw_score) {
 
         char *result = pe_expand_re_matches(score, re_match_data);
 
         if (result != NULL) {
             score = result;
             score_allocated = true;
         }
     }
 
     if (do_and) {
         nodes = pcmk__copy_node_list(rsc->cluster->nodes, true);
         for (iter = nodes; iter != NULL; iter = iter->next) {
             pcmk_node_t *node = iter->data;
 
             node->weight = get_node_score(rule_id, score, raw_score, node, rsc);
         }
     }
 
     for (iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) {
         int score_f = 0;
         pcmk_node_t *node = iter->data;
         pe_match_data_t match_data = {
             .re = re_match_data,
             .params = pe_rsc_params(rsc, node, rsc->cluster),
             .meta = rsc->meta,
         };
 
         accept = pe_test_rule(rule_xml, node->details->attrs, pcmk_role_unknown,
                               rsc->cluster->now, next_change, &match_data);
 
         crm_trace("Rule %s %s on %s",
                   pcmk__xe_id(rule_xml), (accept? "passed" : "failed"),
                   pcmk__node_name(node));
 
         score_f = get_node_score(rule_id, score, raw_score, node, rsc);
 
         if (accept) {
             pcmk_node_t *local = pe_find_node_id(nodes, node->details->id);
 
             if ((local == NULL) && do_and) {
                 continue;
 
             } else if (local == NULL) {
                 local = pe__copy_node(node);
                 nodes = g_list_append(nodes, local);
             }
 
             if (!do_and) {
                 local->weight = pcmk__add_scores(local->weight, score_f);
             }
             crm_trace("%s has score %s after %s", pcmk__node_name(node),
                       pcmk_readable_score(local->weight), rule_id);
 
         } else if (do_and && !accept) {
             // Remove it
             pcmk_node_t *delete = pe_find_node_id(nodes, node->details->id);
 
             if (delete != NULL) {
                 nodes = g_list_remove(nodes, delete);
                 crm_trace("%s did not match", pcmk__node_name(node));
             }
             free(delete);
         }
     }
 
     if (score_allocated) {
         free((char *)score);
     }
 
     location_rule->nodes = nodes;
     if (location_rule->nodes == NULL) {
         crm_trace("No matching nodes for location constraint rule %s", rule_id);
         return NULL;
     } else {
         crm_trace("Location constraint rule %s matched %d nodes",
                   rule_id, g_list_length(location_rule->nodes));
     }
     return location_rule;
 }
 
 static void
 unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc,
                     const char *role_spec, const char *score,
                     pe_re_match_data_t *re_match_data)
 {
     const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
     const char *node = crm_element_value(xml_obj, PCMK_XE_NODE);
     const char *discovery = crm_element_value(xml_obj,
                                               PCMK_XA_RESOURCE_DISCOVERY);
 
     if (rsc == NULL) {
         pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                           "does not exist", id, rsc_id);
         return;
     }
 
     if (score == NULL) {
         score = crm_element_value(xml_obj, PCMK_XA_SCORE);
     }
 
     if ((node != NULL) && (score != NULL)) {
         int score_i = char2score(score);
         pcmk_node_t *match = pe_find_node(rsc->cluster->nodes, node);
         enum rsc_role_e role = pcmk_role_unknown;
         pcmk__location_t *location = NULL;
 
         if (!match) {
             return;
         }
 
         if (role_spec == NULL) {
             role_spec = crm_element_value(xml_obj, PCMK_XA_ROLE);
         }
         if (parse_location_role(role_spec, &role)) {
             crm_trace("Setting location constraint %s role filter: %s",
                       id, role_spec);
         } else {
             /* @COMPAT The previous behavior of creating the constraint ignoring
              * the role is retained for now, but we should ignore the entire
              * constraint when we can break backward compatibility.
              */
             pcmk__config_err("Ignoring role in constraint %s: "
                              "Invalid value '%s'", id, role_spec);
         }
 
         location = pcmk__new_location(id, rsc, score_i, discovery, match);
         if (location == NULL) {
             return; // Error already logged
         }
         location->role_filter = role;
 
     } else {
         bool empty = true;
         crm_time_t *next_change = crm_time_new_undefined();
 
         /* This loop is logically parallel to pe_evaluate_rules(), except
          * instead of checking whether any rule is active, we set up location
          * constraints for each active rule.
          */
         for (xmlNode *rule_xml = first_named_child(xml_obj, PCMK_XE_RULE);
              rule_xml != NULL; rule_xml = crm_next_same_xml(rule_xml)) {
             empty = false;
             crm_trace("Unpacking %s/%s", id, pcmk__xe_id(rule_xml));
             generate_location_rule(rsc, rule_xml, discovery, next_change,
                                    re_match_data);
         }
 
         if (empty) {
             pcmk__config_err("Ignoring constraint '%s' because it contains "
                              "no rules", id);
         }
 
         /* If there is a point in the future when the evaluation of a rule will
          * change, make sure the scheduler is re-run by that time.
          */
         if (crm_time_is_defined(next_change)) {
             time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change);
 
             pe__update_recheck_time(t, rsc->cluster,
                                     "location rule evaluation");
         }
         crm_time_free(next_change);
     }
 }
 
 static void
 unpack_simple_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
     const char *value = crm_element_value(xml_obj, PCMK_XA_RSC);
 
     if (value) {
         pcmk_resource_t *rsc;
 
         rsc = pcmk__find_constraint_resource(scheduler->resources, value);
         unpack_rsc_location(xml_obj, rsc, NULL, NULL, NULL);
     }
 
     value = crm_element_value(xml_obj, PCMK_XA_RSC_PATTERN);
     if (value) {
         regex_t *r_patt = calloc(1, sizeof(regex_t));
         bool invert = false;
 
         if (value[0] == '!') {
             value++;
             invert = true;
         }
 
         if (regcomp(r_patt, value, REG_EXTENDED) != 0) {
             pcmk__config_err("Ignoring constraint '%s' because "
                              PCMK_XA_RSC_PATTERN
                              " has invalid value '%s'", id, value);
             free(r_patt);
             return;
         }
 
         for (GList *iter = scheduler->resources; iter != NULL;
              iter = iter->next) {
 
             pcmk_resource_t *r = iter->data;
             int nregs = 0;
             regmatch_t *pmatch = NULL;
             int status;
 
             if (r_patt->re_nsub > 0) {
                 nregs = r_patt->re_nsub + 1;
             } else {
                 nregs = 1;
             }
             pmatch = calloc(nregs, sizeof(regmatch_t));
 
             status = regexec(r_patt, r->id, nregs, pmatch, 0);
 
             if (!invert && (status == 0)) {
                 pe_re_match_data_t re_match_data = {
                                                 .string = r->id,
                                                 .nregs = nregs,
                                                 .pmatch = pmatch
                                                };
 
                 crm_debug("'%s' matched '%s' for %s", r->id, value, id);
                 unpack_rsc_location(xml_obj, r, NULL, NULL, &re_match_data);
 
             } else if (invert && (status != 0)) {
                 crm_debug("'%s' is an inverted match of '%s' for %s",
                           r->id, value, id);
                 unpack_rsc_location(xml_obj, r, NULL, NULL, NULL);
 
             } else {
                 crm_trace("'%s' does not match '%s' for %s", r->id, value, id);
             }
 
             free(pmatch);
         }
 
         regfree(r_patt);
         free(r_patt);
     }
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                      pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *rsc_id = NULL;
     const char *state = NULL;
     pcmk_resource_t *rsc = NULL;
     pcmk_tag_t *tag = NULL;
     xmlNode *rsc_set = NULL;
 
     *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = pcmk__xe_id(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
                          xml_obj->name);
         return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION);
         return pcmk_rc_ok;
     }
 
     rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     if (rsc_id == NULL) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, rsc_id);
         return pcmk_rc_unpack_error;
 
     } else if (rsc != NULL) {
         // No template is referenced
         return pcmk_rc_ok;
     }
 
     state = crm_element_value(xml_obj, PCMK_XA_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     /* Convert any template or tag reference into constraint
      * PCMK_XE_RESOURCE_SET
      */
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC,
                           false, scheduler)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
         return pcmk_rc_unpack_error;
     }
 
     if (rsc_set != NULL) {
         if (state != NULL) {
             /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
              * PCMK_XA_ROLE attribute
              */
             crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
             xml_remove_prop(*expanded_xml, PCMK_XA_ROLE);
         }
         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION);
 
     } else {
         // No sets
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_location_set(xmlNode *location, xmlNode *set,
                     pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_rsc = NULL;
     pcmk_resource_t *resource = NULL;
     const char *set_id;
     const char *role;
     const char *local_score;
 
     CRM_CHECK(set != NULL, return EINVAL);
 
     set_id = pcmk__xe_id(set);
     if (set_id == NULL) {
         pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET " without "
                          PCMK_XA_ID " in constraint '%s'",
                          pcmk__s(pcmk__xe_id(location), "(missing ID)"));
         return pcmk_rc_unpack_error;
     }
 
     role = crm_element_value(set, PCMK_XA_ROLE);
     local_score = crm_element_value(set, PCMK_XA_SCORE);
 
     for (xml_rsc = first_named_child(set, PCMK_XE_RESOURCE_REF);
          xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
         resource = pcmk__find_constraint_resource(scheduler->resources,
                                                   pcmk__xe_id(xml_rsc));
         if (resource == NULL) {
             pcmk__config_err("%s: No resource found for %s",
                              set_id, pcmk__xe_id(xml_rsc));
             return pcmk_rc_unpack_error;
         }
 
         unpack_rsc_location(location, resource, role, local_score, NULL);
     }
 
     return pcmk_rc_ok;
 }
 
 void
 pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     xmlNode *set = NULL;
     bool any_sets = false;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     if (unpack_location_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) {
         return;
     }
 
     if (expanded_xml) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     for (set = first_named_child(xml_obj, PCMK_XE_RESOURCE_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         any_sets = true;
         set = expand_idref(set, scheduler->input);
         if ((set == NULL) // Configuration error, message already logged
             || (unpack_location_set(xml_obj, set, scheduler) != pcmk_rc_ok)) {
 
             if (expanded_xml) {
                 free_xml(expanded_xml);
             }
             return;
         }
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (!any_sets) {
         unpack_simple_location(xml_obj, scheduler);
     }
 }
 
 /*!
  * \internal
  * \brief Add a new location constraint to scheduler data
  *
  * \param[in]     id             XML ID of location constraint
  * \param[in,out] rsc            Resource in location constraint
  * \param[in]     node_score     Constraint score
  * \param[in]     discover_mode  Resource discovery option for constraint
  * \param[in]     node           Node in constraint (or NULL if rule-based)
  *
  * \return Newly allocated location constraint
  * \note The result will be added to the cluster (via \p rsc) and should not be
  *       freed separately.
  */
 pcmk__location_t *
 pcmk__new_location(const char *id, pcmk_resource_t *rsc,
                    int node_score, const char *discover_mode, pcmk_node_t *node)
 {
     pcmk__location_t *new_con = NULL;
 
     if (id == NULL) {
         pcmk__config_err("Invalid constraint: no ID specified");
         return NULL;
 
     } else if (rsc == NULL) {
         pcmk__config_err("Invalid constraint %s: no resource specified", id);
         return NULL;
 
     } else if (node == NULL) {
         CRM_CHECK(node_score == 0, return NULL);
     }
 
     new_con = calloc(1, sizeof(pcmk__location_t));
     if (new_con != NULL) {
         new_con->id = strdup(id);
         new_con->rsc = rsc;
         new_con->nodes = NULL;
         new_con->role_filter = pcmk_role_unknown;
 
         if (pcmk__str_eq(discover_mode, PCMK_VALUE_ALWAYS,
                          pcmk__str_null_matches|pcmk__str_casei)) {
             new_con->discover_mode = pcmk_probe_always;
 
         } else if (pcmk__str_eq(discover_mode, PCMK_VALUE_NEVER,
                                 pcmk__str_casei)) {
             new_con->discover_mode = pcmk_probe_never;
 
         } else if (pcmk__str_eq(discover_mode, PCMK_VALUE_EXCLUSIVE,
                                 pcmk__str_casei)) {
             new_con->discover_mode = pcmk_probe_exclusive;
             rsc->exclusive_discover = TRUE;
 
         } else {
             pcmk__config_err("Invalid " PCMK_XA_RESOURCE_DISCOVERY " value %s "
                              "in location constraint", discover_mode);
         }
 
         if (node != NULL) {
             pcmk_node_t *copy = pe__copy_node(node);
 
             copy->weight = node_score;
             new_con->nodes = g_list_prepend(NULL, copy);
         }
 
         rsc->cluster->placement_constraints = g_list_prepend(
             rsc->cluster->placement_constraints, new_con);
         rsc->rsc_location = g_list_prepend(rsc->rsc_location, new_con);
     }
 
     return new_con;
 }
 
 /*!
  * \internal
  * \brief Apply all location constraints
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__apply_locations(pcmk_scheduler_t *scheduler)
 {
     for (GList *iter = scheduler->placement_constraints;
          iter != NULL; iter = iter->next) {
         pcmk__location_t *location = iter->data;
 
         location->rsc->cmds->apply_location(location->rsc, location);
     }
 }
 
 /*!
  * \internal
  * \brief Apply a location constraint to a resource's allowed node scores
  *
  * \param[in,out] rsc         Resource to apply constraint to
  * \param[in,out] location    Location constraint to apply
  *
  * \note This does not consider the resource's children, so the resource's
  *       apply_location() method should be used instead in most cases.
  */
 void
 pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
 {
     bool need_role = false;
 
     CRM_ASSERT((rsc != NULL) && (location != NULL));
 
     // If a role was specified, ensure constraint is applicable
     need_role = (location->role_filter > pcmk_role_unknown);
     if (need_role && (location->role_filter != rsc->next_role)) {
         pcmk__rsc_trace(rsc,
                         "Not applying %s to %s because role will be %s not %s",
                         location->id, rsc->id, pcmk_role_text(rsc->next_role),
                         pcmk_role_text(location->role_filter));
         return;
     }
 
     if (location->nodes == NULL) {
         pcmk__rsc_trace(rsc, "Not applying %s to %s because no nodes match",
                         location->id, rsc->id);
         return;
     }
 
     pcmk__rsc_trace(rsc, "Applying %s%s%s to %s", location->id,
                     (need_role? " for role " : ""),
                     (need_role? pcmk_role_text(location->role_filter) : ""),
                     rsc->id);
 
     for (GList *iter = location->nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = iter->data;
         pcmk_node_t *allowed_node = g_hash_table_lookup(rsc->allowed_nodes,
                                                         node->details->id);
 
         if (allowed_node == NULL) {
             pcmk__rsc_trace(rsc, "* = %d on %s",
                             node->weight, pcmk__node_name(node));
             allowed_node = pe__copy_node(node);
             g_hash_table_insert(rsc->allowed_nodes,
                                 (gpointer) allowed_node->details->id,
                                 allowed_node);
         } else {
             pcmk__rsc_trace(rsc, "* + %d on %s",
                             node->weight, pcmk__node_name(node));
             allowed_node->weight = pcmk__add_scores(allowed_node->weight,
                                                     node->weight);
         }
 
         if (allowed_node->rsc_discover_mode < location->discover_mode) {
             if (location->discover_mode == pcmk_probe_exclusive) {
                 rsc->exclusive_discover = TRUE;
             }
             /* exclusive > never > always... always is default */
             allowed_node->rsc_discover_mode = location->discover_mode;
         }
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_nodes.c b/lib/pacemaker/pcmk_sched_nodes.c
index b02d553ca0..bddd23ebd2 100644
--- a/lib/pacemaker/pcmk_sched_nodes.c
+++ b/lib/pacemaker/pcmk_sched_nodes.c
@@ -1,437 +1,437 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <pacemaker-internal.h>
 #include <pacemaker.h>
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Check whether a node is available to run resources
  *
  * \param[in] node            Node to check
  * \param[in] consider_score  If true, consider a negative score unavailable
  * \param[in] consider_guest  If true, consider a guest node unavailable whose
  *                            resource will not be active
  *
  * \return true if node is online and not shutting down, unclean, or in standby
  *         or maintenance mode, otherwise false
  */
 bool
 pcmk__node_available(const pcmk_node_t *node, bool consider_score,
                      bool consider_guest)
 {
     if ((node == NULL) || (node->details == NULL) || !node->details->online
             || node->details->shutdown || node->details->unclean
             || node->details->standby || node->details->maintenance) {
         return false;
     }
 
     if (consider_score && (node->weight < 0)) {
         return false;
     }
 
     // @TODO Go through all callers to see which should set consider_guest
     if (consider_guest && pcmk__is_guest_or_bundle_node(node)) {
         pcmk_resource_t *guest = node->details->remote_rsc->container;
 
         if (guest->fns->location(guest, NULL, FALSE) == NULL) {
             return false;
         }
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Copy a hash table of node objects
  *
  * \param[in] nodes  Hash table to copy
  *
  * \return New copy of nodes (or NULL if nodes is NULL)
  */
 GHashTable *
 pcmk__copy_node_table(GHashTable *nodes)
 {
     GHashTable *new_table = NULL;
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
 
     if (nodes == NULL) {
         return NULL;
     }
     new_table = pcmk__strkey_table(NULL, free);
     g_hash_table_iter_init(&iter, nodes);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
         pcmk_node_t *new_node = pe__copy_node(node);
 
         g_hash_table_insert(new_table, (gpointer) new_node->details->id,
                             new_node);
     }
     return new_table;
 }
 
 /*!
  * \internal
  * \brief Free a table of node tables
  *
  * \param[in,out] data  Table to free
  *
  * \note This is a \c GDestroyNotify wrapper for \c g_hash_table_destroy().
  */
 static void
 destroy_node_tables(gpointer data)
 {
     g_hash_table_destroy((GHashTable *) data);
 }
 
 /*!
  * \internal
  * \brief Recursively copy the node tables of a resource
  *
  * Build a hash table containing copies of the allowed nodes tables of \p rsc
  * and its entire tree of descendants. The key is the resource ID, and the value
  * is a copy of the resource's node table.
  *
  * \param[in]     rsc   Resource whose node table to copy
  * \param[in,out] copy  Where to store the copied node tables
  *
  * \note \p *copy should be \c NULL for the top-level call.
  * \note The caller is responsible for freeing \p copy using
  *       \c g_hash_table_destroy().
  */
 void
 pcmk__copy_node_tables(const pcmk_resource_t *rsc, GHashTable **copy)
 {
     CRM_ASSERT((rsc != NULL) && (copy != NULL));
 
     if (*copy == NULL) {
         *copy = pcmk__strkey_table(NULL, destroy_node_tables);
     }
 
     g_hash_table_insert(*copy, rsc->id,
                         pcmk__copy_node_table(rsc->allowed_nodes));
 
     for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk__copy_node_tables((const pcmk_resource_t *) iter->data, copy);
     }
 }
 
 /*!
  * \internal
  * \brief Recursively restore the node tables of a resource from backup
  *
  * Given a hash table containing backup copies of the allowed nodes tables of
  * \p rsc and its entire tree of descendants, replace the resources' current
  * node tables with the backed-up copies.
  *
  * \param[in,out] rsc     Resource whose node tables to restore
  * \param[in]     backup  Table of backup node tables (created by
  *                        \c pcmk__copy_node_tables())
  *
  * \note This function frees the resources' current node tables.
  */
 void
 pcmk__restore_node_tables(pcmk_resource_t *rsc, GHashTable *backup)
 {
     CRM_ASSERT((rsc != NULL) && (backup != NULL));
 
     g_hash_table_destroy(rsc->allowed_nodes);
 
     // Copy to avoid danger with multiple restores
     rsc->allowed_nodes = g_hash_table_lookup(backup, rsc->id);
     rsc->allowed_nodes = pcmk__copy_node_table(rsc->allowed_nodes);
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk__restore_node_tables((pcmk_resource_t *) iter->data, backup);
     }
 }
 
 /*!
  * \internal
  * \brief Copy a list of node objects
  *
  * \param[in] list   List to copy
  * \param[in] reset  Set copies' scores to 0
  *
  * \return New list of shallow copies of nodes in original list
  */
 GList *
 pcmk__copy_node_list(const GList *list, bool reset)
 {
     GList *result = NULL;
 
     for (const GList *iter = list; iter != NULL; iter = iter->next) {
         pcmk_node_t *new_node = NULL;
         pcmk_node_t *this_node = iter->data;
 
         new_node = pe__copy_node(this_node);
         if (reset) {
             new_node->weight = 0;
         }
         result = g_list_prepend(result, new_node);
     }
     return result;
 }
 
 /*!
  * \internal
  * \brief Compare two nodes for assignment preference
  *
  * Given two nodes, check which one is more preferred by assignment criteria
  * such as node score and utilization.
  *
  * \param[in] a     First node to compare
  * \param[in] b     Second node to compare
  * \param[in] data  Node to prefer if all else equal
  *
  * \return -1 if \p a is preferred, +1 if \p b is preferred, or 0 if they are
  *         equally preferred
  */
 static gint
 compare_nodes(gconstpointer a, gconstpointer b, gpointer data)
 {
     const pcmk_node_t *node1 = (const pcmk_node_t *) a;
     const pcmk_node_t *node2 = (const pcmk_node_t *) b;
     const pcmk_node_t *preferred = (const pcmk_node_t *) data;
 
-    int node1_score = -INFINITY;
-    int node2_score = -INFINITY;
+    int node1_score = -PCMK_SCORE_INFINITY;
+    int node2_score = -PCMK_SCORE_INFINITY;
 
     int result = 0;
 
     if (a == NULL) {
         return 1;
     }
     if (b == NULL) {
         return -1;
     }
 
     // Compare node scores
 
     if (pcmk__node_available(node1, false, false)) {
         node1_score = node1->weight;
     }
     if (pcmk__node_available(node2, false, false)) {
         node2_score = node2->weight;
     }
 
     if (node1_score > node2_score) {
         crm_trace("%s before %s (score %d > %d)",
                   pcmk__node_name(node1), pcmk__node_name(node2),
                   node1_score, node2_score);
         return -1;
     }
 
     if (node1_score < node2_score) {
         crm_trace("%s after %s (score %d < %d)",
                   pcmk__node_name(node1), pcmk__node_name(node2),
                   node1_score, node2_score);
         return 1;
     }
 
     // If appropriate, compare node utilization
 
     if (pcmk__str_eq(node1->details->data_set->placement_strategy,
                      PCMK_VALUE_MINIMAL, pcmk__str_casei)) {
         goto equal;
     }
 
     if (pcmk__str_eq(node1->details->data_set->placement_strategy,
                      PCMK_VALUE_BALANCED, pcmk__str_casei)) {
 
         result = pcmk__compare_node_capacities(node1, node2);
         if (result < 0) {
             crm_trace("%s before %s (greater capacity by %d attributes)",
                       pcmk__node_name(node1), pcmk__node_name(node2),
                       result * -1);
             return -1;
         } else if (result > 0) {
             crm_trace("%s after %s (lower capacity by %d attributes)",
                       pcmk__node_name(node1), pcmk__node_name(node2), result);
             return 1;
         }
     }
 
     // Compare number of resources already assigned to node
 
     if (node1->details->num_resources < node2->details->num_resources) {
         crm_trace("%s before %s (%d resources < %d)",
                   pcmk__node_name(node1), pcmk__node_name(node2),
                   node1->details->num_resources, node2->details->num_resources);
         return -1;
 
     } else if (node1->details->num_resources > node2->details->num_resources) {
         crm_trace("%s after %s (%d resources > %d)",
                   pcmk__node_name(node1), pcmk__node_name(node2),
                   node1->details->num_resources, node2->details->num_resources);
         return 1;
     }
 
     // Check whether one node is already running desired resource
 
     if (preferred != NULL) {
         if (pcmk__same_node(preferred, node1)) {
             crm_trace("%s before %s (preferred node)",
                       pcmk__node_name(node1), pcmk__node_name(node2));
             return -1;
         } else if (pcmk__same_node(preferred, node2)) {
             crm_trace("%s after %s (not preferred node)",
                       pcmk__node_name(node1), pcmk__node_name(node2));
             return 1;
         }
     }
 
     // If all else is equal, prefer node with lowest-sorting name
 equal:
     result = strcmp(node1->details->uname, node2->details->uname);
     if (result < 0) {
         crm_trace("%s before %s (name)",
                   pcmk__node_name(node1), pcmk__node_name(node2));
         return -1;
     } else if (result > 0) {
         crm_trace("%s after %s (name)",
                   pcmk__node_name(node1), pcmk__node_name(node2));
         return 1;
     }
 
     crm_trace("%s == %s", pcmk__node_name(node1), pcmk__node_name(node2));
     return 0;
 }
 
 /*!
  * \internal
  * \brief Sort a list of nodes by assigment preference
  *
  * \param[in,out] nodes        Node list to sort
  * \param[in]     active_node  Node where resource being assigned is active
  *
  * \return New head of sorted list
  */
 GList *
 pcmk__sort_nodes(GList *nodes, pcmk_node_t *active_node)
 {
     return g_list_sort_with_data(nodes, compare_nodes, active_node);
 }
 
 /*!
  * \internal
  * \brief Check whether any node is available to run resources
  *
  * \param[in] nodes  Nodes to check
  *
  * \return true if any node in \p nodes is available to run resources,
  *         otherwise false
  */
 bool
 pcmk__any_node_available(GHashTable *nodes)
 {
     GHashTableIter iter;
     const pcmk_node_t *node = NULL;
 
     if (nodes == NULL) {
         return false;
     }
     g_hash_table_iter_init(&iter, nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
         if (pcmk__node_available(node, true, false)) {
             return true;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Apply node health values for all nodes in cluster
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__apply_node_health(pcmk_scheduler_t *scheduler)
 {
     int base_health = 0;
     enum pcmk__health_strategy strategy;
     const char *strategy_str =
         pcmk__cluster_option(scheduler->config_hash,
                              PCMK_OPT_NODE_HEALTH_STRATEGY);
 
     strategy = pcmk__parse_health_strategy(strategy_str);
     if (strategy == pcmk__health_strategy_none) {
         return;
     }
     crm_info("Applying node health strategy '%s'", strategy_str);
 
     // The progressive strategy can use a base health score
     if (strategy == pcmk__health_strategy_progressive) {
         base_health = pe__health_score(PCMK_OPT_NODE_HEALTH_BASE, scheduler);
     }
 
     for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
         int health = pe__sum_node_health_scores(node, base_health);
 
         // An overall health score of 0 has no effect
         if (health == 0) {
             continue;
         }
         crm_info("Overall system health of %s is %d",
                  pcmk__node_name(node), health);
 
         // Use node health as a location score for each resource on the node
         for (GList *r = scheduler->resources; r != NULL; r = r->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) r->data;
 
             bool constrain = true;
 
             if (health < 0) {
                 /* Negative health scores do not apply to resources with
                  * PCMK_META_ALLOW_UNHEALTHY_NODES=true.
                  */
                 constrain = !crm_is_true(g_hash_table_lookup(rsc->meta,
                                          PCMK_META_ALLOW_UNHEALTHY_NODES));
             }
             if (constrain) {
                 pcmk__new_location(strategy_str, rsc, health, NULL, node);
             } else {
                 pcmk__rsc_trace(rsc, "%s is immune from health ban on %s",
                                 rsc->id, pcmk__node_name(node));
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Check for a node in a resource's parent's allowed nodes
  *
  * \param[in] rsc   Resource whose parent should be checked
  * \param[in] node  Node to check for
  *
  * \return Equivalent of \p node from \p rsc's parent's allowed nodes if any,
  *         otherwise NULL
  */
 pcmk_node_t *
 pcmk__top_allowed_node(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     GHashTable *allowed_nodes = NULL;
 
     if ((rsc == NULL) || (node == NULL)) {
         return NULL;
     } else if (rsc->parent == NULL) {
         allowed_nodes = rsc->allowed_nodes;
     } else {
         allowed_nodes = rsc->parent->allowed_nodes;
     }
     return g_hash_table_lookup(allowed_nodes, node->details->id);
 }
diff --git a/lib/pacemaker/pcmk_sched_primitive.c b/lib/pacemaker/pcmk_sched_primitive.c
index 10a9457479..75c9e31a2c 100644
--- a/lib/pacemaker/pcmk_sched_primitive.c
+++ b/lib/pacemaker/pcmk_sched_primitive.c
@@ -1,1679 +1,1680 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <stdint.h>                 // uint8_t, uint32_t
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 static void stop_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
                           bool optional);
 static void start_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
                            bool optional);
 static void demote_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
                             bool optional);
 static void promote_resource(pcmk_resource_t *rsc, pcmk_node_t *node,
                              bool optional);
 static void assert_role_error(pcmk_resource_t *rsc, pcmk_node_t *node,
                               bool optional);
 
 #define RSC_ROLE_MAX    (pcmk_role_promoted + 1)
 
 static enum rsc_role_e rsc_state_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
     /* This array lists the immediate next role when transitioning from one role
      * to a target role. For example, when going from Stopped to Promoted, the
      * next role is Unpromoted, because the resource must be started before it
      * can be promoted. The current state then becomes Started, which is fed
      * into this array again, giving a next role of Promoted.
      *
      * Current role       Immediate next role   Final target role
      * ------------       -------------------   -----------------
      */
     /* Unknown */       { pcmk_role_unknown,    /* Unknown */
                           pcmk_role_stopped,    /* Stopped */
                           pcmk_role_stopped,    /* Started */
                           pcmk_role_stopped,    /* Unpromoted */
                           pcmk_role_stopped,    /* Promoted */
                         },
     /* Stopped */       { pcmk_role_stopped,    /* Unknown */
                           pcmk_role_stopped,    /* Stopped */
                           pcmk_role_started,    /* Started */
                           pcmk_role_unpromoted, /* Unpromoted */
                           pcmk_role_unpromoted, /* Promoted */
                         },
     /* Started */       { pcmk_role_stopped,    /* Unknown */
                           pcmk_role_stopped,    /* Stopped */
                           pcmk_role_started,    /* Started */
                           pcmk_role_unpromoted, /* Unpromoted */
                           pcmk_role_promoted,   /* Promoted */
                         },
     /* Unpromoted */    { pcmk_role_stopped,    /* Unknown */
                           pcmk_role_stopped,    /* Stopped */
                           pcmk_role_stopped,    /* Started */
                           pcmk_role_unpromoted, /* Unpromoted */
                           pcmk_role_promoted,   /* Promoted */
                         },
     /* Promoted  */     { pcmk_role_stopped,    /* Unknown */
                           pcmk_role_unpromoted, /* Stopped */
                           pcmk_role_unpromoted, /* Started */
                           pcmk_role_unpromoted, /* Unpromoted */
                           pcmk_role_promoted,   /* Promoted */
                         },
 };
 
 /*!
  * \internal
  * \brief Function to schedule actions needed for a role change
  *
  * \param[in,out] rsc       Resource whose role is changing
  * \param[in,out] node      Node where resource will be in its next role
  * \param[in]     optional  Whether scheduled actions should be optional
  */
 typedef void (*rsc_transition_fn)(pcmk_resource_t *rsc, pcmk_node_t *node,
                                   bool optional);
 
 static rsc_transition_fn rsc_action_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
     /* This array lists the function needed to transition directly from one role
      * to another. NULL indicates that nothing is needed.
      *
      * Current role         Transition function             Next role
      * ------------         -------------------             ----------
      */
     /* Unknown */       {   assert_role_error,              /* Unknown */
                             stop_resource,                  /* Stopped */
                             assert_role_error,              /* Started */
                             assert_role_error,              /* Unpromoted */
                             assert_role_error,              /* Promoted */
                         },
     /* Stopped */       {   assert_role_error,              /* Unknown */
                             NULL,                           /* Stopped */
                             start_resource,                 /* Started */
                             start_resource,                 /* Unpromoted */
                             assert_role_error,              /* Promoted */
                         },
     /* Started */       {   assert_role_error,              /* Unknown */
                             stop_resource,                  /* Stopped */
                             NULL,                           /* Started */
                             NULL,                           /* Unpromoted */
                             promote_resource,               /* Promoted */
                         },
     /* Unpromoted */    {   assert_role_error,              /* Unknown */
                             stop_resource,                  /* Stopped */
                             stop_resource,                  /* Started */
                             NULL,                           /* Unpromoted */
                             promote_resource,               /* Promoted */
                         },
     /* Promoted  */     {   assert_role_error,              /* Unknown */
                             demote_resource,                /* Stopped */
                             demote_resource,                /* Started */
                             demote_resource,                /* Unpromoted */
                             NULL,                           /* Promoted */
                         },
 };
 
 /*!
  * \internal
  * \brief Get a list of a resource's allowed nodes sorted by node score
  *
  * \param[in] rsc  Resource to check
  *
  * \return List of allowed nodes sorted by node score
  */
 static GList *
 sorted_allowed_nodes(const pcmk_resource_t *rsc)
 {
     if (rsc->allowed_nodes != NULL) {
         GList *nodes = g_hash_table_get_values(rsc->allowed_nodes);
 
         if (nodes != NULL) {
             return pcmk__sort_nodes(nodes, pcmk__current_node(rsc));
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Assign a resource to its best allowed node, if possible
  *
  * \param[in,out] rsc           Resource to choose a node for
  * \param[in]     prefer        If not \c NULL, prefer this node when all else
  *                              equal
  * \param[in]     stop_if_fail  If \c true and \p rsc can't be assigned to a
  *                              node, set next role to stopped and update
  *                              existing actions
  *
  * \return true if \p rsc could be assigned to a node, otherwise false
  *
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 static bool
 assign_best_node(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                  bool stop_if_fail)
 {
     GList *nodes = NULL;
     pcmk_node_t *chosen = NULL;
     pcmk_node_t *best = NULL;
     const pcmk_node_t *most_free_node = pcmk__ban_insufficient_capacity(rsc);
 
     if (prefer == NULL) {
         prefer = most_free_node;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         // We've already finished assignment of resources to nodes
         return rsc->allocated_to != NULL;
     }
 
     // Sort allowed nodes by score
     nodes = sorted_allowed_nodes(rsc);
     if (nodes != NULL) {
         best = (pcmk_node_t *) nodes->data; // First node has best score
     }
 
     if ((prefer != NULL) && (nodes != NULL)) {
         // Get the allowed node version of prefer
         chosen = g_hash_table_lookup(rsc->allowed_nodes, prefer->details->id);
 
         if (chosen == NULL) {
             pcmk__rsc_trace(rsc, "Preferred node %s for %s was unknown",
                             pcmk__node_name(prefer), rsc->id);
 
         /* Favor the preferred node as long as its score is at least as good as
          * the best allowed node's.
          *
          * An alternative would be to favor the preferred node even if the best
          * node is better, when the best node's score is less than INFINITY.
          */
         } else if (chosen->weight < best->weight) {
             pcmk__rsc_trace(rsc, "Preferred node %s for %s was unsuitable",
                             pcmk__node_name(chosen), rsc->id);
             chosen = NULL;
 
         } else if (!pcmk__node_available(chosen, true, false)) {
             pcmk__rsc_trace(rsc, "Preferred node %s for %s was unavailable",
                             pcmk__node_name(chosen), rsc->id);
             chosen = NULL;
 
         } else {
             pcmk__rsc_trace(rsc,
                             "Chose preferred node %s for %s "
                             "(ignoring %d candidates)",
                             pcmk__node_name(chosen), rsc->id,
                             g_list_length(nodes));
         }
     }
 
     if ((chosen == NULL) && (best != NULL)) {
         /* Either there is no preferred node, or the preferred node is not
          * suitable, but another node is allowed to run the resource.
          */
 
         chosen = best;
 
         if (!pcmk__is_unique_clone(rsc->parent)
             && (chosen->weight > 0) // Zero not acceptable
             && pcmk__node_available(chosen, false, false)) {
             /* If the resource is already running on a node, prefer that node if
              * it is just as good as the chosen node.
              *
              * We don't do this for unique clone instances, because
              * pcmk__assign_instances() has already assigned instances to their
              * running nodes when appropriate, and if we get here, we don't want
              * remaining unassigned instances to prefer a node that's already
              * running another instance.
              */
             pcmk_node_t *running = pcmk__current_node(rsc);
 
             if (running == NULL) {
                 // Nothing to do
 
             } else if (!pcmk__node_available(running, true, false)) {
                 pcmk__rsc_trace(rsc,
                                 "Current node for %s (%s) can't run resources",
                                 rsc->id, pcmk__node_name(running));
 
             } else {
                 int nodes_with_best_score = 1;
 
                 for (GList *iter = nodes->next; iter; iter = iter->next) {
                     pcmk_node_t *allowed = (pcmk_node_t *) iter->data;
 
                     if (allowed->weight != chosen->weight) {
                         // The nodes are sorted by score, so no more are equal
                         break;
                     }
                     if (pcmk__same_node(allowed, running)) {
                         // Scores are equal, so prefer the current node
                         chosen = allowed;
                     }
                     nodes_with_best_score++;
                 }
 
                 if (nodes_with_best_score > 1) {
                     uint8_t log_level = LOG_INFO;
 
-                    if (chosen->weight >= INFINITY) {
+                    if (chosen->weight >= PCMK_SCORE_INFINITY) {
                         log_level = LOG_WARNING;
                     }
                     do_crm_log(log_level,
                                "Chose %s for %s from %d nodes with score %s",
                                pcmk__node_name(chosen), rsc->id,
                                nodes_with_best_score,
                                pcmk_readable_score(chosen->weight));
                 }
             }
         }
 
         pcmk__rsc_trace(rsc, "Chose %s for %s from %d candidates",
                         pcmk__node_name(chosen), rsc->id, g_list_length(nodes));
     }
 
     pcmk__assign_resource(rsc, chosen, false, stop_if_fail);
     g_list_free(nodes);
     return rsc->allocated_to != NULL;
 }
 
 /*!
  * \internal
  * \brief Apply a "this with" colocation to a node's allowed node scores
  *
  * \param[in,out] colocation  Colocation to apply
  * \param[in,out] rsc         Resource being assigned
  */
 static void
 apply_this_with(pcmk__colocation_t *colocation, pcmk_resource_t *rsc)
 {
     GHashTable *archive = NULL;
     pcmk_resource_t *other = colocation->primary;
 
     // In certain cases, we will need to revert the node scores
     if ((colocation->dependent_role >= pcmk_role_promoted)
-        || ((colocation->score < 0) && (colocation->score > -INFINITY))) {
+        || ((colocation->score < 0)
+            && (colocation->score > -PCMK_SCORE_INFINITY))) {
         archive = pcmk__copy_node_table(rsc->allowed_nodes);
     }
 
     if (pcmk_is_set(other->flags, pcmk_rsc_unassigned)) {
         pcmk__rsc_trace(rsc,
                         "%s: Assigning colocation %s primary %s first"
                         "(score=%d role=%s)",
                         rsc->id, colocation->id, other->id,
                         colocation->score,
                         pcmk_role_text(colocation->dependent_role));
         other->cmds->assign(other, NULL, true);
     }
 
     // Apply the colocation score to this resource's allowed node scores
     rsc->cmds->apply_coloc_score(rsc, other, colocation, true);
     if ((archive != NULL)
         && !pcmk__any_node_available(rsc->allowed_nodes)) {
         pcmk__rsc_info(rsc,
                        "%s: Reverting scores from colocation with %s "
                        "because no nodes allowed",
                        rsc->id, other->id);
         g_hash_table_destroy(rsc->allowed_nodes);
         rsc->allowed_nodes = archive;
         archive = NULL;
     }
     if (archive != NULL) {
         g_hash_table_destroy(archive);
     }
 }
 
 /*!
  * \internal
  * \brief Update a Pacemaker Remote node once its connection has been assigned
  *
  * \param[in] connection  Connection resource that has been assigned
  */
 static void
 remote_connection_assigned(const pcmk_resource_t *connection)
 {
     pcmk_node_t *remote_node = pe_find_node(connection->cluster->nodes,
                                             connection->id);
 
     CRM_CHECK(remote_node != NULL, return);
 
     if ((connection->allocated_to != NULL)
         && (connection->next_role != pcmk_role_stopped)) {
 
         crm_trace("Pacemaker Remote node %s will be online",
                   remote_node->details->id);
         remote_node->details->online = TRUE;
         if (remote_node->details->unseen) {
             // Avoid unnecessary fence, since we will attempt connection
             remote_node->details->unclean = FALSE;
         }
 
     } else {
         crm_trace("Pacemaker Remote node %s will be shut down "
                   "(%sassigned connection's next role is %s)",
                   remote_node->details->id,
                   ((connection->allocated_to == NULL)? "un" : ""),
                   pcmk_role_text(connection->next_role));
         remote_node->details->shutdown = TRUE;
     }
 }
 
 /*!
  * \internal
  * \brief Assign a primitive resource to a node
  *
  * \param[in,out] rsc           Resource to assign to a node
  * \param[in]     prefer        Node to prefer, if all else is equal
  * \param[in]     stop_if_fail  If \c true and \p rsc can't be assigned to a
  *                              node, set next role to stopped and update
  *                              existing actions
  *
  * \return Node that \p rsc is assigned to, if assigned entirely to one node
  *
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 pcmk_node_t *
 pcmk__primitive_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
                        bool stop_if_fail)
 {
     GList *this_with_colocations = NULL;
     GList *with_this_colocations = NULL;
     GList *iter = NULL;
     pcmk__colocation_t *colocation = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive));
 
     // Never assign a child without parent being assigned first
     if ((rsc->parent != NULL)
         && !pcmk_is_set(rsc->parent->flags, pcmk_rsc_assigning)) {
         pcmk__rsc_debug(rsc, "%s: Assigning parent %s first",
                         rsc->id, rsc->parent->id);
         rsc->parent->cmds->assign(rsc->parent, prefer, stop_if_fail);
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         // Assignment has already been done
         const char *node_name = "no node";
 
         if (rsc->allocated_to != NULL) {
             node_name = pcmk__node_name(rsc->allocated_to);
         }
         pcmk__rsc_debug(rsc, "%s: pre-assigned to %s", rsc->id, node_name);
         return rsc->allocated_to;
     }
 
     // Ensure we detect assignment loops
     if (pcmk_is_set(rsc->flags, pcmk_rsc_assigning)) {
         pcmk__rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
         return NULL;
     }
     pcmk__set_rsc_flags(rsc, pcmk_rsc_assigning);
 
     pe__show_node_scores(true, rsc, "Pre-assignment", rsc->allowed_nodes,
                          rsc->cluster);
 
     this_with_colocations = pcmk__this_with_colocations(rsc);
     with_this_colocations = pcmk__with_this_colocations(rsc);
 
     // Apply mandatory colocations first, to satisfy as many as possible
     for (iter = this_with_colocations; iter != NULL; iter = iter->next) {
         colocation = iter->data;
 
-        if ((colocation->score <= -CRM_SCORE_INFINITY)
-            || (colocation->score >= CRM_SCORE_INFINITY)) {
+        if ((colocation->score <= -PCMK_SCORE_INFINITY)
+            || (colocation->score >= PCMK_SCORE_INFINITY)) {
             apply_this_with(colocation, rsc);
         }
     }
     for (iter = with_this_colocations; iter != NULL; iter = iter->next) {
         colocation = iter->data;
 
-        if ((colocation->score <= -CRM_SCORE_INFINITY)
-            || (colocation->score >= CRM_SCORE_INFINITY)) {
+        if ((colocation->score <= -PCMK_SCORE_INFINITY)
+            || (colocation->score >= PCMK_SCORE_INFINITY)) {
             pcmk__add_dependent_scores(colocation, rsc);
         }
     }
 
     pe__show_node_scores(true, rsc, "Mandatory-colocations",
                          rsc->allowed_nodes, rsc->cluster);
 
     // Then apply optional colocations
     for (iter = this_with_colocations; iter != NULL; iter = iter->next) {
         colocation = iter->data;
 
-        if ((colocation->score > -CRM_SCORE_INFINITY)
-            && (colocation->score < CRM_SCORE_INFINITY)) {
+        if ((colocation->score > -PCMK_SCORE_INFINITY)
+            && (colocation->score < PCMK_SCORE_INFINITY)) {
             apply_this_with(colocation, rsc);
         }
     }
     for (iter = with_this_colocations; iter != NULL; iter = iter->next) {
         colocation = iter->data;
 
-        if ((colocation->score > -CRM_SCORE_INFINITY)
-            && (colocation->score < CRM_SCORE_INFINITY)) {
+        if ((colocation->score > -PCMK_SCORE_INFINITY)
+            && (colocation->score < PCMK_SCORE_INFINITY)) {
             pcmk__add_dependent_scores(colocation, rsc);
         }
     }
 
     g_list_free(this_with_colocations);
     g_list_free(with_this_colocations);
 
     if (rsc->next_role == pcmk_role_stopped) {
         pcmk__rsc_trace(rsc,
                         "Banning %s from all nodes because it will be stopped",
                         rsc->id);
-        resource_location(rsc, NULL, -INFINITY, PCMK_META_TARGET_ROLE,
-                          rsc->cluster);
+        resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                          PCMK_META_TARGET_ROLE, rsc->cluster);
 
     } else if ((rsc->next_role > rsc->role)
                && !pcmk_is_set(rsc->cluster->flags, pcmk_sched_quorate)
                && (rsc->cluster->no_quorum_policy == pcmk_no_quorum_freeze)) {
         crm_notice("Resource %s cannot be elevated from %s to %s due to "
                    PCMK_OPT_NO_QUORUM_POLICY "=" PCMK_VALUE_FREEZE,
                    rsc->id, pcmk_role_text(rsc->role),
                    pcmk_role_text(rsc->next_role));
         pe__set_next_role(rsc, rsc->role,
                           PCMK_OPT_NO_QUORUM_POLICY "=" PCMK_VALUE_FREEZE);
     }
 
     pe__show_node_scores(!pcmk_is_set(rsc->cluster->flags,
                                       pcmk_sched_output_scores),
                          rsc, __func__, rsc->allowed_nodes, rsc->cluster);
 
     // Unmanage resource if fencing is enabled but no device is configured
     if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)
         && !pcmk_is_set(rsc->cluster->flags, pcmk_sched_have_fencing)) {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         // Unmanaged resources stay on their current node
         const char *reason = NULL;
         pcmk_node_t *assign_to = NULL;
 
         pe__set_next_role(rsc, rsc->role, "unmanaged");
         assign_to = pcmk__current_node(rsc);
         if (assign_to == NULL) {
             reason = "inactive";
         } else if (rsc->role == pcmk_role_promoted) {
             reason = "promoted";
         } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
             reason = "failed";
         } else {
             reason = "active";
         }
         pcmk__rsc_info(rsc, "Unmanaged resource %s assigned to %s: %s", rsc->id,
                        (assign_to? assign_to->details->uname : "no node"),
                        reason);
         pcmk__assign_resource(rsc, assign_to, true, stop_if_fail);
 
     } else if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_stop_all)) {
         // Must stop at some point, but be consistent with stop_if_fail
         if (stop_if_fail) {
             pcmk__rsc_debug(rsc,
                             "Forcing %s to stop: " PCMK_OPT_STOP_ALL_RESOURCES,
                             rsc->id);
         }
         pcmk__assign_resource(rsc, NULL, true, stop_if_fail);
 
     } else if (!assign_best_node(rsc, prefer, stop_if_fail)) {
         // Assignment failed
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
             pcmk__rsc_info(rsc, "Resource %s cannot run anywhere", rsc->id);
         } else if ((rsc->running_on != NULL) && stop_if_fail) {
             pcmk__rsc_info(rsc, "Stopping orphan resource %s", rsc->id);
         }
     }
 
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_assigning);
 
     if (rsc->is_remote_node) {
         remote_connection_assigned(rsc);
     }
 
     return rsc->allocated_to;
 }
 
 /*!
  * \internal
  * \brief Schedule actions to bring resource down and back to current role
  *
  * \param[in,out] rsc           Resource to restart
  * \param[in,out] current       Node that resource should be brought down on
  * \param[in]     need_stop     Whether the resource must be stopped
  * \param[in]     need_promote  Whether the resource must be promoted
  *
  * \return Role that resource would have after scheduled actions are taken
  */
 static void
 schedule_restart_actions(pcmk_resource_t *rsc, pcmk_node_t *current,
                          bool need_stop, bool need_promote)
 {
     enum rsc_role_e role = rsc->role;
     enum rsc_role_e next_role;
     rsc_transition_fn fn = NULL;
 
     pcmk__set_rsc_flags(rsc, pcmk_rsc_restarting);
 
     // Bring resource down to a stop on its current node
     while (role != pcmk_role_stopped) {
         next_role = rsc_state_matrix[role][pcmk_role_stopped];
         pcmk__rsc_trace(rsc, "Creating %s action to take %s down from %s to %s",
                         (need_stop? "required" : "optional"), rsc->id,
                         pcmk_role_text(role), pcmk_role_text(next_role));
         fn = rsc_action_matrix[role][next_role];
         if (fn == NULL) {
             break;
         }
         fn(rsc, current, !need_stop);
         role = next_role;
     }
 
     // Bring resource up to its next role on its next node
     while ((rsc->role <= rsc->next_role) && (role != rsc->role)
            && !pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
         bool required = need_stop;
 
         next_role = rsc_state_matrix[role][rsc->role];
         if ((next_role == pcmk_role_promoted) && need_promote) {
             required = true;
         }
         pcmk__rsc_trace(rsc, "Creating %s action to take %s up from %s to %s",
                         (required? "required" : "optional"), rsc->id,
                         pcmk_role_text(role), pcmk_role_text(next_role));
         fn = rsc_action_matrix[role][next_role];
         if (fn == NULL) {
             break;
         }
         fn(rsc, rsc->allocated_to, !required);
         role = next_role;
     }
 
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_restarting);
 }
 
 /*!
  * \internal
  * \brief If a resource's next role is not explicitly specified, set a default
  *
  * \param[in,out] rsc  Resource to set next role for
  *
  * \return "explicit" if next role was explicitly set, otherwise "implicit"
  */
 static const char *
 set_default_next_role(pcmk_resource_t *rsc)
 {
     if (rsc->next_role != pcmk_role_unknown) {
         return "explicit";
     }
 
     if (rsc->allocated_to == NULL) {
         pe__set_next_role(rsc, pcmk_role_stopped, "assignment");
     } else {
         pe__set_next_role(rsc, pcmk_role_started, "assignment");
     }
     return "implicit";
 }
 
 /*!
  * \internal
  * \brief Create an action to represent an already pending start
  *
  * \param[in,out] rsc  Resource to create start action for
  */
 static void
 create_pending_start(pcmk_resource_t *rsc)
 {
     pcmk_action_t *start = NULL;
 
     pcmk__rsc_trace(rsc,
                     "Creating action for %s to represent already pending start",
                     rsc->id);
     start = start_action(rsc, rsc->allocated_to, TRUE);
     pcmk__set_action_flags(start, pcmk_action_always_in_graph);
 }
 
 /*!
  * \internal
  * \brief Schedule actions needed to take a resource to its next role
  *
  * \param[in,out] rsc  Resource to schedule actions for
  */
 static void
 schedule_role_transition_actions(pcmk_resource_t *rsc)
 {
     enum rsc_role_e role = rsc->role;
 
     while (role != rsc->next_role) {
         enum rsc_role_e next_role = rsc_state_matrix[role][rsc->next_role];
         rsc_transition_fn fn = NULL;
 
         pcmk__rsc_trace(rsc,
                         "Creating action to take %s from %s to %s "
                         "(ending at %s)",
                         rsc->id, pcmk_role_text(role),
                         pcmk_role_text(next_role),
                         pcmk_role_text(rsc->next_role));
         fn = rsc_action_matrix[role][next_role];
         if (fn == NULL) {
             break;
         }
         fn(rsc, rsc->allocated_to, false);
         role = next_role;
     }
 }
 
 /*!
  * \internal
  * \brief Create all actions needed for a given primitive resource
  *
  * \param[in,out] rsc  Primitive resource to create actions for
  */
 void
 pcmk__primitive_create_actions(pcmk_resource_t *rsc)
 {
     bool need_stop = false;
     bool need_promote = false;
     bool is_moving = false;
     bool allow_migrate = false;
     bool multiply_active = false;
 
     pcmk_node_t *current = NULL;
     unsigned int num_all_active = 0;
     unsigned int num_clean_active = 0;
     const char *next_role_source = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive));
 
     next_role_source = set_default_next_role(rsc);
     pcmk__rsc_trace(rsc,
                     "Creating all actions for %s transition from %s to %s "
                     "(%s) on %s",
                     rsc->id, pcmk_role_text(rsc->role),
                     pcmk_role_text(rsc->next_role), next_role_source,
                     pcmk__node_name(rsc->allocated_to));
 
     current = rsc->fns->active_node(rsc, &num_all_active, &num_clean_active);
 
     g_list_foreach(rsc->dangling_migrations, pcmk__abort_dangling_migration,
                    rsc);
 
     if ((current != NULL) && (rsc->allocated_to != NULL)
         && !pcmk__same_node(current, rsc->allocated_to)
         && (rsc->next_role >= pcmk_role_started)) {
 
         pcmk__rsc_trace(rsc, "Moving %s from %s to %s",
                         rsc->id, pcmk__node_name(current),
                         pcmk__node_name(rsc->allocated_to));
         is_moving = true;
         allow_migrate = pcmk__rsc_can_migrate(rsc, current);
 
         // This is needed even if migrating (though I'm not sure why ...)
         need_stop = true;
     }
 
     // Check whether resource is partially migrated and/or multiply active
     if ((rsc->partial_migration_source != NULL)
         && (rsc->partial_migration_target != NULL)
         && allow_migrate && (num_all_active == 2)
         && pcmk__same_node(current, rsc->partial_migration_source)
         && pcmk__same_node(rsc->allocated_to, rsc->partial_migration_target)) {
         /* A partial migration is in progress, and the migration target remains
          * the same as when the migration began.
          */
         pcmk__rsc_trace(rsc,
                         "Partial migration of %s from %s to %s will continue",
                         rsc->id, pcmk__node_name(rsc->partial_migration_source),
                         pcmk__node_name(rsc->partial_migration_target));
 
     } else if ((rsc->partial_migration_source != NULL)
                || (rsc->partial_migration_target != NULL)) {
         // A partial migration is in progress but can't be continued
 
         if (num_all_active > 2) {
             // The resource is migrating *and* multiply active!
             crm_notice("Forcing recovery of %s because it is migrating "
                        "from %s to %s and possibly active elsewhere",
                        rsc->id, pcmk__node_name(rsc->partial_migration_source),
                        pcmk__node_name(rsc->partial_migration_target));
         } else {
             // The migration source or target isn't available
             crm_notice("Forcing recovery of %s because it can no longer "
                        "migrate from %s to %s",
                        rsc->id, pcmk__node_name(rsc->partial_migration_source),
                        pcmk__node_name(rsc->partial_migration_target));
         }
         need_stop = true;
         rsc->partial_migration_source = rsc->partial_migration_target = NULL;
         allow_migrate = false;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
         multiply_active = (num_all_active > 1);
     } else {
         /* If a resource has PCMK_META_REQUIRES set to PCMK_VALUE_NOTHING or
          * PCMK_VALUE_QUORUM, don't consider it active on unclean nodes (similar
          * to how all resources behave when PCMK_OPT_STONITH_ENABLED is false).
          * We can start such resources elsewhere before fencing completes, and
          * if we considered the resource active on the failed node, we would
          * attempt recovery for being active on multiple nodes.
          */
         multiply_active = (num_clean_active > 1);
     }
 
     if (multiply_active) {
         const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
 
         // Resource was (possibly) incorrectly multiply active
         pcmk__sched_err("%s resource %s might be active on %u nodes (%s)",
                         pcmk__s(class, "Untyped"), rsc->id, num_all_active,
                         pcmk_multiply_active_text(rsc->recovery_type));
         crm_notice("For more information, see \"What are multiply active "
                    "resources?\" at "
                    "https://projects.clusterlabs.org/w/clusterlabs/faq/");
 
         switch (rsc->recovery_type) {
             case pcmk_multiply_active_restart:
                 need_stop = true;
                 break;
             case pcmk_multiply_active_unexpected:
                 need_stop = true; // stop_resource() will skip expected node
                 pcmk__set_rsc_flags(rsc, pcmk_rsc_stop_unexpected);
                 break;
             default:
                 break;
         }
 
     } else {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_stop_unexpected);
     }
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_start_pending)) {
         create_pending_start(rsc);
     }
 
     if (is_moving) {
         // Remaining tests are only for resources staying where they are
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_stop_if_failed)) {
             need_stop = true;
             pcmk__rsc_trace(rsc, "Recovering %s", rsc->id);
         } else {
             pcmk__rsc_trace(rsc, "Recovering %s by demotion", rsc->id);
             if (rsc->next_role == pcmk_role_promoted) {
                 need_promote = true;
             }
         }
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
         pcmk__rsc_trace(rsc, "Blocking further actions on %s", rsc->id);
         need_stop = true;
 
     } else if ((rsc->role > pcmk_role_started) && (current != NULL)
                && (rsc->allocated_to != NULL)) {
         pcmk_action_t *start = NULL;
 
         pcmk__rsc_trace(rsc, "Creating start action for promoted resource %s",
                         rsc->id);
         start = start_action(rsc, rsc->allocated_to, TRUE);
         if (!pcmk_is_set(start->flags, pcmk_action_optional)) {
             // Recovery of a promoted resource
             pcmk__rsc_trace(rsc, "%s restart is required for recovery", rsc->id);
             need_stop = true;
         }
     }
 
     // Create any actions needed to bring resource down and back up to same role
     schedule_restart_actions(rsc, current, need_stop, need_promote);
 
     // Create any actions needed to take resource from this role to the next
     schedule_role_transition_actions(rsc);
 
     pcmk__create_recurring_actions(rsc);
 
     if (allow_migrate) {
         pcmk__create_migration_actions(rsc, current);
     }
 }
 
 /*!
  * \internal
  * \brief Ban a resource from any allowed nodes that are Pacemaker Remote nodes
  *
  * \param[in] rsc  Resource to check
  */
 static void
 rsc_avoids_remote_nodes(const pcmk_resource_t *rsc)
 {
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
 
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
         if (node->details->remote_rsc != NULL) {
-            node->weight = -INFINITY;
+            node->weight = -PCMK_SCORE_INFINITY;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Return allowed nodes as (possibly sorted) list
  *
  * Convert a resource's hash table of allowed nodes to a list. If printing to
  * stdout, sort the list, to keep action ID numbers consistent for regression
  * test output (while avoiding the performance hit on a live cluster).
  *
  * \param[in] rsc       Resource to check for allowed nodes
  *
  * \return List of resource's allowed nodes
  * \note Callers should take care not to rely on the list being sorted.
  */
 static GList *
 allowed_nodes_as_list(const pcmk_resource_t *rsc)
 {
     GList *allowed_nodes = NULL;
 
     if (rsc->allowed_nodes) {
         allowed_nodes = g_hash_table_get_values(rsc->allowed_nodes);
     }
 
     if (!pcmk__is_daemon) {
         allowed_nodes = g_list_sort(allowed_nodes, pe__cmp_node_name);
     }
 
     return allowed_nodes;
 }
 
 /*!
  * \internal
  * \brief Create implicit constraints needed for a primitive resource
  *
  * \param[in,out] rsc  Primitive resource to create implicit constraints for
  */
 void
 pcmk__primitive_internal_constraints(pcmk_resource_t *rsc)
 {
     GList *allowed_nodes = NULL;
     bool check_unfencing = false;
     bool check_utilization = false;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         pcmk__rsc_trace(rsc,
                         "Skipping implicit constraints for unmanaged resource "
                         "%s", rsc->id);
         return;
     }
 
     // Whether resource requires unfencing
     check_unfencing = !pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)
                       && pcmk_is_set(rsc->cluster->flags,
                                      pcmk_sched_enable_unfencing)
                       && pcmk_is_set(rsc->flags, pcmk_rsc_needs_unfencing);
 
     // Whether a non-default placement strategy is used
     check_utilization = (g_hash_table_size(rsc->utilization) > 0)
                          && !pcmk__str_eq(rsc->cluster->placement_strategy,
                                           PCMK_VALUE_DEFAULT, pcmk__str_casei);
 
     // Order stops before starts (i.e. restart)
     pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0), NULL,
                        rsc, pcmk__op_key(rsc->id, PCMK_ACTION_START, 0), NULL,
                        pcmk__ar_ordered
                        |pcmk__ar_first_implies_then
                        |pcmk__ar_intermediate_stop,
                        rsc->cluster);
 
     // Promotable ordering: demote before stop, start before promote
     if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
                     pcmk_rsc_promotable)
         || (rsc->role > pcmk_role_unpromoted)) {
 
         pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_DEMOTE, 0),
                            NULL,
                            rsc, pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0),
                            NULL,
                            pcmk__ar_promoted_then_implies_first, rsc->cluster);
 
         pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_START, 0),
                            NULL,
                            rsc, pcmk__op_key(rsc->id, PCMK_ACTION_PROMOTE, 0),
                            NULL,
                            pcmk__ar_unrunnable_first_blocks, rsc->cluster);
     }
 
     // Don't clear resource history if probing on same node
     pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
                        NULL, rsc,
                        pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0),
                        NULL,
                        pcmk__ar_if_on_same_node|pcmk__ar_then_cancels_first,
                        rsc->cluster);
 
     // Certain checks need allowed nodes
     if (check_unfencing || check_utilization || (rsc->container != NULL)) {
         allowed_nodes = allowed_nodes_as_list(rsc);
     }
 
     if (check_unfencing) {
         g_list_foreach(allowed_nodes, pcmk__order_restart_vs_unfence, rsc);
     }
 
     if (check_utilization) {
         pcmk__create_utilization_constraints(rsc, allowed_nodes);
     }
 
     if (rsc->container != NULL) {
         pcmk_resource_t *remote_rsc = NULL;
 
         if (rsc->is_remote_node) {
             // rsc is the implicit remote connection for a guest or bundle node
 
             /* Guest resources are not allowed to run on Pacemaker Remote nodes,
              * to avoid nesting remotes. However, bundles are allowed.
              */
             if (!pcmk_is_set(rsc->flags, pcmk_rsc_remote_nesting_allowed)) {
                 rsc_avoids_remote_nodes(rsc->container);
             }
 
             /* If someone cleans up a guest or bundle node's container, we will
              * likely schedule a (re-)probe of the container and recovery of the
              * connection. Order the connection stop after the container probe,
              * so that if we detect the container running, we will trigger a new
              * transition and avoid the unnecessary recovery.
              */
             pcmk__order_resource_actions(rsc->container, PCMK_ACTION_MONITOR,
                                          rsc, PCMK_ACTION_STOP,
                                          pcmk__ar_ordered);
 
         /* A user can specify that a resource must start on a Pacemaker Remote
          * node by explicitly configuring it with the container=NODENAME
          * meta-attribute. This is of questionable merit, since location
          * constraints can accomplish the same thing. But we support it, so here
          * we check whether a resource (that is not itself a remote connection)
          * has container set to a remote node or guest node resource.
          */
         } else if (rsc->container->is_remote_node) {
             remote_rsc = rsc->container;
         } else  {
             remote_rsc = pe__resource_contains_guest_node(rsc->cluster,
                                                           rsc->container);
         }
 
         if (remote_rsc != NULL) {
             /* Force the resource on the Pacemaker Remote node instead of
              * colocating the resource with the container resource.
              */
             for (GList *item = allowed_nodes; item; item = item->next) {
                 pcmk_node_t *node = item->data;
 
                 if (node->details->remote_rsc != remote_rsc) {
-                    node->weight = -INFINITY;
+                    node->weight = -PCMK_SCORE_INFINITY;
                 }
             }
 
         } else {
             /* This resource is either a filler for a container that does NOT
              * represent a Pacemaker Remote node, or a Pacemaker Remote
              * connection resource for a guest node or bundle.
              */
             int score;
 
             crm_trace("Order and colocate %s relative to its container %s",
                       rsc->id, rsc->container->id);
 
             pcmk__new_ordering(rsc->container,
                                pcmk__op_key(rsc->container->id,
                                             PCMK_ACTION_START, 0),
                                NULL, rsc,
                                pcmk__op_key(rsc->id, PCMK_ACTION_START, 0),
                                NULL,
                                pcmk__ar_first_implies_then
                                |pcmk__ar_unrunnable_first_blocks,
                                rsc->cluster);
 
             pcmk__new_ordering(rsc,
                                pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0),
                                NULL,
                                rsc->container,
                                pcmk__op_key(rsc->container->id,
                                             PCMK_ACTION_STOP, 0),
                                NULL, pcmk__ar_then_implies_first, rsc->cluster);
 
             if (pcmk_is_set(rsc->flags, pcmk_rsc_remote_nesting_allowed)) {
                 score = 10000;    /* Highly preferred but not essential */
             } else {
-                score = INFINITY; /* Force them to run on the same host */
+                score = PCMK_SCORE_INFINITY; // Force to run on same host
             }
             pcmk__new_colocation("#resource-with-container", NULL, score, rsc,
                                  rsc->container, NULL, NULL,
                                  pcmk__coloc_influence);
         }
     }
 
     if (rsc->is_remote_node
         || pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) {
         /* Remote connections and fencing devices are not allowed to run on
          * Pacemaker Remote nodes
          */
         rsc_avoids_remote_nodes(rsc);
     }
     g_list_free(allowed_nodes);
 }
 
 /*!
  * \internal
  * \brief Apply a colocation's score to node scores or resource priority
  *
  * Given a colocation constraint, apply its score to the dependent's
  * allowed node scores (if we are still placing resources) or priority (if
  * we are choosing promotable clone instance roles).
  *
  * \param[in,out] dependent      Dependent resource in colocation
  * \param[in]     primary        Primary resource in colocation
  * \param[in]     colocation     Colocation constraint to apply
  * \param[in]     for_dependent  true if called on behalf of dependent
  */
 void
 pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
                                   const pcmk_resource_t *primary,
                                   const pcmk__colocation_t *colocation,
                                   bool for_dependent)
 {
     enum pcmk__coloc_affects filter_results;
 
     CRM_ASSERT((dependent != NULL) && (primary != NULL)
                && (colocation != NULL));
 
     if (for_dependent) {
         // Always process on behalf of primary resource
         primary->cmds->apply_coloc_score(dependent, primary, colocation, false);
         return;
     }
 
     filter_results = pcmk__colocation_affects(dependent, primary, colocation,
                                               false);
     pcmk__rsc_trace(dependent, "%s %s with %s (%s, score=%d, filter=%d)",
                     ((colocation->score > 0)? "Colocating" : "Anti-colocating"),
                     dependent->id, primary->id, colocation->id,
                     colocation->score,
                     filter_results);
 
     switch (filter_results) {
         case pcmk__coloc_affects_role:
             pcmk__apply_coloc_to_priority(dependent, primary, colocation);
             break;
         case pcmk__coloc_affects_location:
             pcmk__apply_coloc_to_scores(dependent, primary, colocation);
             break;
         default: // pcmk__coloc_affects_nothing
             return;
     }
 }
 
 /* Primitive implementation of
  * pcmk_assignment_methods_t:with_this_colocations()
  */
 void
 pcmk__with_primitive_colocations(const pcmk_resource_t *rsc,
                                  const pcmk_resource_t *orig_rsc, GList **list)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)
                && (list != NULL));
 
     if (rsc == orig_rsc) {
         /* For the resource itself, add all of its own colocations and relevant
          * colocations from its parent (if any).
          */
         pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
         if (rsc->parent != NULL) {
             rsc->parent->cmds->with_this_colocations(rsc->parent, orig_rsc, list);
         }
     } else {
         // For an ancestor, add only explicitly configured constraints
         for (GList *iter = rsc->rsc_cons_lhs; iter != NULL; iter = iter->next) {
             pcmk__colocation_t *colocation = iter->data;
 
             if (pcmk_is_set(colocation->flags, pcmk__coloc_explicit)) {
                 pcmk__add_with_this(list, colocation, orig_rsc);
             }
         }
     }
 }
 
 /* Primitive implementation of
  * pcmk_assignment_methods_t:this_with_colocations()
  */
 void
 pcmk__primitive_with_colocations(const pcmk_resource_t *rsc,
                                  const pcmk_resource_t *orig_rsc, GList **list)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)
                && (list != NULL));
 
     if (rsc == orig_rsc) {
         /* For the resource itself, add all of its own colocations and relevant
          * colocations from its parent (if any).
          */
         pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
         if (rsc->parent != NULL) {
             rsc->parent->cmds->this_with_colocations(rsc->parent, orig_rsc, list);
         }
     } else {
         // For an ancestor, add only explicitly configured constraints
         for (GList *iter = rsc->rsc_cons; iter != NULL; iter = iter->next) {
             pcmk__colocation_t *colocation = iter->data;
 
             if (pcmk_is_set(colocation->flags, pcmk__coloc_explicit)) {
                 pcmk__add_this_with(list, colocation, orig_rsc);
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Return action flags for a given primitive resource action
  *
  * \param[in,out] action  Action to get flags for
  * \param[in]     node    If not NULL, limit effects to this node (ignored)
  *
  * \return Flags appropriate to \p action on \p node
  */
 uint32_t
 pcmk__primitive_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
 {
     CRM_ASSERT(action != NULL);
     return (uint32_t) action->flags;
 }
 
 /*!
  * \internal
  * \brief Check whether a node is a multiply active resource's expected node
  *
  * \param[in] rsc  Resource to check
  * \param[in] node  Node to check
  *
  * \return \c true if \p rsc is multiply active with
  *         \c PCMK_META_MULTIPLE_ACTIVE set to \c stop_unexpected, and \p node
  *         is the node where it will remain active
  * \note This assumes that the resource's next role cannot be changed to stopped
  *       after this is called, which should be reasonable if status has already
  *       been unpacked and resources have been assigned to nodes.
  */
 static bool
 is_expected_node(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     return pcmk_all_flags_set(rsc->flags,
                               pcmk_rsc_stop_unexpected|pcmk_rsc_restarting)
            && (rsc->next_role > pcmk_role_stopped)
            && pcmk__same_node(rsc->allocated_to, node);
 }
 
 /*!
  * \internal
  * \brief Schedule actions needed to stop a resource wherever it is active
  *
  * \param[in,out] rsc       Resource being stopped
  * \param[in]     node      Node where resource is being stopped (ignored)
  * \param[in]     optional  Whether actions should be optional
  */
 static void
 stop_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
 {
     for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
         pcmk_node_t *current = (pcmk_node_t *) iter->data;
         pcmk_action_t *stop = NULL;
 
         if (is_expected_node(rsc, current)) {
             /* We are scheduling restart actions for a multiply active resource
              * with PCMK_META_MULTIPLE_ACTIVE=stop_unexpected, and this is where
              * it should not be stopped.
              */
             pcmk__rsc_trace(rsc,
                             "Skipping stop of multiply active resource %s "
                             "on expected node %s",
                             rsc->id, pcmk__node_name(current));
             continue;
         }
 
         if (rsc->partial_migration_target != NULL) {
             // Continue migration if node originally was and remains target
             if (pcmk__same_node(current, rsc->partial_migration_target)
                 && pcmk__same_node(current, rsc->allocated_to)) {
                 pcmk__rsc_trace(rsc,
                                 "Skipping stop of %s on %s "
                                 "because partial migration there will continue",
                                 rsc->id, pcmk__node_name(current));
                 continue;
             } else {
                 pcmk__rsc_trace(rsc,
                                 "Forcing stop of %s on %s "
                                 "because migration target changed",
                                 rsc->id, pcmk__node_name(current));
                 optional = false;
             }
         }
 
         pcmk__rsc_trace(rsc, "Scheduling stop of %s on %s",
                         rsc->id, pcmk__node_name(current));
         stop = stop_action(rsc, current, optional);
 
         if (rsc->allocated_to == NULL) {
             pe_action_set_reason(stop, "node availability", true);
         } else if (pcmk_all_flags_set(rsc->flags, pcmk_rsc_restarting
                                                   |pcmk_rsc_stop_unexpected)) {
             /* We are stopping a multiply active resource on a node that is
              * not its expected node, and we are still scheduling restart
              * actions, so the stop is for being multiply active.
              */
             pe_action_set_reason(stop, "being multiply active", true);
         }
 
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             pcmk__clear_action_flags(stop, pcmk_action_runnable);
         }
 
         if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_remove_after_stop)) {
             pcmk__schedule_cleanup(rsc, current, optional);
         }
 
         if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_unfencing)) {
             pcmk_action_t *unfence = pe_fence_op(current, PCMK_ACTION_ON, true,
                                                  NULL, false, rsc->cluster);
 
             order_actions(stop, unfence, pcmk__ar_then_implies_first);
             if (!pcmk__node_unfenced(current)) {
                 pcmk__sched_err("Stopping %s until %s can be unfenced",
                                 rsc->id, pcmk__node_name(current));
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Schedule actions needed to start a resource on a node
  *
  * \param[in,out] rsc       Resource being started
  * \param[in,out] node      Node where resource should be started
  * \param[in]     optional  Whether actions should be optional
  */
 static void
 start_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
 {
     pcmk_action_t *start = NULL;
 
     CRM_ASSERT(node != NULL);
 
     pcmk__rsc_trace(rsc, "Scheduling %s start of %s on %s (score %d)",
                     (optional? "optional" : "required"), rsc->id,
                     pcmk__node_name(node), node->weight);
     start = start_action(rsc, node, TRUE);
 
     pcmk__order_vs_unfence(rsc, node, start, pcmk__ar_first_implies_then);
 
     if (pcmk_is_set(start->flags, pcmk_action_runnable) && !optional) {
         pcmk__clear_action_flags(start, pcmk_action_optional);
     }
 
     if (is_expected_node(rsc, node)) {
         /* This could be a problem if the start becomes necessary for other
          * reasons later.
          */
         pcmk__rsc_trace(rsc,
                         "Start of multiply active resouce %s "
                         "on expected node %s will be a pseudo-action",
                         rsc->id, pcmk__node_name(node));
         pcmk__set_action_flags(start, pcmk_action_pseudo);
     }
 }
 
 /*!
  * \internal
  * \brief Schedule actions needed to promote a resource on a node
  *
  * \param[in,out] rsc       Resource being promoted
  * \param[in]     node      Node where resource should be promoted
  * \param[in]     optional  Whether actions should be optional
  */
 static void
 promote_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
 {
     GList *iter = NULL;
     GList *action_list = NULL;
     bool runnable = true;
 
     CRM_ASSERT(node != NULL);
 
     // Any start must be runnable for promotion to be runnable
     action_list = pe__resource_actions(rsc, node, PCMK_ACTION_START, true);
     for (iter = action_list; iter != NULL; iter = iter->next) {
         pcmk_action_t *start = (pcmk_action_t *) iter->data;
 
         if (!pcmk_is_set(start->flags, pcmk_action_runnable)) {
             runnable = false;
         }
     }
     g_list_free(action_list);
 
     if (runnable) {
         pcmk_action_t *promote = promote_action(rsc, node, optional);
 
         pcmk__rsc_trace(rsc, "Scheduling %s promotion of %s on %s",
                         (optional? "optional" : "required"), rsc->id,
                         pcmk__node_name(node));
 
         if (is_expected_node(rsc, node)) {
             /* This could be a problem if the promote becomes necessary for
              * other reasons later.
              */
             pcmk__rsc_trace(rsc,
                             "Promotion of multiply active resouce %s "
                             "on expected node %s will be a pseudo-action",
                             rsc->id, pcmk__node_name(node));
             pcmk__set_action_flags(promote, pcmk_action_pseudo);
         }
     } else {
         pcmk__rsc_trace(rsc, "Not promoting %s on %s: start unrunnable",
                         rsc->id, pcmk__node_name(node));
         action_list = pe__resource_actions(rsc, node, PCMK_ACTION_PROMOTE,
                                            true);
         for (iter = action_list; iter != NULL; iter = iter->next) {
             pcmk_action_t *promote = (pcmk_action_t *) iter->data;
 
             pcmk__clear_action_flags(promote, pcmk_action_runnable);
         }
         g_list_free(action_list);
     }
 }
 
 /*!
  * \internal
  * \brief Schedule actions needed to demote a resource wherever it is active
  *
  * \param[in,out] rsc       Resource being demoted
  * \param[in]     node      Node where resource should be demoted (ignored)
  * \param[in]     optional  Whether actions should be optional
  */
 static void
 demote_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
 {
     /* Since this will only be called for a primitive (possibly as an instance
      * of a collective resource), the resource is multiply active if it is
      * running on more than one node, so we want to demote on all of them as
      * part of recovery, regardless of which one is the desired node.
      */
     for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
         pcmk_node_t *current = (pcmk_node_t *) iter->data;
 
         if (is_expected_node(rsc, current)) {
             pcmk__rsc_trace(rsc,
                             "Skipping demote of multiply active resource %s "
                             "on expected node %s",
                             rsc->id, pcmk__node_name(current));
         } else {
             pcmk__rsc_trace(rsc, "Scheduling %s demotion of %s on %s",
                             (optional? "optional" : "required"), rsc->id,
                             pcmk__node_name(current));
             demote_action(rsc, current, optional);
         }
     }
 }
 
 static void
 assert_role_error(pcmk_resource_t *rsc, pcmk_node_t *node, bool optional)
 {
     CRM_ASSERT(false);
 }
 
 /*!
  * \internal
  * \brief Schedule cleanup of a resource
  *
  * \param[in,out] rsc       Resource to clean up
  * \param[in]     node      Node to clean up on
  * \param[in]     optional  Whether clean-up should be optional
  */
 void
 pcmk__schedule_cleanup(pcmk_resource_t *rsc, const pcmk_node_t *node,
                        bool optional)
 {
     /* If the cleanup is required, its orderings are optional, because they're
      * relevant only if both actions are required. Conversely, if the cleanup is
      * optional, the orderings make the then action required if the first action
      * becomes required.
      */
     uint32_t flag = optional? pcmk__ar_first_implies_then : pcmk__ar_ordered;
 
     CRM_CHECK((rsc != NULL) && (node != NULL), return);
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         pcmk__rsc_trace(rsc, "Skipping clean-up of %s on %s: resource failed",
                         rsc->id, pcmk__node_name(node));
         return;
     }
 
     if (node->details->unclean || !node->details->online) {
         pcmk__rsc_trace(rsc, "Skipping clean-up of %s on %s: node unavailable",
                         rsc->id, pcmk__node_name(node));
         return;
     }
 
     crm_notice("Scheduling clean-up of %s on %s",
                rsc->id, pcmk__node_name(node));
     delete_action(rsc, node, optional);
 
     // stop -> clean-up -> start
     pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
                                  rsc, PCMK_ACTION_DELETE, flag);
     pcmk__order_resource_actions(rsc, PCMK_ACTION_DELETE,
                                  rsc, PCMK_ACTION_START, flag);
 }
 
 /*!
  * \internal
  * \brief Add primitive meta-attributes relevant to graph actions to XML
  *
  * \param[in]     rsc  Primitive resource whose meta-attributes should be added
  * \param[in,out] xml  Transition graph action attributes XML to add to
  */
 void
 pcmk__primitive_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml)
 {
     char *name = NULL;
     char *value = NULL;
     const pcmk_resource_t *parent = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)
                && (xml != NULL));
 
     /* Clone instance numbers get set internally as meta-attributes, and are
      * needed in the transition graph (for example, to tell unique clone
      * instances apart).
      */
     value = g_hash_table_lookup(rsc->meta, PCMK__META_CLONE);
     if (value != NULL) {
         name = crm_meta_name(PCMK__META_CLONE);
         crm_xml_add(xml, name, value);
         free(name);
     }
 
     // Not sure if this one is really needed ...
     value = g_hash_table_lookup(rsc->meta, PCMK_META_REMOTE_NODE);
     if (value != NULL) {
         name = crm_meta_name(PCMK_META_REMOTE_NODE);
         crm_xml_add(xml, name, value);
         free(name);
     }
 
     /* The container meta-attribute can be set on the primitive itself or one of
      * its parents (for example, a group inside a container resource), so check
      * them all, and keep the highest one found.
      */
     for (parent = rsc; parent != NULL; parent = parent->parent) {
         if (parent->container != NULL) {
             crm_xml_add(xml, CRM_META "_" PCMK__META_CONTAINER,
                         parent->container->id);
         }
     }
 
     /* Bundle replica children will get their external-ip set internally as a
      * meta-attribute. The graph action needs it, but under a different naming
      * convention than other meta-attributes.
      */
     value = g_hash_table_lookup(rsc->meta, "external-ip");
     if (value != NULL) {
         crm_xml_add(xml, "pcmk_external_ip", value);
     }
 }
 
 // Primitive implementation of pcmk_assignment_methods_t:add_utilization()
 void
 pcmk__primitive_add_utilization(const pcmk_resource_t *rsc,
                                 const pcmk_resource_t *orig_rsc,
                                 GList *all_rscs, GHashTable *utilization)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)
                && (orig_rsc != NULL) && (utilization != NULL));
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         return;
     }
 
     pcmk__rsc_trace(orig_rsc,
                     "%s: Adding primitive %s as colocated utilization",
                     orig_rsc->id, rsc->id);
     pcmk__release_node_capacity(utilization, rsc);
 }
 
 /*!
  * \internal
  * \brief Get epoch time of node's shutdown attribute (or now if none)
  *
  * \param[in,out] node  Node to check
  *
  * \return Epoch time corresponding to shutdown attribute if set or now if not
  */
 static time_t
 shutdown_time(pcmk_node_t *node)
 {
     const char *shutdown = pcmk__node_attr(node, PCMK__NODE_ATTR_SHUTDOWN, NULL,
                                            pcmk__rsc_node_current);
     time_t result = 0;
 
     if (shutdown != NULL) {
         long long result_ll;
 
         if (pcmk__scan_ll(shutdown, &result_ll, 0LL) == pcmk_rc_ok) {
             result = (time_t) result_ll;
         }
     }
     return (result == 0)? get_effective_time(node->details->data_set) : result;
 }
 
 /*!
  * \internal
  * \brief Ban a resource from a node if it's not locked to the node
  *
  * \param[in]     data       Node to check
  * \param[in,out] user_data  Resource to check
  */
 static void
 ban_if_not_locked(gpointer data, gpointer user_data)
 {
     const pcmk_node_t *node = (const pcmk_node_t *) data;
     pcmk_resource_t *rsc = (pcmk_resource_t *) user_data;
 
     if (strcmp(node->details->uname, rsc->lock_node->details->uname) != 0) {
-        resource_location(rsc, node, -CRM_SCORE_INFINITY,
+        resource_location(rsc, node, -PCMK_SCORE_INFINITY,
                           PCMK_OPT_SHUTDOWN_LOCK, rsc->cluster);
     }
 }
 
 // Primitive implementation of pcmk_assignment_methods_t:shutdown_lock()
 void
 pcmk__primitive_shutdown_lock(pcmk_resource_t *rsc)
 {
     const char *class = NULL;
 
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive));
 
     class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
 
     // Fence devices and remote connections can't be locked
     if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_null_matches)
         || rsc->is_remote_node) {
         return;
     }
 
     if (rsc->lock_node != NULL) {
         // The lock was obtained from resource history
 
         if (rsc->running_on != NULL) {
             /* The resource was started elsewhere even though it is now
              * considered locked. This shouldn't be possible, but as a
              * failsafe, we don't want to disturb the resource now.
              */
             pcmk__rsc_info(rsc,
                            "Cancelling shutdown lock "
                            "because %s is already active", rsc->id);
             pe__clear_resource_history(rsc, rsc->lock_node);
             rsc->lock_node = NULL;
             rsc->lock_time = 0;
         }
 
     // Only a resource active on exactly one node can be locked
     } else if (pcmk__list_of_1(rsc->running_on)) {
         pcmk_node_t *node = rsc->running_on->data;
 
         if (node->details->shutdown) {
             if (node->details->unclean) {
                 pcmk__rsc_debug(rsc,
                                 "Not locking %s to unclean %s for shutdown",
                                 rsc->id, pcmk__node_name(node));
             } else {
                 rsc->lock_node = node;
                 rsc->lock_time = shutdown_time(node);
             }
         }
     }
 
     if (rsc->lock_node == NULL) {
         // No lock needed
         return;
     }
 
     if (rsc->cluster->shutdown_lock > 0) {
         time_t lock_expiration = rsc->lock_time + rsc->cluster->shutdown_lock;
 
         pcmk__rsc_info(rsc, "Locking %s to %s due to shutdown (expires @%lld)",
                        rsc->id, pcmk__node_name(rsc->lock_node),
                        (long long) lock_expiration);
         pe__update_recheck_time(++lock_expiration, rsc->cluster,
                                 "shutdown lock expiration");
     } else {
         pcmk__rsc_info(rsc, "Locking %s to %s due to shutdown",
                        rsc->id, pcmk__node_name(rsc->lock_node));
     }
 
     // If resource is locked to one node, ban it from all other nodes
     g_list_foreach(rsc->cluster->nodes, ban_if_not_locked, rsc);
 }
diff --git a/lib/pacemaker/pcmk_sched_promotable.c b/lib/pacemaker/pcmk_sched_promotable.c
index 71ecea9ab2..fb8bb9a205 100644
--- a/lib/pacemaker/pcmk_sched_promotable.c
+++ b/lib/pacemaker/pcmk_sched_promotable.c
@@ -1,1313 +1,1313 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Add implicit promotion ordering for a promotable instance
  *
  * \param[in,out] clone  Clone resource
  * \param[in,out] child  Instance of \p clone being ordered
  * \param[in,out] last   Previous instance ordered (NULL if \p child is first)
  */
 static void
 order_instance_promotion(pcmk_resource_t *clone, pcmk_resource_t *child,
                          pcmk_resource_t *last)
 {
     // "Promote clone" -> promote instance -> "clone promoted"
     pcmk__order_resource_actions(clone, PCMK_ACTION_PROMOTE,
                                  child, PCMK_ACTION_PROMOTE,
                                  pcmk__ar_ordered);
     pcmk__order_resource_actions(child, PCMK_ACTION_PROMOTE,
                                  clone, PCMK_ACTION_PROMOTED,
                                  pcmk__ar_ordered);
 
     // If clone is ordered, order this instance relative to last
     if ((last != NULL) && pe__clone_is_ordered(clone)) {
         pcmk__order_resource_actions(last, PCMK_ACTION_PROMOTE,
                                      child, PCMK_ACTION_PROMOTE,
                                      pcmk__ar_ordered);
     }
 }
 
 /*!
  * \internal
  * \brief Add implicit demotion ordering for a promotable instance
  *
  * \param[in,out] clone  Clone resource
  * \param[in,out] child  Instance of \p clone being ordered
  * \param[in]     last   Previous instance ordered (NULL if \p child is first)
  */
 static void
 order_instance_demotion(pcmk_resource_t *clone, pcmk_resource_t *child,
                         pcmk_resource_t *last)
 {
     // "Demote clone" -> demote instance -> "clone demoted"
     pcmk__order_resource_actions(clone, PCMK_ACTION_DEMOTE, child,
                                  PCMK_ACTION_DEMOTE,
                                  pcmk__ar_then_implies_first_graphed);
     pcmk__order_resource_actions(child, PCMK_ACTION_DEMOTE,
                                  clone, PCMK_ACTION_DEMOTED,
                                  pcmk__ar_first_implies_then_graphed);
 
     // If clone is ordered, order this instance relative to last
     if ((last != NULL) && pe__clone_is_ordered(clone)) {
         pcmk__order_resource_actions(child, PCMK_ACTION_DEMOTE, last,
                                      PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether an instance will be promoted or demoted
  *
  * \param[in]  rsc        Instance to check
  * \param[out] demoting   If \p rsc will be demoted, this will be set to true
  * \param[out] promoting  If \p rsc will be promoted, this will be set to true
  */
 static void
 check_for_role_change(const pcmk_resource_t *rsc, bool *demoting,
                       bool *promoting)
 {
     const GList *iter = NULL;
 
     // If this is a cloned group, check group members recursively
     if (rsc->children != NULL) {
         for (iter = rsc->children; iter != NULL; iter = iter->next) {
             check_for_role_change((const pcmk_resource_t *) iter->data,
                                   demoting, promoting);
         }
         return;
     }
 
     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
         const pcmk_action_t *action = (const pcmk_action_t *) iter->data;
 
         if (*promoting && *demoting) {
             return;
 
         } else if (pcmk_is_set(action->flags, pcmk_action_optional)) {
             continue;
 
         } else if (pcmk__str_eq(PCMK_ACTION_DEMOTE, action->task,
                                 pcmk__str_none)) {
             *demoting = true;
 
         } else if (pcmk__str_eq(PCMK_ACTION_PROMOTE, action->task,
                                 pcmk__str_none)) {
             *promoting = true;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Add promoted-role location constraint scores to an instance's priority
  *
  * Adjust a promotable clone instance's promotion priority by the scores of any
  * location constraints in a list that are both limited to the promoted role and
  * for the node where the instance will be placed.
  *
  * \param[in,out] child                 Promotable clone instance
  * \param[in]     location_constraints  List of location constraints to apply
  * \param[in]     chosen                Node where \p child will be placed
  */
 static void
 apply_promoted_locations(pcmk_resource_t *child,
                          const GList *location_constraints,
                          const pcmk_node_t *chosen)
 {
     for (const GList *iter = location_constraints; iter; iter = iter->next) {
         const pcmk__location_t *location = iter->data;
         const pcmk_node_t *constraint_node = NULL;
 
         if (location->role_filter == pcmk_role_promoted) {
             constraint_node = pe_find_node_id(location->nodes,
                                               chosen->details->id);
         }
         if (constraint_node != NULL) {
             int new_priority = pcmk__add_scores(child->priority,
                                                 constraint_node->weight);
 
             pcmk__rsc_trace(child,
                             "Applying location %s to %s promotion priority on "
                             "%s: %s + %s = %s",
                             location->id, child->id,
                             pcmk__node_name(constraint_node),
                             pcmk_readable_score(child->priority),
                             pcmk_readable_score(constraint_node->weight),
                             pcmk_readable_score(new_priority));
             child->priority = new_priority;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Get the node that an instance will be promoted on
  *
  * \param[in] rsc  Promotable clone instance to check
  *
  * \return Node that \p rsc will be promoted on, or NULL if none
  */
 static pcmk_node_t *
 node_to_be_promoted_on(const pcmk_resource_t *rsc)
 {
     pcmk_node_t *node = NULL;
     pcmk_node_t *local_node = NULL;
     const pcmk_resource_t *parent = NULL;
 
     // If this is a cloned group, bail if any group member can't be promoted
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
 
         if (node_to_be_promoted_on(child) == NULL) {
             pcmk__rsc_trace(rsc,
                             "%s can't be promoted because member %s can't",
                             rsc->id, child->id);
             return NULL;
         }
     }
 
     node = rsc->fns->location(rsc, NULL, FALSE);
     if (node == NULL) {
         pcmk__rsc_trace(rsc, "%s can't be promoted because it won't be active",
                         rsc->id);
         return NULL;
 
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         if (rsc->fns->state(rsc, TRUE) == pcmk_role_promoted) {
             crm_notice("Unmanaged instance %s will be left promoted on %s",
                        rsc->id, pcmk__node_name(node));
         } else {
             pcmk__rsc_trace(rsc, "%s can't be promoted because it is unmanaged",
                             rsc->id);
             return NULL;
         }
 
     } else if (rsc->priority < 0) {
         pcmk__rsc_trace(rsc,
                         "%s can't be promoted because its promotion priority "
                         "%d is negative",
                         rsc->id, rsc->priority);
         return NULL;
 
     } else if (!pcmk__node_available(node, false, true)) {
         pcmk__rsc_trace(rsc,
                         "%s can't be promoted because %s can't run resources",
                         rsc->id, pcmk__node_name(node));
         return NULL;
     }
 
     parent = pe__const_top_resource(rsc, false);
     local_node = g_hash_table_lookup(parent->allowed_nodes, node->details->id);
 
     if (local_node == NULL) {
         /* It should not be possible for the scheduler to have assigned the
          * instance to a node where its parent is not allowed, but it's good to
          * have a fail-safe.
          */
         if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             pcmk__sched_err("%s can't be promoted because %s is not allowed "
                             "on %s (scheduler bug?)",
                             rsc->id, parent->id, pcmk__node_name(node));
         } // else the instance is unmanaged and already promoted
         return NULL;
 
     } else if ((local_node->count >= pe__clone_promoted_node_max(parent))
                && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         pcmk__rsc_trace(rsc,
                         "%s can't be promoted because %s has "
                         "maximum promoted instances already",
                         rsc->id, pcmk__node_name(node));
         return NULL;
     }
 
     return local_node;
 }
 
 /*!
  * \internal
  * \brief Compare two promotable clone instances by promotion priority
  *
  * \param[in] a  First instance to compare
  * \param[in] b  Second instance to compare
  *
  * \return A negative number if \p a has higher promotion priority,
  *         a positive number if \p b has higher promotion priority,
  *         or 0 if promotion priorities are equal
  */
 static gint
 cmp_promotable_instance(gconstpointer a, gconstpointer b)
 {
     const pcmk_resource_t *rsc1 = (const pcmk_resource_t *) a;
     const pcmk_resource_t *rsc2 = (const pcmk_resource_t *) b;
 
     enum rsc_role_e role1 = pcmk_role_unknown;
     enum rsc_role_e role2 = pcmk_role_unknown;
 
     CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
 
     // Check sort index set by pcmk__set_instance_roles()
     if (rsc1->sort_index > rsc2->sort_index) {
         pcmk__rsc_trace(rsc1,
                         "%s has higher promotion priority than %s "
                         "(sort index %d > %d)",
                         rsc1->id, rsc2->id, rsc1->sort_index, rsc2->sort_index);
         return -1;
     } else if (rsc1->sort_index < rsc2->sort_index) {
         pcmk__rsc_trace(rsc1,
                         "%s has lower promotion priority than %s "
                         "(sort index %d < %d)",
                         rsc1->id, rsc2->id, rsc1->sort_index, rsc2->sort_index);
         return 1;
     }
 
     // If those are the same, prefer instance whose current role is higher
     role1 = rsc1->fns->state(rsc1, TRUE);
     role2 = rsc2->fns->state(rsc2, TRUE);
     if (role1 > role2) {
         pcmk__rsc_trace(rsc1,
                         "%s has higher promotion priority than %s "
                         "(higher current role)",
                         rsc1->id, rsc2->id);
         return -1;
     } else if (role1 < role2) {
         pcmk__rsc_trace(rsc1,
                         "%s has lower promotion priority than %s "
                         "(lower current role)",
                         rsc1->id, rsc2->id);
         return 1;
     }
 
     // Finally, do normal clone instance sorting
     return pcmk__cmp_instance(a, b);
 }
 
 /*!
  * \internal
  * \brief Add a promotable clone instance's sort index to its node's score
  *
  * Add a promotable clone instance's sort index (which sums its promotion
  * preferences and scores of relevant location constraints for the promoted
  * role) to the node score of the instance's assigned node.
  *
  * \param[in]     data       Promotable clone instance
  * \param[in,out] user_data  Clone parent of \p data
  */
 static void
 add_sort_index_to_node_score(gpointer data, gpointer user_data)
 {
     const pcmk_resource_t *child = (const pcmk_resource_t *) data;
     pcmk_resource_t *clone = (pcmk_resource_t *) user_data;
 
     pcmk_node_t *node = NULL;
     const pcmk_node_t *chosen = NULL;
 
     if (child->sort_index < 0) {
         pcmk__rsc_trace(clone, "Not adding sort index of %s: negative",
                         child->id);
         return;
     }
 
     chosen = child->fns->location(child, NULL, FALSE);
     if (chosen == NULL) {
         pcmk__rsc_trace(clone, "Not adding sort index of %s: inactive",
                         child->id);
         return;
     }
 
     node = g_hash_table_lookup(clone->allowed_nodes, chosen->details->id);
     CRM_ASSERT(node != NULL);
 
     node->weight = pcmk__add_scores(child->sort_index, node->weight);
     pcmk__rsc_trace(clone,
                     "Added cumulative priority of %s (%s) to score on %s "
                     "(now %s)",
                     child->id, pcmk_readable_score(child->sort_index),
                     pcmk__node_name(node), pcmk_readable_score(node->weight));
 }
 
 /*!
  * \internal
  * \brief Apply colocation to dependent's node scores if for promoted role
  *
  * \param[in,out] data       Colocation constraint to apply
  * \param[in,out] user_data  Promotable clone that is constraint's dependent
  */
 static void
 apply_coloc_to_dependent(gpointer data, gpointer user_data)
 {
     pcmk__colocation_t *colocation = data;
     pcmk_resource_t *clone = user_data;
     pcmk_resource_t *primary = colocation->primary;
     uint32_t flags = pcmk__coloc_select_default;
-    float factor = colocation->score / (float) INFINITY;
+    float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
 
     if (colocation->dependent_role != pcmk_role_promoted) {
         return;
     }
-    if (colocation->score < INFINITY) {
+    if (colocation->score < PCMK_SCORE_INFINITY) {
         flags = pcmk__coloc_select_active;
     }
     pcmk__rsc_trace(clone, "Applying colocation %s (promoted %s with %s) @%s",
                     colocation->id, colocation->dependent->id,
                     colocation->primary->id,
                     pcmk_readable_score(colocation->score));
     primary->cmds->add_colocated_node_scores(primary, clone, clone->id,
                                              &clone->allowed_nodes, colocation,
                                              factor, flags);
 }
 
 /*!
  * \internal
  * \brief Apply colocation to primary's node scores if for promoted role
  *
  * \param[in,out] data       Colocation constraint to apply
  * \param[in,out] user_data  Promotable clone that is constraint's primary
  */
 static void
 apply_coloc_to_primary(gpointer data, gpointer user_data)
 {
     pcmk__colocation_t *colocation = data;
     pcmk_resource_t *clone = user_data;
     pcmk_resource_t *dependent = colocation->dependent;
-    const float factor = colocation->score / (float) INFINITY;
+    const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
     const uint32_t flags = pcmk__coloc_select_active
                            |pcmk__coloc_select_nonnegative;
 
     if ((colocation->primary_role != pcmk_role_promoted)
          || !pcmk__colocation_has_influence(colocation, NULL)) {
         return;
     }
 
     pcmk__rsc_trace(clone, "Applying colocation %s (%s with promoted %s) @%s",
                     colocation->id, colocation->dependent->id,
                     colocation->primary->id,
                     pcmk_readable_score(colocation->score));
     dependent->cmds->add_colocated_node_scores(dependent, clone, clone->id,
                                                &clone->allowed_nodes,
                                                colocation, factor, flags);
 }
 
 /*!
  * \internal
  * \brief Set clone instance's sort index to its node's score
  *
  * \param[in,out] data       Promotable clone instance
  * \param[in]     user_data  Parent clone of \p data
  */
 static void
 set_sort_index_to_node_score(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *child = (pcmk_resource_t *) data;
     const pcmk_resource_t *clone = (const pcmk_resource_t *) user_data;
 
     pcmk_node_t *chosen = child->fns->location(child, NULL, FALSE);
 
     if (!pcmk_is_set(child->flags, pcmk_rsc_managed)
         && (child->next_role == pcmk_role_promoted)) {
-        child->sort_index = INFINITY;
+        child->sort_index = PCMK_SCORE_INFINITY;
         pcmk__rsc_trace(clone,
                         "Final sort index for %s is INFINITY "
                         "(unmanaged promoted)",
                         child->id);
 
     } else if ((chosen == NULL) || (child->sort_index < 0)) {
         pcmk__rsc_trace(clone,
                         "Final sort index for %s is %d (ignoring node score)",
                         child->id, child->sort_index);
 
     } else {
         const pcmk_node_t *node = g_hash_table_lookup(clone->allowed_nodes,
                                                       chosen->details->id);
 
         CRM_ASSERT(node != NULL);
         child->sort_index = node->weight;
         pcmk__rsc_trace(clone,
                         "Adding scores for %s: final sort index for %s is %d",
                         clone->id, child->id, child->sort_index);
     }
 }
 
 /*!
  * \internal
  * \brief Sort a promotable clone's instances by descending promotion priority
  *
  * \param[in,out] clone  Promotable clone to sort
  */
 static void
 sort_promotable_instances(pcmk_resource_t *clone)
 {
     GList *colocations = NULL;
 
     if (pe__set_clone_flag(clone, pcmk__clone_promotion_constrained)
             == pcmk_rc_already) {
         return;
     }
     pcmk__set_rsc_flags(clone, pcmk_rsc_updating_nodes);
 
     for (GList *iter = clone->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
 
         pcmk__rsc_trace(clone,
                         "Adding scores for %s: initial sort index for %s is %d",
                         clone->id, child->id, child->sort_index);
     }
     pe__show_node_scores(true, clone, "Before", clone->allowed_nodes,
                          clone->cluster);
 
     g_list_foreach(clone->children, add_sort_index_to_node_score, clone);
 
     colocations = pcmk__this_with_colocations(clone);
     g_list_foreach(colocations, apply_coloc_to_dependent, clone);
     g_list_free(colocations);
 
     colocations = pcmk__with_this_colocations(clone);
     g_list_foreach(colocations, apply_coloc_to_primary, clone);
     g_list_free(colocations);
 
     // Ban resource from all nodes if it needs a ticket but doesn't have it
     pcmk__require_promotion_tickets(clone);
 
     pe__show_node_scores(true, clone, "After", clone->allowed_nodes,
                          clone->cluster);
 
     // Reset sort indexes to final node scores
     g_list_foreach(clone->children, set_sort_index_to_node_score, clone);
 
     // Finally, sort instances in descending order of promotion priority
     clone->children = g_list_sort(clone->children, cmp_promotable_instance);
     pcmk__clear_rsc_flags(clone, pcmk_rsc_updating_nodes);
 }
 
 /*!
  * \internal
  * \brief Find the active instance (if any) of an anonymous clone on a node
  *
  * \param[in] clone  Anonymous clone to check
  * \param[in] id     Instance ID (without instance number) to check
  * \param[in] node   Node to check
  *
  * \return
  */
 static pcmk_resource_t *
 find_active_anon_instance(const pcmk_resource_t *clone, const char *id,
                           const pcmk_node_t *node)
 {
     for (GList *iter = clone->children; iter; iter = iter->next) {
         pcmk_resource_t *child = iter->data;
         pcmk_resource_t *active = NULL;
 
         // Use ->find_rsc() in case this is a cloned group
         active = clone->fns->find_rsc(child, id, node,
                                       pcmk_rsc_match_clone_only
                                       |pcmk_rsc_match_current_node);
         if (active != NULL) {
             return active;
         }
     }
     return NULL;
 }
 
 /*
  * \brief Check whether an anonymous clone instance is known on a node
  *
  * \param[in] clone  Anonymous clone to check
  * \param[in] id     Instance ID (without instance number) to check
  * \param[in] node   Node to check
  *
  * \return true if \p id instance of \p clone is known on \p node,
  *         otherwise false
  */
 static bool
 anonymous_known_on(const pcmk_resource_t *clone, const char *id,
                    const pcmk_node_t *node)
 {
     for (GList *iter = clone->children; iter; iter = iter->next) {
         pcmk_resource_t *child = iter->data;
 
         /* Use ->find_rsc() because this might be a cloned group, and knowing
          * that other members of the group are known here implies nothing.
          */
         child = clone->fns->find_rsc(child, id, NULL,
                                      pcmk_rsc_match_clone_only);
         CRM_LOG_ASSERT(child != NULL);
         if (child != NULL) {
             if (g_hash_table_lookup(child->known_on, node->details->id)) {
                 return true;
             }
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Check whether a node is allowed to run a resource
  *
  * \param[in] rsc   Resource to check
  * \param[in] node  Node to check
  *
  * \return true if \p node is allowed to run \p rsc, otherwise false
  */
 static bool
 is_allowed(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     pcmk_node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes,
                                                node->details->id);
 
     return (allowed != NULL) && (allowed->weight >= 0);
 }
 
 /*!
  * \brief Check whether a clone instance's promotion score should be considered
  *
  * \param[in] rsc   Promotable clone instance to check
  * \param[in] node  Node where score would be applied
  *
  * \return true if \p rsc's promotion score should be considered on \p node,
  *         otherwise false
  */
 static bool
 promotion_score_applies(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     char *id = clone_strip(rsc->id);
     const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
     pcmk_resource_t *active = NULL;
     const char *reason = "allowed";
 
     // Some checks apply only to anonymous clone instances
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
 
         // If instance is active on the node, its score definitely applies
         active = find_active_anon_instance(parent, id, node);
         if (active == rsc) {
             reason = "active";
             goto check_allowed;
         }
 
         /* If *no* instance is active on this node, this instance's score will
          * count if it has been probed on this node.
          */
         if ((active == NULL) && anonymous_known_on(parent, id, node)) {
             reason = "probed";
             goto check_allowed;
         }
     }
 
     /* If this clone's status is unknown on *all* nodes (e.g. cluster startup),
      * take all instances' scores into account, to make sure we use any
      * permanent promotion scores.
      */
     if ((rsc->running_on == NULL) && (g_hash_table_size(rsc->known_on) == 0)) {
         reason = "none probed";
         goto check_allowed;
     }
 
     /* Otherwise, we've probed and/or started the resource *somewhere*, so
      * consider promotion scores on nodes where we know the status.
      */
     if ((g_hash_table_lookup(rsc->known_on, node->details->id) != NULL)
         || (pe_find_node_id(rsc->running_on, node->details->id) != NULL)) {
         reason = "known";
     } else {
         pcmk__rsc_trace(rsc,
                         "Ignoring %s promotion score (for %s) on %s: "
                         "not probed",
                         rsc->id, id, pcmk__node_name(node));
         free(id);
         return false;
     }
 
 check_allowed:
     if (is_allowed(rsc, node)) {
         pcmk__rsc_trace(rsc, "Counting %s promotion score (for %s) on %s: %s",
                         rsc->id, id, pcmk__node_name(node), reason);
         free(id);
         return true;
     }
 
     pcmk__rsc_trace(rsc,
                     "Ignoring %s promotion score (for %s) on %s: not allowed",
                     rsc->id, id, pcmk__node_name(node));
     free(id);
     return false;
 }
 
 /*!
  * \internal
  * \brief Get the value of a promotion score node attribute
  *
  * \param[in] rsc   Promotable clone instance to get promotion score for
  * \param[in] node  Node to get promotion score for
  * \param[in] name  Resource name to use in promotion score attribute name
  *
  * \return Value of promotion score node attribute for \p rsc on \p node
  */
 static const char *
 promotion_attr_value(const pcmk_resource_t *rsc, const pcmk_node_t *node,
                      const char *name)
 {
     char *attr_name = NULL;
     const char *attr_value = NULL;
     const char *target = NULL;
     enum pcmk__rsc_node node_type = pcmk__rsc_node_assigned;
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
         // Not assigned yet
         node_type = pcmk__rsc_node_current;
     }
     target = g_hash_table_lookup(rsc->meta,
                                  PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
     attr_name = pcmk_promotion_score_name(name);
     attr_value = pcmk__node_attr(node, attr_name, target, node_type);
     free(attr_name);
     return attr_value;
 }
 
 /*!
  * \internal
  * \brief Get the promotion score for a clone instance on a node
  *
  * \param[in]  rsc         Promotable clone instance to get score for
  * \param[in]  node        Node to get score for
  * \param[out] is_default  If non-NULL, will be set true if no score available
  *
  * \return Promotion score for \p rsc on \p node (or 0 if none)
  */
 static int
 promotion_score(const pcmk_resource_t *rsc, const pcmk_node_t *node,
                 bool *is_default)
 {
     char *name = NULL;
     const char *attr_value = NULL;
 
     if (is_default != NULL) {
         *is_default = true;
     }
 
     CRM_CHECK((rsc != NULL) && (node != NULL), return 0);
 
     /* If this is an instance of a cloned group, the promotion score is the sum
      * of all members' promotion scores.
      */
     if (rsc->children != NULL) {
         int score = 0;
 
         for (const GList *iter = rsc->children;
              iter != NULL; iter = iter->next) {
 
             const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
             bool child_default = false;
             int child_score = promotion_score(child, node, &child_default);
 
             if (!child_default && (is_default != NULL)) {
                 *is_default = false;
             }
             score += child_score;
         }
         return score;
     }
 
     if (!promotion_score_applies(rsc, node)) {
         return 0;
     }
 
     /* For the promotion score attribute name, use the name the resource is
      * known as in resource history, since that's what crm_attribute --promotion
      * would have used.
      */
     name = (rsc->clone_name == NULL)? rsc->id : rsc->clone_name;
 
     attr_value = promotion_attr_value(rsc, node, name);
     if (attr_value != NULL) {
         pcmk__rsc_trace(rsc, "Promotion score for %s on %s = %s",
                         name, pcmk__node_name(node),
                         pcmk__s(attr_value, "(unset)"));
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         /* If we don't have any resource history yet, we won't have clone_name.
          * In that case, for anonymous clones, try the resource name without
          * any instance number.
          */
         name = clone_strip(rsc->id);
         if (strcmp(rsc->id, name) != 0) {
             attr_value = promotion_attr_value(rsc, node, name);
             pcmk__rsc_trace(rsc, "Promotion score for %s on %s (for %s) = %s",
                             name, pcmk__node_name(node), rsc->id,
                             pcmk__s(attr_value, "(unset)"));
         }
         free(name);
     }
 
     if (attr_value == NULL) {
         return 0;
     }
 
     if (is_default != NULL) {
         *is_default = false;
     }
     return char2score(attr_value);
 }
 
 /*!
  * \internal
  * \brief Include promotion scores in instances' node scores and priorities
  *
  * \param[in,out] rsc  Promotable clone resource to update
  */
 void
 pcmk__add_promotion_scores(pcmk_resource_t *rsc)
 {
     if (pe__set_clone_flag(rsc,
                            pcmk__clone_promotion_added) == pcmk_rc_already) {
         return;
     }
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
 
         GHashTableIter iter;
         pcmk_node_t *node = NULL;
         int score, new_score;
 
         g_hash_table_iter_init(&iter, child_rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             if (!pcmk__node_available(node, false, false)) {
                 /* This node will never be promoted, so don't apply the
                  * promotion score, as that may lead to clone shuffling.
                  */
                 continue;
             }
 
             score = promotion_score(child_rsc, node, NULL);
             if (score > 0) {
                 new_score = pcmk__add_scores(node->weight, score);
                 if (new_score != node->weight) { // Could remain INFINITY
                     node->weight = new_score;
                     pcmk__rsc_trace(rsc,
                                     "Added %s promotion priority (%s) to score "
                                     "on %s (now %s)",
                                     child_rsc->id, pcmk_readable_score(score),
                                     pcmk__node_name(node),
                                     pcmk_readable_score(new_score));
                 }
             }
 
             if (score > child_rsc->priority) {
                 pcmk__rsc_trace(rsc,
                                 "Updating %s priority to promotion score "
                                 "(%d->%d)",
                                 child_rsc->id, child_rsc->priority, score);
                 child_rsc->priority = score;
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief If a resource's current role is started, change it to unpromoted
  *
  * \param[in,out] data       Resource to update
  * \param[in]     user_data  Ignored
  */
 static void
 set_current_role_unpromoted(void *data, void *user_data)
 {
     pcmk_resource_t *rsc = (pcmk_resource_t *) data;
 
     if (rsc->role == pcmk_role_started) {
         // Promotable clones should use unpromoted role instead of started
         rsc->role = pcmk_role_unpromoted;
     }
     g_list_foreach(rsc->children, set_current_role_unpromoted, NULL);
 }
 
 /*!
  * \internal
  * \brief Set a resource's next role to unpromoted (or stopped if unassigned)
  *
  * \param[in,out] data       Resource to update
  * \param[in]     user_data  Ignored
  */
 static void
 set_next_role_unpromoted(void *data, void *user_data)
 {
     pcmk_resource_t *rsc = (pcmk_resource_t *) data;
     GList *assigned = NULL;
 
     rsc->fns->location(rsc, &assigned, FALSE);
     if (assigned == NULL) {
         pe__set_next_role(rsc, pcmk_role_stopped, "stopped instance");
     } else {
         pe__set_next_role(rsc, pcmk_role_unpromoted, "unpromoted instance");
         g_list_free(assigned);
     }
     g_list_foreach(rsc->children, set_next_role_unpromoted, NULL);
 }
 
 /*!
  * \internal
  * \brief Set a resource's next role to promoted if not already set
  *
  * \param[in,out] data       Resource to update
  * \param[in]     user_data  Ignored
  */
 static void
 set_next_role_promoted(void *data, gpointer user_data)
 {
     pcmk_resource_t *rsc = (pcmk_resource_t *) data;
 
     if (rsc->next_role == pcmk_role_unknown) {
         pe__set_next_role(rsc, pcmk_role_promoted, "promoted instance");
     }
     g_list_foreach(rsc->children, set_next_role_promoted, NULL);
 }
 
 /*!
  * \internal
  * \brief Show instance's promotion score on node where it will be active
  *
  * \param[in,out] instance  Promotable clone instance to show
  */
 static void
 show_promotion_score(pcmk_resource_t *instance)
 {
     pcmk_node_t *chosen = instance->fns->location(instance, NULL, FALSE);
 
     if (pcmk_is_set(instance->cluster->flags, pcmk_sched_output_scores)
         && !pcmk__is_daemon && (instance->cluster->priv != NULL)) {
 
         pcmk__output_t *out = instance->cluster->priv;
 
         out->message(out, "promotion-score", instance, chosen,
                      pcmk_readable_score(instance->sort_index));
     } else {
         pcmk__rsc_debug(pe__const_top_resource(instance, false),
                         "%s promotion score on %s: sort=%s priority=%s",
                         instance->id,
                         ((chosen == NULL)? "none" : pcmk__node_name(chosen)),
                         pcmk_readable_score(instance->sort_index),
                         pcmk_readable_score(instance->priority));
     }
 }
 
 /*!
  * \internal
  * \brief Set a clone instance's promotion priority
  *
  * \param[in,out] data       Promotable clone instance to update
  * \param[in]     user_data  Instance's parent clone
  */
 static void
 set_instance_priority(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *instance = (pcmk_resource_t *) data;
     const pcmk_resource_t *clone = (const pcmk_resource_t *) user_data;
     const pcmk_node_t *chosen = NULL;
     enum rsc_role_e next_role = pcmk_role_unknown;
     GList *list = NULL;
 
     pcmk__rsc_trace(clone, "Assigning priority for %s: %s", instance->id,
                     pcmk_role_text(instance->next_role));
 
     if (instance->fns->state(instance, TRUE) == pcmk_role_started) {
         set_current_role_unpromoted(instance, NULL);
     }
 
     // Only an instance that will be active can be promoted
     chosen = instance->fns->location(instance, &list, FALSE);
     if (pcmk__list_of_multiple(list)) {
         pcmk__config_err("Cannot promote non-colocated child %s",
                          instance->id);
     }
     g_list_free(list);
     if (chosen == NULL) {
         return;
     }
 
     next_role = instance->fns->state(instance, FALSE);
     switch (next_role) {
         case pcmk_role_started:
         case pcmk_role_unknown:
             // Set instance priority to its promotion score (or -1 if none)
             {
                 bool is_default = false;
 
                 instance->priority = promotion_score(instance, chosen,
                                                       &is_default);
                 if (is_default) {
                     /* Default to -1 if no value is set. This allows instances
                      * eligible for promotion to be specified based solely on
                      * PCMK_XE_RSC_LOCATION constraints, but prevents any
                      * instance from being promoted if neither a constraint nor
                      * a promotion score is present.
                      */
                     instance->priority = -1;
                 }
             }
             break;
 
         case pcmk_role_unpromoted:
         case pcmk_role_stopped:
             // Instance can't be promoted
-            instance->priority = -INFINITY;
+            instance->priority = -PCMK_SCORE_INFINITY;
             break;
 
         case pcmk_role_promoted:
             // Nothing needed (re-creating actions after scheduling fencing)
             break;
 
         default:
             CRM_CHECK(FALSE, crm_err("Unknown resource role %d for %s",
                                      next_role, instance->id));
     }
 
     // Add relevant location constraint scores for promoted role
     apply_promoted_locations(instance, instance->rsc_location, chosen);
     apply_promoted_locations(instance, clone->rsc_location, chosen);
 
     // Consider instance's role-based colocations with other resources
     list = pcmk__this_with_colocations(instance);
     for (GList *iter = list; iter != NULL; iter = iter->next) {
         pcmk__colocation_t *cons = (pcmk__colocation_t *) iter->data;
 
         instance->cmds->apply_coloc_score(instance, cons->primary, cons, true);
     }
     g_list_free(list);
 
     instance->sort_index = instance->priority;
     if (next_role == pcmk_role_promoted) {
-        instance->sort_index = INFINITY;
+        instance->sort_index = PCMK_SCORE_INFINITY;
     }
     pcmk__rsc_trace(clone, "Assigning %s priority = %d",
                     instance->id, instance->priority);
 }
 
 /*!
  * \internal
  * \brief Set a promotable clone instance's role
  *
  * \param[in,out] data       Promotable clone instance to update
  * \param[in,out] user_data  Pointer to count of instances chosen for promotion
  */
 static void
 set_instance_role(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *instance = (pcmk_resource_t *) data;
     int *count = (int *) user_data;
 
     const pcmk_resource_t *clone = pe__const_top_resource(instance, false);
     pcmk_node_t *chosen = NULL;
 
     show_promotion_score(instance);
 
     if (instance->sort_index < 0) {
         pcmk__rsc_trace(clone, "Not supposed to promote instance %s",
                         instance->id);
 
     } else if ((*count < pe__clone_promoted_max(instance))
                || !pcmk_is_set(clone->flags, pcmk_rsc_managed)) {
         chosen = node_to_be_promoted_on(instance);
     }
 
     if (chosen == NULL) {
         set_next_role_unpromoted(instance, NULL);
         return;
     }
 
     if ((instance->role < pcmk_role_promoted)
         && !pcmk_is_set(instance->cluster->flags, pcmk_sched_quorate)
         && (instance->cluster->no_quorum_policy == pcmk_no_quorum_freeze)) {
         crm_notice("Clone instance %s cannot be promoted without quorum",
                    instance->id);
         set_next_role_unpromoted(instance, NULL);
         return;
     }
 
     chosen->count++;
     pcmk__rsc_info(clone, "Choosing %s (%s) on %s for promotion",
                    instance->id, pcmk_role_text(instance->role),
                    pcmk__node_name(chosen));
     set_next_role_promoted(instance, NULL);
     (*count)++;
 }
 
 /*!
  * \internal
  * \brief Set roles for all instances of a promotable clone
  *
  * \param[in,out] rsc  Promotable clone resource to update
  */
 void
 pcmk__set_instance_roles(pcmk_resource_t *rsc)
 {
     int promoted = 0;
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
 
     // Repurpose count to track the number of promoted instances assigned
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
         node->count = 0;
     }
 
     // Set instances' promotion priorities and sort by highest priority first
     g_list_foreach(rsc->children, set_instance_priority, rsc);
     sort_promotable_instances(rsc);
 
     // Choose the first N eligible instances to be promoted
     g_list_foreach(rsc->children, set_instance_role, &promoted);
     pcmk__rsc_info(rsc, "%s: Promoted %d instances of a possible %d",
                    rsc->id, promoted, pe__clone_promoted_max(rsc));
 }
 
 /*!
  *
  * \internal
  * \brief Create actions for promotable clone instances
  *
  * \param[in,out] clone          Promotable clone to create actions for
  * \param[out]    any_promoting  Will be set true if any instance is promoting
  * \param[out]    any_demoting   Will be set true if any instance is demoting
  */
 static void
 create_promotable_instance_actions(pcmk_resource_t *clone,
                                    bool *any_promoting, bool *any_demoting)
 {
     for (GList *iter = clone->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         instance->cmds->create_actions(instance);
         check_for_role_change(instance, any_demoting, any_promoting);
     }
 }
 
 /*!
  * \internal
  * \brief Reset each promotable instance's resource priority
  *
  * Reset the priority of each instance of a promotable clone to the clone's
  * priority (after promotion actions are scheduled, when instance priorities
  * were repurposed as promotion scores).
  *
  * \param[in,out] clone  Promotable clone to reset
  */
 static void
 reset_instance_priorities(pcmk_resource_t *clone)
 {
     for (GList *iter = clone->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         instance->priority = clone->priority;
     }
 }
 
 /*!
  * \internal
  * \brief Create actions specific to promotable clones
  *
  * \param[in,out] clone  Promotable clone to create actions for
  */
 void
 pcmk__create_promotable_actions(pcmk_resource_t *clone)
 {
     bool any_promoting = false;
     bool any_demoting = false;
 
     // Create actions for each clone instance individually
     create_promotable_instance_actions(clone, &any_promoting, &any_demoting);
 
     // Create pseudo-actions for clone as a whole
     pe__create_promotable_pseudo_ops(clone, any_promoting, any_demoting);
 
     // Undo our temporary repurposing of resource priority for instances
     reset_instance_priorities(clone);
 }
 
 /*!
  * \internal
  * \brief Create internal orderings for a promotable clone's instances
  *
  * \param[in,out] clone  Promotable clone instance to order
  */
 void
 pcmk__order_promotable_instances(pcmk_resource_t *clone)
 {
     pcmk_resource_t *previous = NULL; // Needed for ordered clones
 
     pcmk__promotable_restart_ordering(clone);
 
     for (GList *iter = clone->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
 
         // Demote before promote
         pcmk__order_resource_actions(instance, PCMK_ACTION_DEMOTE,
                                      instance, PCMK_ACTION_PROMOTE,
                                      pcmk__ar_ordered);
 
         order_instance_promotion(clone, instance, previous);
         order_instance_demotion(clone, instance, previous);
         previous = instance;
     }
 }
 
 /*!
  * \internal
  * \brief Update dependent's allowed nodes for colocation with promotable
  *
  * \param[in,out] dependent     Dependent resource to update
  * \param[in]     primary       Primary resource
  * \param[in]     primary_node  Node where an instance of the primary will be
  * \param[in]     colocation    Colocation constraint to apply
  */
 static void
 update_dependent_allowed_nodes(pcmk_resource_t *dependent,
                                const pcmk_resource_t *primary,
                                const pcmk_node_t *primary_node,
                                const pcmk__colocation_t *colocation)
 {
     GHashTableIter iter;
     pcmk_node_t *node = NULL;
     const char *primary_value = NULL;
     const char *attr = colocation->node_attribute;
 
-    if (colocation->score >= INFINITY) {
+    if (colocation->score >= PCMK_SCORE_INFINITY) {
         return; // Colocation is mandatory, so allowed node scores don't matter
     }
 
     primary_value = pcmk__colocation_node_attr(primary_node, attr, primary);
 
     pcmk__rsc_trace(colocation->primary,
                     "Applying %s (%s with %s on %s by %s @%d) to %s",
                     colocation->id, colocation->dependent->id,
                     colocation->primary->id, pcmk__node_name(primary_node),
                     attr, colocation->score, dependent->id);
 
     g_hash_table_iter_init(&iter, dependent->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
         const char *dependent_value = pcmk__colocation_node_attr(node, attr,
                                                                  dependent);
 
         if (pcmk__str_eq(primary_value, dependent_value, pcmk__str_casei)) {
             node->weight = pcmk__add_scores(node->weight, colocation->score);
             pcmk__rsc_trace(colocation->primary,
                             "Added %s score (%s) to %s (now %s)",
                             colocation->id,
                             pcmk_readable_score(colocation->score),
                             pcmk__node_name(node),
                             pcmk_readable_score(node->weight));
         }
     }
 }
 
 /*!
  * \brief Update dependent for a colocation with a promotable clone
  *
  * \param[in]     primary     Primary resource in the colocation
  * \param[in,out] dependent   Dependent resource in the colocation
  * \param[in]     colocation  Colocation constraint to apply
  */
 void
 pcmk__update_dependent_with_promotable(const pcmk_resource_t *primary,
                                        pcmk_resource_t *dependent,
                                        const pcmk__colocation_t *colocation)
 {
     GList *affected_nodes = NULL;
 
     /* Build a list of all nodes where an instance of the primary will be, and
      * (for optional colocations) update the dependent's allowed node scores for
      * each one.
      */
     for (GList *iter = primary->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
         pcmk_node_t *node = instance->fns->location(instance, NULL, FALSE);
 
         if (node == NULL) {
             continue;
         }
         if (instance->fns->state(instance, FALSE) == colocation->primary_role) {
             update_dependent_allowed_nodes(dependent, primary, node,
                                            colocation);
             affected_nodes = g_list_prepend(affected_nodes, node);
         }
     }
 
     /* For mandatory colocations, add the primary's node score to the
      * dependent's node score for each affected node, and ban the dependent
      * from all other nodes.
      *
      * However, skip this for promoted-with-promoted colocations, otherwise
      * inactive dependent instances can't start (in the unpromoted role).
      */
-    if ((colocation->score >= INFINITY)
+    if ((colocation->score >= PCMK_SCORE_INFINITY)
         && ((colocation->dependent_role != pcmk_role_promoted)
             || (colocation->primary_role != pcmk_role_promoted))) {
 
         pcmk__rsc_trace(colocation->primary,
                         "Applying %s (mandatory %s with %s) to %s",
                         colocation->id, colocation->dependent->id,
                         colocation->primary->id, dependent->id);
         pcmk__colocation_intersect_nodes(dependent, primary, colocation,
                                          affected_nodes, true);
     }
     g_list_free(affected_nodes);
 }
 
 /*!
  * \internal
  * \brief Update dependent priority for colocation with promotable
  *
  * \param[in]     primary     Primary resource in the colocation
  * \param[in,out] dependent   Dependent resource in the colocation
  * \param[in]     colocation  Colocation constraint to apply
  */
 void
 pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
                                            pcmk_resource_t *dependent,
                                            const pcmk__colocation_t *colocation)
 {
     pcmk_resource_t *primary_instance = NULL;
 
     // Look for a primary instance where dependent will be
     primary_instance = pcmk__find_compatible_instance(dependent, primary,
                                                       colocation->primary_role,
                                                       false);
 
     if (primary_instance != NULL) {
         // Add primary instance's priority to dependent's
         int new_priority = pcmk__add_scores(dependent->priority,
                                             colocation->score);
 
         pcmk__rsc_trace(colocation->primary,
                         "Applying %s (%s with %s) to %s priority "
                         "(%s + %s = %s)",
                         colocation->id, colocation->dependent->id,
                         colocation->primary->id, dependent->id,
                         pcmk_readable_score(dependent->priority),
                         pcmk_readable_score(colocation->score),
                         pcmk_readable_score(new_priority));
         dependent->priority = new_priority;
 
-    } else if (colocation->score >= INFINITY) {
+    } else if (colocation->score >= PCMK_SCORE_INFINITY) {
         // Mandatory colocation, but primary won't be here
         pcmk__rsc_trace(colocation->primary,
                         "Applying %s (%s with %s) to %s: can't be promoted",
                         colocation->id, colocation->dependent->id,
                         colocation->primary->id, dependent->id);
-        dependent->priority = -INFINITY;
+        dependent->priority = -PCMK_SCORE_INFINITY;
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_resource.c b/lib/pacemaker/pcmk_sched_resource.c
index bc21169f96..0791994f8f 100644
--- a/lib/pacemaker/pcmk_sched_resource.c
+++ b/lib/pacemaker/pcmk_sched_resource.c
@@ -1,774 +1,774 @@
 /*
  * Copyright 2014-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 <crm_internal.h>
 
 #include <stdlib.h>
 #include <string.h>
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 // Resource assignment methods by resource variant
 static pcmk_assignment_methods_t assignment_methods[] = {
     {
         pcmk__primitive_assign,
         pcmk__primitive_create_actions,
         pcmk__probe_rsc_on_node,
         pcmk__primitive_internal_constraints,
         pcmk__primitive_apply_coloc_score,
         pcmk__colocated_resources,
         pcmk__with_primitive_colocations,
         pcmk__primitive_with_colocations,
         pcmk__add_colocated_node_scores,
         pcmk__apply_location,
         pcmk__primitive_action_flags,
         pcmk__update_ordered_actions,
         pcmk__output_resource_actions,
         pcmk__add_rsc_actions_to_graph,
         pcmk__primitive_add_graph_meta,
         pcmk__primitive_add_utilization,
         pcmk__primitive_shutdown_lock,
     },
     {
         pcmk__group_assign,
         pcmk__group_create_actions,
         pcmk__probe_rsc_on_node,
         pcmk__group_internal_constraints,
         pcmk__group_apply_coloc_score,
         pcmk__group_colocated_resources,
         pcmk__with_group_colocations,
         pcmk__group_with_colocations,
         pcmk__group_add_colocated_node_scores,
         pcmk__group_apply_location,
         pcmk__group_action_flags,
         pcmk__group_update_ordered_actions,
         pcmk__output_resource_actions,
         pcmk__add_rsc_actions_to_graph,
         pcmk__noop_add_graph_meta,
         pcmk__group_add_utilization,
         pcmk__group_shutdown_lock,
     },
     {
         pcmk__clone_assign,
         pcmk__clone_create_actions,
         pcmk__clone_create_probe,
         pcmk__clone_internal_constraints,
         pcmk__clone_apply_coloc_score,
         pcmk__colocated_resources,
         pcmk__with_clone_colocations,
         pcmk__clone_with_colocations,
         pcmk__add_colocated_node_scores,
         pcmk__clone_apply_location,
         pcmk__clone_action_flags,
         pcmk__instance_update_ordered_actions,
         pcmk__output_resource_actions,
         pcmk__clone_add_actions_to_graph,
         pcmk__clone_add_graph_meta,
         pcmk__clone_add_utilization,
         pcmk__clone_shutdown_lock,
     },
     {
         pcmk__bundle_assign,
         pcmk__bundle_create_actions,
         pcmk__bundle_create_probe,
         pcmk__bundle_internal_constraints,
         pcmk__bundle_apply_coloc_score,
         pcmk__colocated_resources,
         pcmk__with_bundle_colocations,
         pcmk__bundle_with_colocations,
         pcmk__add_colocated_node_scores,
         pcmk__bundle_apply_location,
         pcmk__bundle_action_flags,
         pcmk__instance_update_ordered_actions,
         pcmk__output_bundle_actions,
         pcmk__bundle_add_actions_to_graph,
         pcmk__noop_add_graph_meta,
         pcmk__bundle_add_utilization,
         pcmk__bundle_shutdown_lock,
     }
 };
 
 /*!
  * \internal
  * \brief Check whether a resource's agent standard, provider, or type changed
  *
  * \param[in,out] rsc             Resource to check
  * \param[in,out] node            Node needing unfencing if agent changed
  * \param[in]     rsc_entry       XML with previously known agent information
  * \param[in]     active_on_node  Whether \p rsc is active on \p node
  *
  * \return true if agent for \p rsc changed, otherwise false
  */
 bool
 pcmk__rsc_agent_changed(pcmk_resource_t *rsc, pcmk_node_t *node,
                         const xmlNode *rsc_entry, bool active_on_node)
 {
     bool changed = false;
     const char *attr_list[] = {
         PCMK_XA_TYPE,
         PCMK_XA_CLASS,
         PCMK_XA_PROVIDER,
     };
 
     for (int i = 0; i < PCMK__NELEM(attr_list); i++) {
         const char *value = crm_element_value(rsc->xml, attr_list[i]);
         const char *old_value = crm_element_value(rsc_entry, attr_list[i]);
 
         if (!pcmk__str_eq(value, old_value, pcmk__str_none)) {
             changed = true;
             trigger_unfencing(rsc, node, "Device definition changed", NULL,
                               rsc->cluster);
             if (active_on_node) {
                 crm_notice("Forcing restart of %s on %s "
                            "because %s changed from '%s' to '%s'",
                            rsc->id, pcmk__node_name(node), attr_list[i],
                            pcmk__s(old_value, ""), pcmk__s(value, ""));
             }
         }
     }
     if (changed && active_on_node) {
         // Make sure the resource is restarted
         custom_action(rsc, stop_key(rsc), PCMK_ACTION_STOP, node, FALSE,
                       rsc->cluster);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_start_pending);
     }
     return changed;
 }
 
 /*!
  * \internal
  * \brief Add resource (and any matching children) to list if it matches ID
  *
  * \param[in] result  List to add resource to
  * \param[in] rsc     Resource to check
  * \param[in] id      ID to match
  *
  * \return (Possibly new) head of list
  */
 static GList *
 add_rsc_if_matching(GList *result, pcmk_resource_t *rsc, const char *id)
 {
     if ((strcmp(rsc->id, id) == 0)
         || ((rsc->clone_name != NULL) && (strcmp(rsc->clone_name, id) == 0))) {
         result = g_list_prepend(result, rsc);
     }
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
 
         result = add_rsc_if_matching(result, child, id);
     }
     return result;
 }
 
 /*!
  * \internal
  * \brief Find all resources matching a given ID by either ID or clone name
  *
  * \param[in] id         Resource ID to check
  * \param[in] scheduler  Scheduler data
  *
  * \return List of all resources that match \p id
  * \note The caller is responsible for freeing the return value with
  *       g_list_free().
  */
 GList *
 pcmk__rscs_matching_id(const char *id, const pcmk_scheduler_t *scheduler)
 {
     GList *result = NULL;
 
     CRM_CHECK((id != NULL) && (scheduler != NULL), return NULL);
     for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
         result = add_rsc_if_matching(result, (pcmk_resource_t *) iter->data,
                                      id);
     }
     return result;
 }
 
 /*!
  * \internal
  * \brief Set the variant-appropriate assignment methods for a resource
  *
  * \param[in,out] data       Resource to set assignment methods for
  * \param[in]     user_data  Ignored
  */
 static void
 set_assignment_methods_for_rsc(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
 
     rsc->cmds = &assignment_methods[rsc->variant];
     g_list_foreach(rsc->children, set_assignment_methods_for_rsc, NULL);
 }
 
 /*!
  * \internal
  * \brief Set the variant-appropriate assignment methods for all resources
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__set_assignment_methods(pcmk_scheduler_t *scheduler)
 {
     g_list_foreach(scheduler->resources, set_assignment_methods_for_rsc, NULL);
 }
 
 /*!
  * \internal
  * \brief Wrapper for colocated_resources() method for readability
  *
  * \param[in]      rsc       Resource to add to colocated list
  * \param[in]      orig_rsc  Resource originally requested
  * \param[in,out]  list      Pointer to list to add to
  *
  * \return (Possibly new) head of list
  */
 static inline void
 add_colocated_resources(const pcmk_resource_t *rsc,
                         const pcmk_resource_t *orig_rsc, GList **list)
 {
     *list = rsc->cmds->colocated_resources(rsc, orig_rsc, *list);
 }
 
 // Shared implementation of pcmk_assignment_methods_t:colocated_resources()
 GList *
 pcmk__colocated_resources(const pcmk_resource_t *rsc,
                           const pcmk_resource_t *orig_rsc,
                           GList *colocated_rscs)
 {
     const GList *iter = NULL;
     GList *colocations = NULL;
 
     if (orig_rsc == NULL) {
         orig_rsc = rsc;
     }
 
     if ((rsc == NULL) || (g_list_find(colocated_rscs, rsc) != NULL)) {
         return colocated_rscs;
     }
 
     pcmk__rsc_trace(orig_rsc, "%s is in colocation chain with %s",
                     rsc->id, orig_rsc->id);
     colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
 
     // Follow colocations where this resource is the dependent resource
     colocations = pcmk__this_with_colocations(rsc);
     for (iter = colocations; iter != NULL; iter = iter->next) {
         const pcmk__colocation_t *constraint = iter->data;
         const pcmk_resource_t *primary = constraint->primary;
 
         if (primary == orig_rsc) {
             continue; // Break colocation loop
         }
 
-        if ((constraint->score == INFINITY) &&
+        if ((constraint->score == PCMK_SCORE_INFINITY) &&
             (pcmk__colocation_affects(rsc, primary, constraint,
                                       true) == pcmk__coloc_affects_location)) {
             add_colocated_resources(primary, orig_rsc, &colocated_rscs);
         }
     }
     g_list_free(colocations);
 
     // Follow colocations where this resource is the primary resource
     colocations = pcmk__with_this_colocations(rsc);
     for (iter = colocations; iter != NULL; iter = iter->next) {
         const pcmk__colocation_t *constraint = iter->data;
         const pcmk_resource_t *dependent = constraint->dependent;
 
         if (dependent == orig_rsc) {
             continue; // Break colocation loop
         }
 
         if (pcmk__is_clone(rsc) && !pcmk__is_clone(dependent)) {
             continue; // We can't be sure whether dependent will be colocated
         }
 
-        if ((constraint->score == INFINITY) &&
+        if ((constraint->score == PCMK_SCORE_INFINITY) &&
             (pcmk__colocation_affects(dependent, rsc, constraint,
                                       true) == pcmk__coloc_affects_location)) {
             add_colocated_resources(dependent, orig_rsc, &colocated_rscs);
         }
     }
     g_list_free(colocations);
 
     return colocated_rscs;
 }
 
 // No-op function for variants that don't need to implement add_graph_meta()
 void
 pcmk__noop_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml)
 {
 }
 
 /*!
  * \internal
  * \brief Output a summary of scheduled actions for a resource
  *
  * \param[in,out] rsc  Resource to output actions for
  */
 void
 pcmk__output_resource_actions(pcmk_resource_t *rsc)
 {
     pcmk_node_t *next = NULL;
     pcmk_node_t *current = NULL;
     pcmk__output_t *out = NULL;
 
     CRM_ASSERT(rsc != NULL);
 
     out = rsc->cluster->priv;
     if (rsc->children != NULL) {
         for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
             pcmk_resource_t *child = (pcmk_resource_t *) iter->data;
 
             child->cmds->output_actions(child);
         }
         return;
     }
 
     next = rsc->allocated_to;
     if (rsc->running_on) {
         current = pcmk__current_node(rsc);
         if (rsc->role == pcmk_role_stopped) {
             /* This can occur when resources are being recovered because
              * the current role can change in pcmk__primitive_create_actions()
              */
             rsc->role = pcmk_role_started;
         }
     }
 
     if ((current == NULL) && pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
         /* Don't log stopped orphans */
         return;
     }
 
     out->message(out, "rsc-action", rsc, current, next);
 }
 
 /*!
  * \internal
  * \brief Add a resource to a node's list of assigned resources
  *
  * \param[in,out] node  Node to add resource to
  * \param[in]     rsc   Resource to add
  */
 static inline void
 add_assigned_resource(pcmk_node_t *node, pcmk_resource_t *rsc)
 {
     node->details->allocated_rsc = g_list_prepend(node->details->allocated_rsc,
                                                   rsc);
 }
 
 /*!
  * \internal
  * \brief Assign a specified resource (of any variant) to a node
  *
  * Assign a specified resource and its children (if any) to a specified node, if
  * the node can run the resource (or unconditionally, if \p force is true). Mark
  * the resources as no longer provisional.
  *
  * If a resource can't be assigned (or \p node is \c NULL), unassign any
  * previous assignment. If \p stop_if_fail is \c true, set next role to stopped
  * and update any existing actions scheduled for the resource.
  *
  * \param[in,out] rsc           Resource to assign
  * \param[in,out] node          Node to assign \p rsc to
  * \param[in]     force         If true, assign to \p node even if unavailable
  * \param[in]     stop_if_fail  If \c true and either \p rsc can't be assigned
  *                              or \p chosen is \c NULL, set next role to
  *                              stopped and update existing actions (if \p rsc
  *                              is not a primitive, this applies to its
  *                              primitive descendants instead)
  *
  * \return \c true if the assignment of \p rsc changed, or \c false otherwise
  *
  * \note Assigning a resource to the NULL node using this function is different
  *       from calling pcmk__unassign_resource(), in that it may also update any
  *       actions created for the resource.
  * \note The \c pcmk_assignment_methods_t:assign() method is preferred, unless
  *       a resource should be assigned to the \c NULL node or every resource in
  *       a tree should be assigned to the same node.
  * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
  *       completely undo the assignment. A successful assignment can be either
  *       undone or left alone as final. A failed assignment has the same effect
  *       as calling pcmk__unassign_resource(); there are no side effects on
  *       roles or actions.
  */
 bool
 pcmk__assign_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool force,
                       bool stop_if_fail)
 {
     bool changed = false;
 
     CRM_ASSERT(rsc != NULL);
 
     if (rsc->children != NULL) {
         for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
             pcmk_resource_t *child_rsc = iter->data;
 
             changed |= pcmk__assign_resource(child_rsc, node, force,
                                              stop_if_fail);
         }
         return changed;
     }
 
     // Assigning a primitive
 
     if (!force && (node != NULL)
         && ((node->weight < 0)
             // Allow graph to assume that guest node connections will come up
             || (!pcmk__node_available(node, true, false)
                 && !pcmk__is_guest_or_bundle_node(node)))) {
 
         pcmk__rsc_debug(rsc,
                         "All nodes for resource %s are unavailable, unclean or "
                         "shutting down (%s can%s run resources, with score %s)",
                         rsc->id, pcmk__node_name(node),
                         (pcmk__node_available(node, true, false)? "" : "not"),
                         pcmk_readable_score(node->weight));
 
         if (stop_if_fail) {
             pe__set_next_role(rsc, pcmk_role_stopped, "node availability");
         }
         node = NULL;
     }
 
     if (rsc->allocated_to != NULL) {
         changed = !pcmk__same_node(rsc->allocated_to, node);
     } else {
         changed = (node != NULL);
     }
     pcmk__unassign_resource(rsc);
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_unassigned);
 
     if (node == NULL) {
         char *rc_stopped = NULL;
 
         pcmk__rsc_debug(rsc, "Could not assign %s to a node", rsc->id);
 
         if (!stop_if_fail) {
             return changed;
         }
         pe__set_next_role(rsc, pcmk_role_stopped, "unable to assign");
 
         for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) {
             pcmk_action_t *op = (pcmk_action_t *) iter->data;
 
             pcmk__rsc_debug(rsc, "Updating %s for %s assignment failure",
                             op->uuid, rsc->id);
 
             if (pcmk__str_eq(op->task, PCMK_ACTION_STOP, pcmk__str_none)) {
                 pcmk__clear_action_flags(op, pcmk_action_optional);
 
             } else if (pcmk__str_eq(op->task, PCMK_ACTION_START,
                                     pcmk__str_none)) {
                 pcmk__clear_action_flags(op, pcmk_action_runnable);
 
             } else {
                 // Cancel recurring actions, unless for stopped state
                 const char *interval_ms_s = NULL;
                 const char *target_rc_s = NULL;
 
                 interval_ms_s = g_hash_table_lookup(op->meta,
                                                     PCMK_META_INTERVAL);
                 target_rc_s = g_hash_table_lookup(op->meta,
                                                   PCMK__META_OP_TARGET_RC);
                 if (rc_stopped == NULL) {
                     rc_stopped = pcmk__itoa(PCMK_OCF_NOT_RUNNING);
                 }
 
                 if (!pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)
                     && !pcmk__str_eq(rc_stopped, target_rc_s, pcmk__str_none)) {
 
                     pcmk__clear_action_flags(op, pcmk_action_runnable);
                 }
             }
         }
         free(rc_stopped);
         return changed;
     }
 
     pcmk__rsc_debug(rsc, "Assigning %s to %s", rsc->id, pcmk__node_name(node));
     rsc->allocated_to = pe__copy_node(node);
 
     add_assigned_resource(node, rsc);
     node->details->num_resources++;
     node->count++;
     pcmk__consume_node_capacity(node->details->utilization, rsc);
 
     if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_show_utilization)) {
         pcmk__output_t *out = rsc->cluster->priv;
 
         out->message(out, "resource-util", rsc, node, __func__);
     }
     return changed;
 }
 
 /*!
  * \internal
  * \brief Remove any node assignment from a specified resource and its children
  *
  * If a specified resource has been assigned to a node, remove that assignment
  * and mark the resource as provisional again.
  *
  * \param[in,out] rsc  Resource to unassign
  *
  * \note This function is called recursively on \p rsc and its children.
  */
 void
 pcmk__unassign_resource(pcmk_resource_t *rsc)
 {
     pcmk_node_t *old = rsc->allocated_to;
 
     if (old == NULL) {
         crm_info("Unassigning %s", rsc->id);
     } else {
         crm_info("Unassigning %s from %s", rsc->id, pcmk__node_name(old));
     }
 
     pcmk__set_rsc_flags(rsc, pcmk_rsc_unassigned);
 
     if (rsc->children == NULL) {
         if (old == NULL) {
             return;
         }
         rsc->allocated_to = NULL;
 
         /* We're going to free the pcmk_node_t, but its details member is shared
          * and will remain, so update that appropriately first.
          */
         old->details->allocated_rsc = g_list_remove(old->details->allocated_rsc,
                                                     rsc);
         old->details->num_resources--;
         pcmk__release_node_capacity(old->details->utilization, rsc);
         free(old);
         return;
     }
 
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         pcmk__unassign_resource((pcmk_resource_t *) iter->data);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a resource has reached its migration threshold on a node
  *
  * \param[in,out] rsc       Resource to check
  * \param[in]     node      Node to check
  * \param[out]    failed    If threshold has been reached, this will be set to
  *                          resource that failed (possibly a parent of \p rsc)
  *
  * \return true if the migration threshold has been reached, false otherwise
  */
 bool
 pcmk__threshold_reached(pcmk_resource_t *rsc, const pcmk_node_t *node,
                         pcmk_resource_t **failed)
 {
     int fail_count, remaining_tries;
     pcmk_resource_t *rsc_to_ban = rsc;
 
     // Migration threshold of 0 means never force away
     if (rsc->migration_threshold == 0) {
         return false;
     }
 
     // If we're ignoring failures, also ignore the migration threshold
     if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) {
         return false;
     }
 
     // If there are no failures, there's no need to force away
     fail_count = pe_get_failcount(node, rsc, NULL,
                                   pcmk__fc_effective|pcmk__fc_fillers, NULL);
     if (fail_count <= 0) {
         return false;
     }
 
     // If failed resource is anonymous clone instance, we'll force clone away
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         rsc_to_ban = uber_parent(rsc);
     }
 
     // How many more times recovery will be tried on this node
     remaining_tries = rsc->migration_threshold - fail_count;
 
     if (remaining_tries <= 0) {
         pcmk__sched_warn("%s cannot run on %s due to reaching migration "
                          "threshold (clean up resource to allow again)"
                          CRM_XS " failures=%d "
                          PCMK_META_MIGRATION_THRESHOLD "=%d",
                          rsc_to_ban->id, pcmk__node_name(node), fail_count,
                          rsc->migration_threshold);
         if (failed != NULL) {
             *failed = rsc_to_ban;
         }
         return true;
     }
 
     crm_info("%s can fail %d more time%s on "
              "%s before reaching migration threshold (%d)",
              rsc_to_ban->id, remaining_tries, pcmk__plural_s(remaining_tries),
              pcmk__node_name(node), rsc->migration_threshold);
     return false;
 }
 
 /*!
  * \internal
  * \brief Get a node's score
  *
  * \param[in] node     Node with ID to check
  * \param[in] nodes    List of nodes to look for \p node score in
  *
  * \return Node's score, or -INFINITY if not found
  */
 static int
 get_node_score(const pcmk_node_t *node, GHashTable *nodes)
 {
     pcmk_node_t *found_node = NULL;
 
     if ((node != NULL) && (nodes != NULL)) {
         found_node = g_hash_table_lookup(nodes, node->details->id);
     }
-    return (found_node == NULL)? -INFINITY : found_node->weight;
+    return (found_node == NULL)? -PCMK_SCORE_INFINITY : found_node->weight;
 }
 
 /*!
  * \internal
  * \brief Compare two resources according to which should be assigned first
  *
  * \param[in] a     First resource to compare
  * \param[in] b     Second resource to compare
  * \param[in] data  Sorted list of all nodes in cluster
  *
  * \return -1 if \p a should be assigned before \b, 0 if they are equal,
  *         or +1 if \p a should be assigned after \b
  */
 static gint
 cmp_resources(gconstpointer a, gconstpointer b, gpointer data)
 {
     /* GLib insists that this function require gconstpointer arguments, but we
      * make a small, temporary change to each argument (setting the
      * pe_rsc_merging flag) during comparison
      */
     pcmk_resource_t *resource1 = (pcmk_resource_t *) a;
     pcmk_resource_t *resource2 = (pcmk_resource_t *) b;
     const GList *nodes = data;
 
     int rc = 0;
-    int r1_score = -INFINITY;
-    int r2_score = -INFINITY;
+    int r1_score = -PCMK_SCORE_INFINITY;
+    int r2_score = -PCMK_SCORE_INFINITY;
     pcmk_node_t *r1_node = NULL;
     pcmk_node_t *r2_node = NULL;
     GHashTable *r1_nodes = NULL;
     GHashTable *r2_nodes = NULL;
     const char *reason = NULL;
 
     // Resources with highest priority should be assigned first
     reason = "priority";
     r1_score = resource1->priority;
     r2_score = resource2->priority;
     if (r1_score > r2_score) {
         rc = -1;
         goto done;
     }
     if (r1_score < r2_score) {
         rc = 1;
         goto done;
     }
 
     // We need nodes to make any other useful comparisons
     reason = "no node list";
     if (nodes == NULL) {
         goto done;
     }
 
     // Calculate and log node scores
     resource1->cmds->add_colocated_node_scores(resource1, NULL, resource1->id,
                                                &r1_nodes, NULL, 1,
                                                pcmk__coloc_select_this_with);
     resource2->cmds->add_colocated_node_scores(resource2, NULL, resource2->id,
                                                &r2_nodes, NULL, 1,
                                                pcmk__coloc_select_this_with);
     pe__show_node_scores(true, NULL, resource1->id, r1_nodes,
                          resource1->cluster);
     pe__show_node_scores(true, NULL, resource2->id, r2_nodes,
                          resource2->cluster);
 
     // The resource with highest score on its current node goes first
     reason = "current location";
     if (resource1->running_on != NULL) {
         r1_node = pcmk__current_node(resource1);
     }
     if (resource2->running_on != NULL) {
         r2_node = pcmk__current_node(resource2);
     }
     r1_score = get_node_score(r1_node, r1_nodes);
     r2_score = get_node_score(r2_node, r2_nodes);
     if (r1_score > r2_score) {
         rc = -1;
         goto done;
     }
     if (r1_score < r2_score) {
         rc = 1;
         goto done;
     }
 
     // Otherwise a higher score on any node will do
     reason = "score";
     for (const GList *iter = nodes; iter != NULL; iter = iter->next) {
         const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
 
         r1_score = get_node_score(node, r1_nodes);
         r2_score = get_node_score(node, r2_nodes);
         if (r1_score > r2_score) {
             rc = -1;
             goto done;
         }
         if (r1_score < r2_score) {
             rc = 1;
             goto done;
         }
     }
 
 done:
     crm_trace("%s (%d)%s%s %c %s (%d)%s%s: %s",
               resource1->id, r1_score,
               ((r1_node == NULL)? "" : " on "),
               ((r1_node == NULL)? "" : r1_node->details->id),
               ((rc < 0)? '>' : ((rc > 0)? '<' : '=')),
               resource2->id, r2_score,
               ((r2_node == NULL)? "" : " on "),
               ((r2_node == NULL)? "" : r2_node->details->id),
               reason);
     if (r1_nodes != NULL) {
         g_hash_table_destroy(r1_nodes);
     }
     if (r2_nodes != NULL) {
         g_hash_table_destroy(r2_nodes);
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Sort resources in the order they should be assigned to nodes
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__sort_resources(pcmk_scheduler_t *scheduler)
 {
     GList *nodes = g_list_copy(scheduler->nodes);
 
     nodes = pcmk__sort_nodes(nodes, NULL);
     scheduler->resources = g_list_sort_with_data(scheduler->resources,
                                                  cmp_resources, nodes);
     g_list_free(nodes);
 }
diff --git a/lib/pacemaker/pcmk_sched_tickets.c b/lib/pacemaker/pcmk_sched_tickets.c
index 0eb6e8513c..38f0eb09f2 100644
--- a/lib/pacemaker/pcmk_sched_tickets.c
+++ b/lib/pacemaker/pcmk_sched_tickets.c
@@ -1,532 +1,532 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdbool.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/common/scheduler_internal.h>
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 enum loss_ticket_policy {
     loss_ticket_stop,
     loss_ticket_demote,
     loss_ticket_fence,
     loss_ticket_freeze
 };
 
 typedef struct {
     const char *id;
     pcmk_resource_t *rsc;
     pcmk_ticket_t *ticket;
     enum loss_ticket_policy loss_policy;
     int role;
 } rsc_ticket_t;
 
 /*!
  * \brief Check whether a ticket constraint matches a resource by role
  *
  * \param[in] rsc_ticket  Ticket constraint
  * \param[in] rsc         Resource to compare with ticket
  *
  * \param[in] true if constraint has no role or resource's role matches
  *            constraint's, otherwise false
  */
 static bool
 ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
 {
     if ((rsc_ticket->role == pcmk_role_unknown)
         || (rsc_ticket->role == rsc->role)) {
         return true;
     }
     pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
                     pcmk_role_text(rsc_ticket->role));
     return false;
 }
 
 /*!
  * \brief Create location constraints and fencing as needed for a ticket
  *
  * \param[in,out] rsc         Resource affected by ticket
  * \param[in]     rsc_ticket  Ticket
  */
 static void
 constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
 {
     GList *iter = NULL;
 
     CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
 
     if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
         return;
     }
 
     if (rsc->children) {
         pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
         for (iter = rsc->children; iter != NULL; iter = iter->next) {
             constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket);
         }
         return;
     }
 
     pcmk__rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
                     rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
                     pcmk_role_text(rsc_ticket->role));
 
     if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
 
         switch (rsc_ticket->loss_policy) {
             case loss_ticket_stop:
-                resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
-                                  rsc->cluster);
+                resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                                  "__loss_of_ticket__", rsc->cluster);
                 break;
 
             case loss_ticket_demote:
                 // Promotion score will be set to -INFINITY in promotion_order()
                 if (rsc_ticket->role != pcmk_role_promoted) {
-                    resource_location(rsc, NULL, -INFINITY,
+                    resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
                                       "__loss_of_ticket__", rsc->cluster);
                 }
                 break;
 
             case loss_ticket_fence:
                 if (!ticket_role_matches(rsc, rsc_ticket)) {
                     return;
                 }
 
-                resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
-                                  rsc->cluster);
+                resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                                  "__loss_of_ticket__", rsc->cluster);
 
                 for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
                     pe_fence_node(rsc->cluster, (pcmk_node_t *) iter->data,
                                   "deadman ticket was lost", FALSE);
                 }
                 break;
 
             case loss_ticket_freeze:
                 if (!ticket_role_matches(rsc, rsc_ticket)) {
                     return;
                 }
                 if (rsc->running_on != NULL) {
                     pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
                     pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
                 }
                 break;
         }
 
     } else if (!rsc_ticket->ticket->granted) {
 
         if ((rsc_ticket->role != pcmk_role_promoted)
             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
-            resource_location(rsc, NULL, -INFINITY, "__no_ticket__",
-                              rsc->cluster);
+            resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                              "__no_ticket__", rsc->cluster);
         }
 
     } else if (rsc_ticket->ticket->standby) {
 
         if ((rsc_ticket->role != pcmk_role_promoted)
             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
-            resource_location(rsc, NULL, -INFINITY, "__ticket_standby__",
-                              rsc->cluster);
+            resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                              "__ticket_standby__", rsc->cluster);
         }
     }
 }
 
 static void
 rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk_ticket_t *ticket,
                const char *state, const char *loss_policy)
 {
     rsc_ticket_t *new_rsc_ticket = NULL;
 
     if (rsc == NULL) {
         pcmk__config_err("Ignoring ticket '%s' because resource "
                          "does not exist", id);
         return;
     }
 
     new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
     if (new_rsc_ticket == NULL) {
         return;
     }
 
     if (pcmk__str_eq(state, PCMK__ROLE_STARTED,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         state = PCMK__ROLE_UNKNOWN;
     }
 
     new_rsc_ticket->id = id;
     new_rsc_ticket->ticket = ticket;
     new_rsc_ticket->rsc = rsc;
     new_rsc_ticket->role = pcmk_parse_role(state);
 
     if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) {
         if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
             new_rsc_ticket->loss_policy = loss_ticket_fence;
         } else {
             pcmk__config_err("Resetting '" PCMK_XA_LOSS_POLICY "' "
                              "for ticket '%s' to '" PCMK_VALUE_STOP "' "
                              "because fencing is not configured", ticket->id);
             loss_policy = PCMK_VALUE_STOP;
         }
     }
 
     if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
         crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   pcmk_role_text(new_rsc_ticket->role));
 
     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Freeze %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   pcmk_role_text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_freeze;
 
     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Demote %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   pcmk_role_text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_demote;
 
     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) {
         crm_debug("On loss of ticket '%s': Stop %s (%s)",
                   new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                   pcmk_role_text(new_rsc_ticket->role));
         new_rsc_ticket->loss_policy = loss_ticket_stop;
 
     } else {
         if (new_rsc_ticket->role == pcmk_role_promoted) {
             crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
                       new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                       pcmk_role_text(new_rsc_ticket->role));
             new_rsc_ticket->loss_policy = loss_ticket_demote;
 
         } else {
             crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
                       new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
                       pcmk_role_text(new_rsc_ticket->role));
             new_rsc_ticket->loss_policy = loss_ticket_stop;
         }
     }
 
     pcmk__rsc_trace(rsc, "%s (%s) ==> %s",
                     rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id);
 
     rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
 
     rsc->cluster->ticket_constraints = g_list_append(
         rsc->cluster->ticket_constraints, new_rsc_ticket);
 
     if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
         constraints_for_ticket(rsc, new_rsc_ticket);
     }
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_rsc_ticket_set(xmlNode *set, pcmk_ticket_t *ticket,
                       const char *loss_policy, pcmk_scheduler_t *scheduler)
 {
     const char *set_id = NULL;
     const char *role = NULL;
 
     CRM_CHECK(set != NULL, return EINVAL);
     CRM_CHECK(ticket != NULL, return EINVAL);
 
     set_id = pcmk__xe_id(set);
     if (set_id == NULL) {
         pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without "
                          PCMK_XA_ID);
         return pcmk_rc_unpack_error;
     }
 
     role = crm_element_value(set, PCMK_XA_ROLE);
 
     for (xmlNode *xml_rsc = first_named_child(set, PCMK_XE_RESOURCE_REF);
          xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 
         pcmk_resource_t *resource = NULL;
 
         resource = pcmk__find_constraint_resource(scheduler->resources,
                                                   pcmk__xe_id(xml_rsc));
         if (resource == NULL) {
             pcmk__config_err("%s: No resource found for %s",
                              set_id, pcmk__xe_id(xml_rsc));
             return pcmk_rc_unpack_error;
         }
         pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
                         resource->id, ticket->id);
         rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
     }
 
     return pcmk_rc_ok;
 }
 
 static void
 unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
     const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
 
     pcmk_ticket_t *ticket = NULL;
 
     const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 
     // @COMPAT: Deprecated since 2.1.5
     const char *instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
 
     pcmk_resource_t *rsc = NULL;
 
     if (instance != NULL) {
         pcmk__warn_once(pcmk__wo_coloc_inst,
                         "Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
                         "and will be removed in a future release");
     }
 
     CRM_CHECK(xml_obj != NULL, return);
 
     id = pcmk__xe_id(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
                          xml_obj->name);
         return;
     }
 
     if (ticket_str == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without ticket specified",
                          id);
         return;
     } else {
         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
     }
 
     if (ticket == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
                          "does not exist", id, ticket_str);
         return;
     }
 
     if (rsc_id == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without resource", id);
         return;
     } else {
         rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
     }
 
     if (rsc == NULL) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "does not exist", id, rsc_id);
         return;
 
     } else if ((instance != NULL) && !pcmk__is_clone(rsc)) {
         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
                          "is not a clone but instance '%s' was requested",
                          id, rsc_id, instance);
         return;
     }
 
     if (instance != NULL) {
         rsc = find_clone_instance(rsc, instance);
         if (rsc == NULL) {
             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
                               "does not have an instance '%s'",
                               "'%s'", id, rsc_id, instance);
             return;
         }
     }
 
     rsc_ticket_new(id, rsc, ticket, state, loss_policy);
 }
 
 // \return Standard Pacemaker return code
 static int
 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
                        pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *rsc_id = NULL;
     const char *state = NULL;
 
     pcmk_resource_t *rsc = NULL;
     pcmk_tag_t *tag = NULL;
 
     xmlNode *rsc_set = NULL;
 
     *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return EINVAL);
 
     id = pcmk__xe_id(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
                          xml_obj->name);
         return pcmk_rc_unpack_error;
     }
 
     // Check whether there are any resource sets with template or tag references
     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
     if (*expanded_xml != NULL) {
         crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
         return pcmk_rc_ok;
     }
 
     rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
     if (rsc_id == NULL) {
         return pcmk_rc_ok;
     }
 
     if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
                          "valid resource or tag", id, rsc_id);
         return pcmk_rc_unpack_error;
 
     } else if (rsc != NULL) {
         // No template or tag is referenced
         return pcmk_rc_ok;
     }
 
     state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 
     *expanded_xml = copy_xml(xml_obj);
 
     /* Convert any template or tag reference in "rsc" into ticket
      * PCMK_XE_RESOURCE_SET
      */
     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false,
                           scheduler)) {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
         return pcmk_rc_unpack_error;
     }
 
     if (rsc_set != NULL) {
         if (state != NULL) {
             /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a
              * PCMK_XA_ROLE attribute
              */
             crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
             xml_remove_prop(*expanded_xml, PCMK_XA_RSC_ROLE);
         }
 
     } else {
         free_xml(*expanded_xml);
         *expanded_xml = NULL;
     }
 
     return pcmk_rc_ok;
 }
 
 void
 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
 {
     xmlNode *set = NULL;
     bool any_sets = false;
 
     const char *id = NULL;
     const char *ticket_str = NULL;
 
     pcmk_ticket_t *ticket = NULL;
 
     xmlNode *orig_xml = NULL;
     xmlNode *expanded_xml = NULL;
 
     CRM_CHECK(xml_obj != NULL, return);
 
     id = pcmk__xe_id(xml_obj);
     if (id == NULL) {
         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
                          xml_obj->name);
         return;
     }
 
     if (scheduler->tickets == NULL) {
         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
     if (ticket_str == NULL) {
         pcmk__config_err("Ignoring constraint '%s' without ticket", id);
         return;
     } else {
         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
     }
 
     if (ticket == NULL) {
         ticket = ticket_new(ticket_str, scheduler);
         if (ticket == NULL) {
             return;
         }
     }
 
     if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
                                scheduler) != pcmk_rc_ok) {
         return;
     }
     if (expanded_xml != NULL) {
         orig_xml = xml_obj;
         xml_obj = expanded_xml;
     }
 
     for (set = first_named_child(xml_obj, PCMK_XE_RESOURCE_SET); set != NULL;
          set = crm_next_same_xml(set)) {
 
         const char *loss_policy = NULL;
 
         any_sets = true;
         set = expand_idref(set, scheduler->input);
         loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
 
         if ((set == NULL) // Configuration error, message already logged
             || (unpack_rsc_ticket_set(set, ticket, loss_policy,
                                       scheduler) != pcmk_rc_ok)) {
             if (expanded_xml != NULL) {
                 free_xml(expanded_xml);
             }
             return;
         }
     }
 
     if (expanded_xml) {
         free_xml(expanded_xml);
         xml_obj = orig_xml;
     }
 
     if (!any_sets) {
         unpack_simple_rsc_ticket(xml_obj, scheduler);
     }
 }
 
 /*!
  * \internal
  * \brief Ban resource from a node if it doesn't have a promotion ticket
  *
  * If a resource has tickets for the promoted role, and the ticket is either not
  * granted or set to standby, then ban the resource from all nodes.
  *
  * \param[in,out] rsc  Resource to check
  */
 void
 pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
 {
     for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
         rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
 
         if ((rsc_ticket->role == pcmk_role_promoted)
             && (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
-            resource_location(rsc, NULL, -INFINITY,
+            resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
                               "__stateful_without_ticket__", rsc->cluster);
         }
     }
 }
diff --git a/lib/pacemaker/pcmk_sched_utilization.c b/lib/pacemaker/pcmk_sched_utilization.c
index 86c6772fd6..05e3cf6bae 100644
--- a/lib/pacemaker/pcmk_sched_utilization.c
+++ b/lib/pacemaker/pcmk_sched_utilization.c
@@ -1,469 +1,469 @@
 /*
  * Copyright 2014-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 <crm_internal.h>
 #include <crm/common/xml.h>
 #include <pacemaker-internal.h>
 
 #include "libpacemaker_private.h"
 
 /*!
  * \internal
  * \brief Get integer utilization from a string
  *
  * \param[in] s  String representation of a node utilization value
  *
  * \return Integer equivalent of \p s
  * \todo It would make sense to restrict utilization values to nonnegative
  *       integers, but the documentation just says "integers" and we didn't
  *       restrict them initially, so for backward compatibility, allow any
  *       integer.
  */
 static int
 utilization_value(const char *s)
 {
     int value = 0;
 
     if ((s != NULL) && (pcmk__scan_min_int(s, &value, INT_MIN) == EINVAL)) {
         pcmk__config_warn("Using 0 for utilization instead of "
                           "invalid value '%s'", value);
         value = 0;
     }
     return value;
 }
 
 
 /*
  * Functions for comparing node capacities
  */
 
 struct compare_data {
     const pcmk_node_t *node1;
     const pcmk_node_t *node2;
     bool node2_only;
     int result;
 };
 
 /*!
  * \internal
  * \brief Compare a single utilization attribute for two nodes
  *
  * Compare one utilization attribute for two nodes, decrementing the result if
  * the first node has greater capacity, and incrementing it if the second node
  * has greater capacity.
  *
  * \param[in]     key        Utilization attribute name to compare
  * \param[in]     value      Utilization attribute value to compare
  * \param[in,out] user_data  Comparison data (as struct compare_data*)
  */
 static void
 compare_utilization_value(gpointer key, gpointer value, gpointer user_data)
 {
     int node1_capacity = 0;
     int node2_capacity = 0;
     struct compare_data *data = user_data;
     const char *node2_value = NULL;
 
     if (data->node2_only) {
         if (g_hash_table_lookup(data->node1->details->utilization, key)) {
             return; // We've already compared this attribute
         }
     } else {
         node1_capacity = utilization_value((const char *) value);
     }
 
     node2_value = g_hash_table_lookup(data->node2->details->utilization, key);
     node2_capacity = utilization_value(node2_value);
 
     if (node1_capacity > node2_capacity) {
         data->result--;
     } else if (node1_capacity < node2_capacity) {
         data->result++;
     }
 }
 
 /*!
  * \internal
  * \brief Compare utilization capacities of two nodes
  *
  * \param[in] node1  First node to compare
  * \param[in] node2  Second node to compare
  *
  * \return Negative integer if node1 has more free capacity,
  *         0 if the capacities are equal, or a positive integer
  *         if node2 has more free capacity
  */
 int
 pcmk__compare_node_capacities(const pcmk_node_t *node1,
                               const pcmk_node_t *node2)
 {
     struct compare_data data = {
         .node1      = node1,
         .node2      = node2,
         .node2_only = false,
         .result     = 0,
     };
 
     // Compare utilization values that node1 and maybe node2 have
     g_hash_table_foreach(node1->details->utilization, compare_utilization_value,
                          &data);
 
     // Compare utilization values that only node2 has
     data.node2_only = true;
     g_hash_table_foreach(node2->details->utilization, compare_utilization_value,
                          &data);
 
     return data.result;
 }
 
 
 /*
  * Functions for updating node capacities
  */
 
 struct calculate_data {
     GHashTable *current_utilization;
     bool plus;
 };
 
 /*!
  * \internal
  * \brief Update a single utilization attribute with a new value
  *
  * \param[in]     key        Name of utilization attribute to update
  * \param[in]     value      Value to add or substract
  * \param[in,out] user_data  Calculation data (as struct calculate_data *)
  */
 static void
 update_utilization_value(gpointer key, gpointer value, gpointer user_data)
 {
     int result = 0;
     const char *current = NULL;
     struct calculate_data *data = user_data;
 
     current = g_hash_table_lookup(data->current_utilization, key);
     if (data->plus) {
         result = utilization_value(current) + utilization_value(value);
     } else if (current) {
         result = utilization_value(current) - utilization_value(value);
     }
     g_hash_table_replace(data->current_utilization,
                          strdup(key), pcmk__itoa(result));
 }
 
 /*!
  * \internal
  * \brief Subtract a resource's utilization from node capacity
  *
  * \param[in,out] current_utilization  Current node utilization attributes
  * \param[in]     rsc                  Resource with utilization to subtract
  */
 void
 pcmk__consume_node_capacity(GHashTable *current_utilization,
                             const pcmk_resource_t *rsc)
 {
     struct calculate_data data = {
         .current_utilization = current_utilization,
         .plus = false,
     };
 
     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
 }
 
 /*!
  * \internal
  * \brief Add a resource's utilization to node capacity
  *
  * \param[in,out] current_utilization  Current node utilization attributes
  * \param[in]     rsc                  Resource with utilization to add
  */
 void
 pcmk__release_node_capacity(GHashTable *current_utilization,
                             const pcmk_resource_t *rsc)
 {
     struct calculate_data data = {
         .current_utilization = current_utilization,
         .plus = true,
     };
 
     g_hash_table_foreach(rsc->utilization, update_utilization_value, &data);
 }
 
 
 /*
  * Functions for checking for sufficient node capacity
  */
 
 struct capacity_data {
     const pcmk_node_t *node;
     const char *rsc_id;
     bool is_enough;
 };
 
 /*!
  * \internal
  * \brief Check whether a single utilization attribute has sufficient capacity
  *
  * \param[in]     key        Name of utilization attribute to check
  * \param[in]     value      Amount of utilization required
  * \param[in,out] user_data  Capacity data (as struct capacity_data *)
  */
 static void
 check_capacity(gpointer key, gpointer value, gpointer user_data)
 {
     int required = 0;
     int remaining = 0;
     const char *node_value_s = NULL;
     struct capacity_data *data = user_data;
 
     node_value_s = g_hash_table_lookup(data->node->details->utilization, key);
 
     required = utilization_value(value);
     remaining = utilization_value(node_value_s);
 
     if (required > remaining) {
         crm_debug("Remaining capacity for %s on %s (%d) is insufficient "
                   "for resource %s usage (%d)",
                   (const char *) key, pcmk__node_name(data->node), remaining,
                   data->rsc_id, required);
         data->is_enough = false;
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a node has sufficient capacity for a resource
  *
  * \param[in] node         Node to check
  * \param[in] rsc_id       ID of resource to check (for debug logs only)
  * \param[in] utilization  Required utilization amounts
  *
  * \return true if node has sufficient capacity for resource, otherwise false
  */
 static bool
 have_enough_capacity(const pcmk_node_t *node, const char *rsc_id,
                      GHashTable *utilization)
 {
     struct capacity_data data = {
         .node = node,
         .rsc_id = rsc_id,
         .is_enough = true,
     };
 
     g_hash_table_foreach(utilization, check_capacity, &data);
     return data.is_enough;
 }
 
 /*!
  * \internal
  * \brief Sum the utilization requirements of a list of resources
  *
  * \param[in] orig_rsc  Resource being assigned (for logging purposes)
  * \param[in] rscs      Resources whose utilization should be summed
  *
  * \return Newly allocated hash table with sum of all utilization values
  * \note It is the caller's responsibility to free the return value using
  *       g_hash_table_destroy().
  */
 static GHashTable *
 sum_resource_utilization(const pcmk_resource_t *orig_rsc, GList *rscs)
 {
     GHashTable *utilization = pcmk__strkey_table(free, free);
 
     for (GList *iter = rscs; iter != NULL; iter = iter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
         rsc->cmds->add_utilization(rsc, orig_rsc, rscs, utilization);
     }
     return utilization;
 }
 
 /*!
  * \internal
  * \brief Ban resource from nodes with insufficient utilization capacity
  *
  * \param[in,out] rsc  Resource to check
  *
  * \return Allowed node for \p rsc with most spare capacity, if there are no
  *         nodes with enough capacity for \p rsc and all its colocated resources
  */
 const pcmk_node_t *
 pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc)
 {
     bool any_capable = false;
     char *rscs_id = NULL;
     pcmk_node_t *node = NULL;
     const pcmk_node_t *most_capable_node = NULL;
     GList *colocated_rscs = NULL;
     GHashTable *unassigned_utilization = NULL;
     GHashTableIter iter;
 
     CRM_CHECK(rsc != NULL, return NULL);
 
     // The default placement strategy ignores utilization
     if (pcmk__str_eq(rsc->cluster->placement_strategy, PCMK_VALUE_DEFAULT,
                      pcmk__str_casei)) {
         return NULL;
     }
 
     // Check whether any resources are colocated with this one
     colocated_rscs = rsc->cmds->colocated_resources(rsc, NULL, NULL);
     if (colocated_rscs == NULL) {
         return NULL;
     }
 
     rscs_id = crm_strdup_printf("%s and its colocated resources", rsc->id);
 
     // If rsc isn't in the list, add it so we include its utilization
     if (g_list_find(colocated_rscs, rsc) == NULL) {
         colocated_rscs = g_list_append(colocated_rscs, rsc);
     }
 
     // Sum utilization of colocated resources that haven't been assigned yet
     unassigned_utilization = sum_resource_utilization(rsc, colocated_rscs);
 
     // Check whether any node has enough capacity for all the resources
     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
         if (!pcmk__node_available(node, true, false)) {
             continue;
         }
 
         if (have_enough_capacity(node, rscs_id, unassigned_utilization)) {
             any_capable = true;
         }
 
         // Keep track of node with most free capacity
         if ((most_capable_node == NULL)
             || (pcmk__compare_node_capacities(node, most_capable_node) < 0)) {
             most_capable_node = node;
         }
     }
 
     if (any_capable) {
         // If so, ban resource from any node with insufficient capacity
         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             if (pcmk__node_available(node, true, false)
                 && !have_enough_capacity(node, rscs_id,
                                          unassigned_utilization)) {
                 pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
                                 pcmk__node_name(node), rscs_id);
-                resource_location(rsc, node, -INFINITY, "__limit_utilization__",
-                                  rsc->cluster);
+                resource_location(rsc, node, -PCMK_SCORE_INFINITY,
+                                  "__limit_utilization__", rsc->cluster);
             }
         }
         most_capable_node = NULL;
 
     } else {
         // Otherwise, ban from nodes with insufficient capacity for rsc alone
         g_hash_table_iter_init(&iter, rsc->allowed_nodes);
         while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
             if (pcmk__node_available(node, true, false)
                 && !have_enough_capacity(node, rsc->id, rsc->utilization)) {
                 pcmk__rsc_debug(rsc, "%s does not have enough capacity for %s",
                                 pcmk__node_name(node), rsc->id);
-                resource_location(rsc, node, -INFINITY, "__limit_utilization__",
-                                  rsc->cluster);
+                resource_location(rsc, node, -PCMK_SCORE_INFINITY,
+                                  "__limit_utilization__", rsc->cluster);
             }
         }
     }
 
     g_hash_table_destroy(unassigned_utilization);
     g_list_free(colocated_rscs);
     free(rscs_id);
 
     pe__show_node_scores(true, rsc, "Post-utilization", rsc->allowed_nodes,
                          rsc->cluster);
     return most_capable_node;
 }
 
 /*!
  * \internal
  * \brief Create a new load_stopped pseudo-op for a node
  *
  * \param[in,out] node  Node to create op for
  *
  * \return Newly created load_stopped op
  */
 static pcmk_action_t *
 new_load_stopped_op(pcmk_node_t *node)
 {
     char *load_stopped_task = crm_strdup_printf(PCMK_ACTION_LOAD_STOPPED "_%s",
                                                 node->details->uname);
     pcmk_action_t *load_stopped = get_pseudo_op(load_stopped_task,
                                               node->details->data_set);
 
     if (load_stopped->node == NULL) {
         load_stopped->node = pe__copy_node(node);
         pcmk__clear_action_flags(load_stopped, pcmk_action_optional);
     }
     free(load_stopped_task);
     return load_stopped;
 }
 
 /*!
  * \internal
  * \brief Create utilization-related internal constraints for a resource
  *
  * \param[in,out] rsc            Resource to create constraints for
  * \param[in]     allowed_nodes  List of allowed next nodes for \p rsc
  */
 void
 pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
                                      const GList *allowed_nodes)
 {
     const GList *iter = NULL;
     pcmk_action_t *load_stopped = NULL;
 
     pcmk__rsc_trace(rsc,
                     "Creating utilization constraints for %s - strategy: %s",
                     rsc->id, rsc->cluster->placement_strategy);
 
     // "stop rsc then load_stopped" constraints for current nodes
     for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
         load_stopped = new_load_stopped_op(iter->data);
         pcmk__new_ordering(rsc, stop_key(rsc), NULL, NULL, NULL, load_stopped,
                            pcmk__ar_if_on_same_node_or_target, rsc->cluster);
     }
 
     // "load_stopped then start/migrate_to rsc" constraints for allowed nodes
     for (iter = allowed_nodes; iter; iter = iter->next) {
         load_stopped = new_load_stopped_op(iter->data);
         pcmk__new_ordering(NULL, NULL, load_stopped, rsc, start_key(rsc), NULL,
                            pcmk__ar_if_on_same_node_or_target, rsc->cluster);
         pcmk__new_ordering(NULL, NULL, load_stopped,
                            rsc,
                            pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_TO, 0),
                            NULL,
                            pcmk__ar_if_on_same_node_or_target, rsc->cluster);
     }
 }
 
 /*!
  * \internal
  * \brief Output node capacities if enabled
  *
  * \param[in]     desc       Prefix for output
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler)
 {
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
         return;
     }
     for (const GList *iter = scheduler->nodes;
          iter != NULL; iter = iter->next) {
         const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
         pcmk__output_t *out = scheduler->priv;
 
         out->message(out, "node-capacity", node, desc);
     }
 }
diff --git a/lib/pacemaker/pcmk_scheduler.c b/lib/pacemaker/pcmk_scheduler.c
index d6da630a8c..5674b98a3c 100644
--- a/lib/pacemaker/pcmk_scheduler.c
+++ b/lib/pacemaker/pcmk_scheduler.c
@@ -1,821 +1,821 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <crm/crm.h>
 #include <crm/cib.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/scheduler_internal.h>
 
 #include <glib.h>
 
 #include <crm/pengine/status.h>
 #include <pacemaker-internal.h>
 #include "libpacemaker_private.h"
 
 CRM_TRACE_INIT_DATA(pacemaker);
 
 /*!
  * \internal
  * \brief Do deferred action checks after assignment
  *
  * When unpacking the resource history, the scheduler checks for resource
  * configurations that have changed since an action was run. However, at that
  * time, bundles using the REMOTE_CONTAINER_HACK don't have their final
  * parameter information, so instead they add a deferred check to a list. This
  * function processes one entry in that list.
  *
  * \param[in,out] rsc     Resource that action history is for
  * \param[in,out] node    Node that action history is for
  * \param[in]     rsc_op  Action history entry
  * \param[in]     check   Type of deferred check to do
  */
 static void
 check_params(pcmk_resource_t *rsc, pcmk_node_t *node, const xmlNode *rsc_op,
              enum pcmk__check_parameters check)
 {
     const char *reason = NULL;
     pcmk__op_digest_t *digest_data = NULL;
 
     switch (check) {
         case pcmk__check_active:
             if (pcmk__check_action_config(rsc, node, rsc_op)
                 && pe_get_failcount(node, rsc, NULL, pcmk__fc_effective,
                                     NULL)) {
                 reason = "action definition changed";
             }
             break;
 
         case pcmk__check_last_failure:
             digest_data = rsc_action_digest_cmp(rsc, rsc_op, node,
                                                 rsc->cluster);
             switch (digest_data->rc) {
                 case pcmk__digest_unknown:
                     crm_trace("Resource %s history entry %s on %s has "
                               "no digest to compare",
                               rsc->id, pcmk__xe_id(rsc_op), node->details->id);
                     break;
                 case pcmk__digest_match:
                     break;
                 default:
                     reason = "resource parameters have changed";
                     break;
             }
             break;
     }
     if (reason != NULL) {
         pe__clear_failcount(rsc, node, reason, rsc->cluster);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a resource has failcount clearing scheduled on a node
  *
  * \param[in] node  Node to check
  * \param[in] rsc   Resource to check
  *
  * \return true if \p rsc has failcount clearing scheduled on \p node,
  *         otherwise false
  */
 static bool
 failcount_clear_action_exists(const pcmk_node_t *node,
                               const pcmk_resource_t *rsc)
 {
     GList *list = pe__resource_actions(rsc, node, PCMK_ACTION_CLEAR_FAILCOUNT,
                                        TRUE);
 
     if (list != NULL) {
         g_list_free(list);
         return true;
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Ban a resource from a node if it reached its failure threshold there
  *
  * \param[in,out] data       Resource to check failure threshold for
  * \param[in]     user_data  Node to check resource on
  */
 static void
 check_failure_threshold(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
     const pcmk_node_t *node = user_data;
 
     // If this is a collective resource, apply recursively to children instead
     if (rsc->children != NULL) {
         g_list_foreach(rsc->children, check_failure_threshold, user_data);
         return;
     }
 
     if (!failcount_clear_action_exists(node, rsc)) {
         /* Don't force the resource away from this node due to a failcount
          * that's going to be cleared.
          *
          * @TODO Failcount clearing can be scheduled in
          * pcmk__handle_rsc_config_changes() via process_rsc_history(), or in
          * schedule_resource_actions() via check_params(). This runs well before
          * then, so it cannot detect those, meaning we might check the migration
          * threshold when we shouldn't. Worst case, we stop or move the
          * resource, then move it back in the next transition.
          */
         pcmk_resource_t *failed = NULL;
 
         if (pcmk__threshold_reached(rsc, node, &failed)) {
-            resource_location(failed, node, -INFINITY, "__fail_limit__",
-                              rsc->cluster);
+            resource_location(failed, node, -PCMK_SCORE_INFINITY,
+                              "__fail_limit__", rsc->cluster);
         }
     }
 }
 
 /*!
  * \internal
  * \brief If resource has exclusive discovery, ban node if not allowed
  *
  * Location constraints have a PCMK_XA_RESOURCE_DISCOVERY option that allows
  * users to specify where probes are done for the affected resource. If this is
  * set to \c exclusive, probes will only be done on nodes listed in exclusive
  * constraints. This function bans the resource from the node if the node is not
  * listed.
  *
  * \param[in,out] data       Resource to check
  * \param[in]     user_data  Node to check resource on
  */
 static void
 apply_exclusive_discovery(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
     const pcmk_node_t *node = user_data;
 
     if (rsc->exclusive_discover
         || pe__const_top_resource(rsc, false)->exclusive_discover) {
         pcmk_node_t *match = NULL;
 
         // If this is a collective resource, apply recursively to children
         g_list_foreach(rsc->children, apply_exclusive_discovery, user_data);
 
         match = g_hash_table_lookup(rsc->allowed_nodes, node->details->id);
         if ((match != NULL)
             && (match->rsc_discover_mode != pcmk_probe_exclusive)) {
-            match->weight = -INFINITY;
+            match->weight = -PCMK_SCORE_INFINITY;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Apply stickiness to a resource if appropriate
  *
  * \param[in,out] data       Resource to check for stickiness
  * \param[in]     user_data  Ignored
  */
 static void
 apply_stickiness(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
     pcmk_node_t *node = NULL;
 
     // If this is a collective resource, apply recursively to children instead
     if (rsc->children != NULL) {
         g_list_foreach(rsc->children, apply_stickiness, NULL);
         return;
     }
 
     /* A resource is sticky if it is managed, has stickiness configured, and is
      * active on a single node.
      */
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)
         || (rsc->stickiness < 1) || !pcmk__list_of_1(rsc->running_on)) {
         return;
     }
 
     node = rsc->running_on->data;
 
     /* In a symmetric cluster, stickiness can always be used. In an
      * asymmetric cluster, we have to check whether the resource is still
      * allowed on the node, so we don't keep the resource somewhere it is no
      * longer explicitly enabled.
      */
     if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_symmetric_cluster)
         && (g_hash_table_lookup(rsc->allowed_nodes,
                                 node->details->id) == NULL)) {
         pcmk__rsc_debug(rsc,
                         "Ignoring %s stickiness because the cluster is "
                         "asymmetric and %s is not explicitly allowed",
                         rsc->id, pcmk__node_name(node));
         return;
     }
 
     pcmk__rsc_debug(rsc, "Resource %s has %d stickiness on %s",
                     rsc->id, rsc->stickiness, pcmk__node_name(node));
     resource_location(rsc, node, rsc->stickiness, "stickiness", rsc->cluster);
 }
 
 /*!
  * \internal
  * \brief Apply shutdown locks for all resources as appropriate
  *
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 apply_shutdown_locks(pcmk_scheduler_t *scheduler)
 {
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         return;
     }
     for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
         rsc->cmds->shutdown_lock(rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Calculate the number of available nodes in the cluster
  *
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 count_available_nodes(pcmk_scheduler_t *scheduler)
 {
     if (pcmk_is_set(scheduler->flags, pcmk_sched_no_compat)) {
         return;
     }
 
     // @COMPAT for API backward compatibility only (cluster does not use value)
     for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
 
         if ((node != NULL) && (node->weight >= 0) && node->details->online
             && (node->details->type != node_ping)) {
             scheduler->max_valid_nodes++;
         }
     }
     crm_trace("Online node count: %d", scheduler->max_valid_nodes);
 }
 
 /*
  * \internal
  * \brief Apply node-specific scheduling criteria
  *
  * After the CIB has been unpacked, process node-specific scheduling criteria
  * including shutdown locks, location constraints, resource stickiness,
  * migration thresholds, and exclusive resource discovery.
  */
 static void
 apply_node_criteria(pcmk_scheduler_t *scheduler)
 {
     crm_trace("Applying node-specific scheduling criteria");
     apply_shutdown_locks(scheduler);
     count_available_nodes(scheduler);
     pcmk__apply_locations(scheduler);
     g_list_foreach(scheduler->resources, apply_stickiness, NULL);
 
     for (GList *node_iter = scheduler->nodes; node_iter != NULL;
          node_iter = node_iter->next) {
         for (GList *rsc_iter = scheduler->resources; rsc_iter != NULL;
              rsc_iter = rsc_iter->next) {
             check_failure_threshold(rsc_iter->data, node_iter->data);
             apply_exclusive_discovery(rsc_iter->data, node_iter->data);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Assign resources to nodes
  *
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 assign_resources(pcmk_scheduler_t *scheduler)
 {
     GList *iter = NULL;
 
     crm_trace("Assigning resources to nodes");
 
     if (!pcmk__str_eq(scheduler->placement_strategy, PCMK_VALUE_DEFAULT,
                       pcmk__str_casei)) {
         pcmk__sort_resources(scheduler);
     }
     pcmk__show_node_capacities("Original", scheduler);
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_have_remote_nodes)) {
         /* Assign remote connection resources first (which will also assign any
          * colocation dependencies). If the connection is migrating, always
          * prefer the partial migration target.
          */
         for (iter = scheduler->resources; iter != NULL; iter = iter->next) {
             pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
             if (rsc->is_remote_node) {
                 pcmk__rsc_trace(rsc, "Assigning remote connection resource '%s'",
                                 rsc->id);
                 rsc->cmds->assign(rsc, rsc->partial_migration_target, true);
             }
         }
     }
 
     /* now do the rest of the resources */
     for (iter = scheduler->resources; iter != NULL; iter = iter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
         if (!rsc->is_remote_node) {
             pcmk__rsc_trace(rsc, "Assigning %s resource '%s'",
                             rsc->xml->name, rsc->id);
             rsc->cmds->assign(rsc, NULL, true);
         }
     }
 
     pcmk__show_node_capacities("Remaining", scheduler);
 }
 
 /*!
  * \internal
  * \brief Schedule fail count clearing on online nodes if resource is orphaned
  *
  * \param[in,out] data       Resource to check
  * \param[in]     user_data  Ignored
  */
 static void
 clear_failcounts_if_orphaned(gpointer data, gpointer user_data)
 {
     pcmk_resource_t *rsc = data;
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
         return;
     }
     crm_trace("Clear fail counts for orphaned resource %s", rsc->id);
 
     /* There's no need to recurse into rsc->children because those
      * should just be unassigned clone instances.
      */
 
     for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
         pcmk_action_t *clear_op = NULL;
 
         if (!node->details->online) {
             continue;
         }
         if (pe_get_failcount(node, rsc, NULL, pcmk__fc_effective, NULL) == 0) {
             continue;
         }
 
         clear_op = pe__clear_failcount(rsc, node, "it is orphaned",
                                        rsc->cluster);
 
         /* We can't use order_action_then_stop() here because its
          * pcmk__ar_guest_allowed breaks things
          */
         pcmk__new_ordering(clear_op->rsc, NULL, clear_op, rsc, stop_key(rsc),
                            NULL, pcmk__ar_ordered, rsc->cluster);
     }
 }
 
 /*!
  * \internal
  * \brief Schedule any resource actions needed
  *
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 schedule_resource_actions(pcmk_scheduler_t *scheduler)
 {
     // Process deferred action checks
     pe__foreach_param_check(scheduler, check_params);
     pe__free_param_checks(scheduler);
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_probe_resources)) {
         crm_trace("Scheduling probes");
         pcmk__schedule_probes(scheduler);
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_removed_resources)) {
         g_list_foreach(scheduler->resources, clear_failcounts_if_orphaned,
                        NULL);
     }
 
     crm_trace("Scheduling resource actions");
     for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 
         rsc->cmds->create_actions(rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a resource or any of its descendants are managed
  *
  * \param[in] rsc  Resource to check
  *
  * \return true if resource or any descendant is managed, otherwise false
  */
 static bool
 is_managed(const pcmk_resource_t *rsc)
 {
     if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         return true;
     }
     for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
         if (is_managed((pcmk_resource_t *) iter->data)) {
             return true;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Check whether any resources in the cluster are managed
  *
  * \param[in] scheduler  Scheduler data
  *
  * \return true if any resource is managed, otherwise false
  */
 static bool
 any_managed_resources(const pcmk_scheduler_t *scheduler)
 {
     for (const GList *iter = scheduler->resources;
          iter != NULL; iter = iter->next) {
         if (is_managed((const pcmk_resource_t *) iter->data)) {
             return true;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Check whether a node requires fencing
  *
  * \param[in] node          Node to check
  * \param[in] have_managed  Whether any resource in cluster is managed
  *
  * \return true if \p node should be fenced, otherwise false
  */
 static bool
 needs_fencing(const pcmk_node_t *node, bool have_managed)
 {
     return have_managed && node->details->unclean
            && pe_can_fence(node->details->data_set, node);
 }
 
 /*!
  * \internal
  * \brief Check whether a node requires shutdown
  *
  * \param[in] node          Node to check
  *
  * \return true if \p node should be shut down, otherwise false
  */
 static bool
 needs_shutdown(const pcmk_node_t *node)
 {
     if (pcmk__is_pacemaker_remote_node(node)) {
        /* Do not send shutdown actions for Pacemaker Remote nodes.
         * @TODO We might come up with a good use for this in the future.
         */
         return false;
     }
     return node->details->online && node->details->shutdown;
 }
 
 /*!
  * \internal
  * \brief Track and order non-DC fencing
  *
  * \param[in,out] list       List of existing non-DC fencing actions
  * \param[in,out] action     Fencing action to prepend to \p list
  * \param[in]     scheduler  Scheduler data
  *
  * \return (Possibly new) head of \p list
  */
 static GList *
 add_nondc_fencing(GList *list, pcmk_action_t *action,
                   const pcmk_scheduler_t *scheduler)
 {
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_concurrent_fencing)
         && (list != NULL)) {
         /* Concurrent fencing is disabled, so order each non-DC
          * fencing in a chain. If there is any DC fencing or
          * shutdown, it will be ordered after the last action in the
          * chain later.
          */
         order_actions((pcmk_action_t *) list->data, action, pcmk__ar_ordered);
     }
     return g_list_prepend(list, action);
 }
 
 /*!
  * \internal
  * \brief Schedule a node for fencing
  *
  * \param[in,out] node      Node that requires fencing
  */
 static pcmk_action_t *
 schedule_fencing(pcmk_node_t *node)
 {
     pcmk_action_t *fencing = pe_fence_op(node, NULL, FALSE, "node is unclean",
                                        FALSE, node->details->data_set);
 
     pcmk__sched_warn("Scheduling node %s for fencing", pcmk__node_name(node));
     pcmk__order_vs_fence(fencing, node->details->data_set);
     return fencing;
 }
 
 /*!
  * \internal
  * \brief Create and order node fencing and shutdown actions
  *
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 schedule_fencing_and_shutdowns(pcmk_scheduler_t *scheduler)
 {
     pcmk_action_t *dc_down = NULL;
     bool integrity_lost = false;
     bool have_managed = any_managed_resources(scheduler);
     GList *fencing_ops = NULL;
     GList *shutdown_ops = NULL;
 
     crm_trace("Scheduling fencing and shutdowns as needed");
     if (!have_managed) {
         crm_notice("No fencing will be done until there are resources "
                    "to manage");
     }
 
     // Check each node for whether it needs fencing or shutdown
     for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) {
         pcmk_node_t *node = (pcmk_node_t *) iter->data;
         pcmk_action_t *fencing = NULL;
 
         /* Guest nodes are "fenced" by recovering their container resource,
          * so handle them separately.
          */
         if (pcmk__is_guest_or_bundle_node(node)) {
             if (node->details->remote_requires_reset && have_managed
                 && pe_can_fence(scheduler, node)) {
                 pcmk__fence_guest(node);
             }
             continue;
         }
 
         if (needs_fencing(node, have_managed)) {
             fencing = schedule_fencing(node);
 
             // Track DC and non-DC fence actions separately
             if (node->details->is_dc) {
                 dc_down = fencing;
             } else {
                 fencing_ops = add_nondc_fencing(fencing_ops, fencing,
                                                 scheduler);
             }
 
         } else if (needs_shutdown(node)) {
             pcmk_action_t *down_op = pcmk__new_shutdown_action(node);
 
             // Track DC and non-DC shutdown actions separately
             if (node->details->is_dc) {
                 dc_down = down_op;
             } else {
                 shutdown_ops = g_list_prepend(shutdown_ops, down_op);
             }
         }
 
         if ((fencing == NULL) && node->details->unclean) {
             integrity_lost = true;
             pcmk__config_warn("Node %s is unclean but cannot be fenced",
                               pcmk__node_name(node));
         }
     }
 
     if (integrity_lost) {
         if (!pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
             pcmk__config_warn("Resource functionality and data integrity "
                               "cannot be guaranteed (configure, enable, "
                               "and test fencing to correct this)");
 
         } else if (!pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
             crm_notice("Unclean nodes will not be fenced until quorum is "
                        "attained or " PCMK_OPT_NO_QUORUM_POLICY " is set to "
                        PCMK_VALUE_IGNORE);
         }
     }
 
     if (dc_down != NULL) {
         /* Order any non-DC shutdowns before any DC shutdown, to avoid repeated
          * DC elections. However, we don't want to order non-DC shutdowns before
          * a DC *fencing*, because even though we don't want a node that's
          * shutting down to become DC, the DC fencing could be ordered before a
          * clone stop that's also ordered before the shutdowns, thus leading to
          * a graph loop.
          */
         if (pcmk__str_eq(dc_down->task, PCMK_ACTION_DO_SHUTDOWN,
                          pcmk__str_none)) {
             pcmk__order_after_each(dc_down, shutdown_ops);
         }
 
         // Order any non-DC fencing before any DC fencing or shutdown
 
         if (pcmk_is_set(scheduler->flags, pcmk_sched_concurrent_fencing)) {
             /* With concurrent fencing, order each non-DC fencing action
              * separately before any DC fencing or shutdown.
              */
             pcmk__order_after_each(dc_down, fencing_ops);
         } else if (fencing_ops != NULL) {
             /* Without concurrent fencing, the non-DC fencing actions are
              * already ordered relative to each other, so we just need to order
              * the DC fencing after the last action in the chain (which is the
              * first item in the list).
              */
             order_actions((pcmk_action_t *) fencing_ops->data, dc_down,
                           pcmk__ar_ordered);
         }
     }
     g_list_free(fencing_ops);
     g_list_free(shutdown_ops);
 }
 
 static void
 log_resource_details(pcmk_scheduler_t *scheduler)
 {
     pcmk__output_t *out = scheduler->priv;
     GList *all = NULL;
 
     /* Due to the `crm_mon --node=` feature, out->message() for all the
      * resource-related messages expects a list of nodes that we are allowed to
      * output information for. Here, we create a wildcard to match all nodes.
      */
     all = g_list_prepend(all, (gpointer) "*");
 
     for (GList *item = scheduler->resources; item != NULL; item = item->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) item->data;
 
         // Log all resources except inactive orphans
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)
             || (rsc->role != pcmk_role_stopped)) {
             out->message(out, crm_map_element_name(rsc->xml), 0UL, rsc, all, all);
         }
     }
 
     g_list_free(all);
 }
 
 static void
 log_all_actions(pcmk_scheduler_t *scheduler)
 {
     /* This only ever outputs to the log, so ignore whatever output object was
      * previously set and just log instead.
      */
     pcmk__output_t *prev_out = scheduler->priv;
     pcmk__output_t *out = NULL;
 
     if (pcmk__log_output_new(&out) != pcmk_rc_ok) {
         return;
     }
 
     pe__register_messages(out);
     pcmk__register_lib_messages(out);
     pcmk__output_set_log_level(out, LOG_NOTICE);
     scheduler->priv = out;
 
     out->begin_list(out, NULL, NULL, "Actions");
     pcmk__output_actions(scheduler);
     out->end_list(out);
     out->finish(out, CRM_EX_OK, true, NULL);
     pcmk__output_free(out);
 
     scheduler->priv = prev_out;
 }
 
 /*!
  * \internal
  * \brief Log all required but unrunnable actions at trace level
  *
  * \param[in] scheduler  Scheduler data
  */
 static void
 log_unrunnable_actions(const pcmk_scheduler_t *scheduler)
 {
     const uint64_t flags = pcmk_action_optional
                            |pcmk_action_runnable
                            |pcmk_action_pseudo;
 
     crm_trace("Required but unrunnable actions:");
     for (const GList *iter = scheduler->actions;
          iter != NULL; iter = iter->next) {
 
         const pcmk_action_t *action = (const pcmk_action_t *) iter->data;
 
         if (!pcmk_any_flags_set(action->flags, flags)) {
             pcmk__log_action("\t", action, true);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack the CIB for scheduling
  *
  * \param[in,out] cib        CIB XML to unpack (may be NULL if already unpacked)
  * \param[in]     flags      Scheduler flags to set in addition to defaults
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_cib(xmlNode *cib, unsigned long long flags, pcmk_scheduler_t *scheduler)
 {
     const char* localhost_save = NULL;
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_have_status)) {
         crm_trace("Reusing previously calculated cluster status");
         pcmk__set_scheduler_flags(scheduler, flags);
         return;
     }
 
     if (scheduler->localhost) {
         localhost_save = scheduler->localhost;
     }
 
     CRM_ASSERT(cib != NULL);
     crm_trace("Calculating cluster status");
 
     /* This will zero the entire struct without freeing anything first, so
      * callers should never call pcmk__schedule_actions() with a populated data
      * set unless pcmk_sched_have_status is set (i.e. cluster_status() was
      * previously called, whether directly or via pcmk__schedule_actions()).
      */
     set_working_set_defaults(scheduler);
 
     if (localhost_save) {
         scheduler->localhost = localhost_save;
     }
 
     pcmk__set_scheduler_flags(scheduler, flags);
     scheduler->input = cib;
     cluster_status(scheduler); // Sets pcmk_sched_have_status
 }
 
 /*!
  * \internal
  * \brief Run the scheduler for a given CIB
  *
  * \param[in,out] cib        CIB XML to use as scheduler input
  * \param[in]     flags      Scheduler flags to set in addition to defaults
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pcmk__schedule_actions(xmlNode *cib, unsigned long long flags,
                        pcmk_scheduler_t *scheduler)
 {
     unpack_cib(cib, flags, scheduler);
     pcmk__set_assignment_methods(scheduler);
     pcmk__apply_node_health(scheduler);
     pcmk__unpack_constraints(scheduler);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_validate_only)) {
         return;
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_location_only)
         && pcmk__is_daemon) {
         log_resource_details(scheduler);
     }
 
     apply_node_criteria(scheduler);
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         return;
     }
 
     pcmk__create_internal_constraints(scheduler);
     pcmk__handle_rsc_config_changes(scheduler);
     assign_resources(scheduler);
     schedule_resource_actions(scheduler);
 
     /* Remote ordering constraints need to happen prior to calculating fencing
      * because it is one more place we can mark nodes as needing fencing.
      */
     pcmk__order_remote_connection_actions(scheduler);
 
     schedule_fencing_and_shutdowns(scheduler);
     pcmk__apply_orderings(scheduler);
     log_all_actions(scheduler);
     pcmk__create_graph(scheduler);
 
     if (get_crm_log_level() == LOG_TRACE) {
         log_unrunnable_actions(scheduler);
     }
 }
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 39b2ad93c5..42f9fb32e1 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,1008 +1,1008 @@
 /*
  * Copyright 2021-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/cib/internal.h>
 #include <crm/common/output.h>
 #include <crm/common/results.h>
 #include <crm/common/scheduler.h>
 #include <pacemaker-internal.h>
 #include <pacemaker.h>
 
 #include <stdint.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #include "libpacemaker_private.h"
 
 static pcmk__output_t *out = NULL;
 static cib_t *fake_cib = NULL;
 static GList *fake_resource_list = NULL;
 static const GList *fake_op_fail_list = NULL;
 
 static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
                                const char *use_date);
 
 /*!
  * \internal
  * \brief Create an action name for use in a dot graph
  *
  * \param[in] action   Action to create name for
  * \param[in] verbose  If true, add action ID to name
  *
  * \return Newly allocated string with action name
  * \note It is the caller's responsibility to free the result.
  */
 static char *
 create_action_name(const pcmk_action_t *action, bool verbose)
 {
     char *action_name = NULL;
     const char *prefix = "";
     const char *action_host = NULL;
     const char *clone_name = NULL;
     const char *task = action->task;
 
     if (action->node != NULL) {
         action_host = action->node->details->uname;
     } else if (!pcmk_is_set(action->flags, pcmk_action_pseudo)) {
         action_host = "<none>";
     }
 
     if (pcmk__str_eq(action->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
         prefix = "Cancel ";
         task = action->cancel_task;
     }
 
     if (action->rsc != NULL) {
         clone_name = action->rsc->clone_name;
     }
 
     if (clone_name != NULL) {
         char *key = NULL;
         guint interval_ms = 0;
 
         if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
                                   &interval_ms) != pcmk_rc_ok) {
             interval_ms = 0;
         }
 
         if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
                                  PCMK_ACTION_NOTIFIED, NULL)) {
             const char *n_type = g_hash_table_lookup(action->meta,
                                                      "notify_key_type");
             const char *n_task = g_hash_table_lookup(action->meta,
                                                      "notify_key_operation");
 
             CRM_ASSERT(n_type != NULL);
             CRM_ASSERT(n_task != NULL);
             key = pcmk__notify_key(clone_name, n_type, n_task);
         } else {
             key = pcmk__op_key(clone_name, task, interval_ms);
         }
 
         if (action_host != NULL) {
             action_name = crm_strdup_printf("%s%s %s",
                                             prefix, key, action_host);
         } else {
             action_name = crm_strdup_printf("%s%s", prefix, key);
         }
         free(key);
 
     } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
                             pcmk__str_none)) {
         const char *op = g_hash_table_lookup(action->meta,
                                              PCMK__META_STONITH_ACTION);
 
         action_name = crm_strdup_printf("%s%s '%s' %s",
                                         prefix, action->task, op, action_host);
 
     } else if (action->rsc && action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->uuid, action_host);
 
     } else if (action_host) {
         action_name = crm_strdup_printf("%s%s %s",
                                         prefix, action->task, action_host);
 
     } else {
         action_name = crm_strdup_printf("%s", action->uuid);
     }
 
     if (verbose) {
         char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
 
         free(action_name);
         action_name = with_id;
     }
     return action_name;
 }
 
 /*!
  * \internal
  * \brief Display the status of a cluster
  *
  * \param[in,out] scheduler     Scheduler data
  * \param[in]     show_opts     How to modify display (as pcmk_show_opt_e flags)
  * \param[in]     section_opts  Sections to display (as pcmk_section_e flags)
  * \param[in]     title         What to use as list title
  * \param[in]     print_spacer  Whether to display a spacer first
  */
 static void
 print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts,
                      uint32_t section_opts, const char *title,
                      bool print_spacer)
 {
     pcmk__output_t *out = scheduler->priv;
     GList *all = NULL;
     crm_exit_t stonith_rc = 0;
     enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
 
     section_opts |= pcmk_section_nodes | pcmk_section_resources;
     show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
 
     all = g_list_prepend(all, (gpointer) "*");
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "%s", title);
     out->message(out, "cluster-status",
                  scheduler, state, stonith_rc, NULL,
                  pcmk__fence_history_none, section_opts, show_opts, NULL,
                  all, all);
     out->end_list(out);
 
     g_list_free(all);
 }
 
 /*!
  * \internal
  * \brief Display a summary of all actions scheduled in a transition
  *
  * \param[in,out] scheduler     Scheduler data (fully scheduled)
  * \param[in]     print_spacer  Whether to display a spacer first
  */
 static void
 print_transition_summary(pcmk_scheduler_t *scheduler, bool print_spacer)
 {
     pcmk__output_t *out = scheduler->priv;
 
     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
     out->begin_list(out, NULL, NULL, "Transition Summary");
     pcmk__output_actions(scheduler);
     out->end_list(out);
 }
 
 /*!
  * \internal
  * \brief Reset scheduler input, output, date, and flags
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     input      What to set as cluster input
  * \param[in]     out        What to set as cluster output object
  * \param[in]     use_date   What to set as cluster's current timestamp
  * \param[in]     flags      Group of enum pcmk_scheduler_flags to set
  */
 static void
 reset(pcmk_scheduler_t *scheduler, xmlNodePtr input, pcmk__output_t *out,
       const char *use_date, unsigned int flags)
 {
     scheduler->input = input;
     scheduler->priv = out;
     set_effective_date(scheduler, true, use_date);
     if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_sanitized);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_output_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_show_utilization);
     }
 }
 
 /*!
  * \brief Write out a file in dot(1) format describing the actions that will
  *        be taken by the scheduler in response to an input CIB file.
  *
  * \param[in,out] scheduler    Scheduler data
  * \param[in]     dot_file     The filename to write
  * \param[in]     all_actions  Write all actions, even those that are optional
  *                             or are on unmanaged resources
  * \param[in]     verbose      Add extra information, such as action IDs, to the
  *                             output
  *
  * \return Standard Pacemaker return code
  */
 static int
 write_sim_dotfile(pcmk_scheduler_t *scheduler, const char *dot_file,
                   bool all_actions, bool verbose)
 {
     GList *iter = NULL;
     FILE *dot_strm = fopen(dot_file, "w");
 
     if (dot_strm == NULL) {
         return errno;
     }
 
     fprintf(dot_strm, " digraph \"g\" {\n");
     for (iter = scheduler->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = (pcmk_action_t *) iter->data;
         const char *style = "dashed";
         const char *font = "black";
         const char *color = "black";
         char *action_name = create_action_name(action, verbose);
 
         if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
             font = "orange";
         }
 
         if (pcmk_is_set(action->flags, pcmk_action_added_to_graph)) {
             style = PCMK__VALUE_BOLD;
             color = "green";
 
         } else if ((action->rsc != NULL)
                    && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed)) {
             color = "red";
             font = "purple";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else if (pcmk_is_set(action->flags, pcmk_action_optional)) {
             color = "blue";
             if (!all_actions) {
                 goto do_not_write;
             }
 
         } else {
             color = "red";
             CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pcmk_action_runnable));
         }
 
         pcmk__set_action_flags(action, pcmk_action_added_to_graph);
         fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
                 action_name, style, color, font);
   do_not_write:
         free(action_name);
     }
 
     for (iter = scheduler->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *action = (pcmk_action_t *) iter->data;
 
         for (GList *before_iter = action->actions_before;
              before_iter != NULL; before_iter = before_iter->next) {
 
             pcmk__related_action_t *before = before_iter->data;
 
             char *before_name = NULL;
             char *after_name = NULL;
             const char *style = "dashed";
             bool optional = true;
 
             if (before->state == pe_link_dumped) {
                 optional = false;
                 style = PCMK__VALUE_BOLD;
             } else if ((uint32_t) before->type == pcmk__ar_none) {
                 continue;
             } else if (pcmk_is_set(before->action->flags,
                                    pcmk_action_added_to_graph)
                        && pcmk_is_set(action->flags, pcmk_action_added_to_graph)
                        && (uint32_t) before->type != pcmk__ar_if_on_same_node_or_target) {
                 optional = false;
             }
 
             if (all_actions || !optional) {
                 before_name = create_action_name(before->action, verbose);
                 after_name = create_action_name(action, verbose);
                 fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
                         before_name, after_name, style);
                 free(before_name);
                 free(after_name);
             }
         }
     }
 
     fprintf(dot_strm, "}\n");
     fflush(dot_strm);
     fclose(dot_strm);
     return pcmk_rc_ok;
 }
 
 /*!
  * \brief Profile the configuration updates and scheduler actions in a single
  *        CIB file, printing the profiling timings.
  *
  * \note \p scheduler->priv must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in]     xml_file   The CIB file to profile
  * \param[in]     repeat     Number of times to run
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     use_date   The date to set the cluster's time to (may be NULL)
  */
 static void
 profile_file(const char *xml_file, long long repeat,
              pcmk_scheduler_t *scheduler, const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv;
     xmlNode *cib_object = NULL;
     clock_t start = 0;
     clock_t end;
     unsigned long long scheduler_flags = pcmk_sched_no_compat;
 
     CRM_ASSERT(out != NULL);
 
     cib_object = filename2xml(xml_file);
     start = clock();
 
     if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
         create_xml_node(cib_object, PCMK_XE_STATUS);
     }
 
     if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
         free_xml(cib_object);
         return;
     }
 
     if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
         free_xml(cib_object);
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
         scheduler_flags |= pcmk_sched_output_scores;
     }
     if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
         scheduler_flags |= pcmk_sched_show_utilization;
     }
 
     for (int i = 0; i < repeat; ++i) {
         xmlNode *input = (repeat == 1)? cib_object : copy_xml(cib_object);
 
         scheduler->input = input;
         set_effective_date(scheduler, false, use_date);
         pcmk__schedule_actions(input, scheduler_flags, scheduler);
         pe_reset_working_set(scheduler);
     }
 
     end = clock();
     out->message(out, "profile", xml_file, start, end);
 }
 
 void
 pcmk__profile_dir(const char *dir, long long repeat,
                   pcmk_scheduler_t *scheduler, const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv;
     struct dirent **namelist;
 
     int file_num = scandir(dir, &namelist, 0, alphasort);
 
     CRM_ASSERT(out != NULL);
 
     if (file_num > 0) {
         struct stat prop;
         char buffer[FILENAME_MAX];
 
         out->begin_list(out, NULL, NULL, "Timings");
 
         while (file_num--) {
             if ('.' == namelist[file_num]->d_name[0]) {
                 free(namelist[file_num]);
                 continue;
 
             } else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
                                             ".xml")) {
                 free(namelist[file_num]);
                 continue;
             }
             snprintf(buffer, sizeof(buffer), "%s/%s",
                      dir, namelist[file_num]->d_name);
             if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
                 profile_file(buffer, repeat, scheduler, use_date);
             }
             free(namelist[file_num]);
         }
         free(namelist);
 
         out->end_list(out);
     }
 }
 
 /*!
  * \brief Set the date of the cluster, either to the value given by
  *        \p use_date, or to the \c PCMK_XA_EXECUTION_DATE value in the CIB.
  *
  * \note \p scheduler->priv must have been set to a valid \p pcmk__output_t
  *       object before this function is called.
  *
  * \param[in,out] scheduler       Scheduler data
  * \param[in]     print_original  If \p true, the \c PCMK_XA_EXECUTION_DATE
  *                                should also be printed
  * \param[in]     use_date        The date to set the cluster's time to
  *                                (may be NULL)
  */
 static void
 set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
                    const char *use_date)
 {
     pcmk__output_t *out = scheduler->priv;
     time_t original_date = 0;
 
     CRM_ASSERT(out != NULL);
 
     crm_element_value_epoch(scheduler->input, PCMK_XA_EXECUTION_DATE,
                             &original_date);
 
     if (use_date) {
         scheduler->now = crm_time_new(use_date);
         out->info(out, "Setting effective cluster time: %s", use_date);
         crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->now,
                      crm_time_log_date | crm_time_log_timeofday);
 
     } else if (original_date != 0) {
         scheduler->now = pcmk__copy_timet(original_date);
 
         if (print_original) {
             char *when = crm_time_as_string(scheduler->now,
                             crm_time_log_date|crm_time_log_timeofday);
 
             out->info(out, "Using the original execution date of: %s", when);
             free(when);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a pseudo-action in a graph
  *
  * \param[in,out] graph   Graph to update with pseudo-action result
  * \param[in,out] action  Pseudo-action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     const char *task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-pseudo-action", node, task);
 
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate executing a resource action in a graph
  *
  * \param[in,out] graph   Graph to update with resource action result
  * \param[in,out] action  Resource action to simulate executing
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     int rc;
     lrmd_event_data_t *op = NULL;
     int target_outcome = PCMK_OCF_OK;
 
     const char *rtype = NULL;
     const char *rclass = NULL;
     const char *resource = NULL;
     const char *rprovider = NULL;
     const char *resource_config_name = NULL;
     const char *operation = crm_element_value(action->xml, PCMK_XA_OPERATION);
     const char *target_rc_s = crm_meta_value(action->params,
                                              PCMK__META_OP_TARGET_RC);
 
     xmlNode *cib_node = NULL;
     xmlNode *cib_resource = NULL;
     xmlNode *action_rsc = first_named_child(action->xml, PCMK_XE_PRIMITIVE);
 
     char *node = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
     char *uuid = NULL;
     const char *router_node = crm_element_value(action->xml,
                                                 PCMK__XA_ROUTER_NODE);
 
     // Certain actions don't need to be displayed or history entries
     if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
         crm_debug("No history injection for %s op on %s", operation, node);
         goto done; // Confirm action and update graph
     }
 
     if (action_rsc == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "Bad");
         free(node);
         return EPROTO;
     }
 
     /* A resource might be known by different names in the configuration and in
      * the action (for example, a clone instance). Grab the configuration name
      * (which is preferred when writing history), and if necessary, the instance
      * name.
      */
     resource_config_name = crm_element_value(action_rsc, PCMK_XA_ID);
     if (resource_config_name == NULL) { // Shouldn't be possible
         crm_log_xml_err(action->xml, "No ID");
         free(node);
         return EPROTO;
     }
     resource = resource_config_name;
     if (pe_find_resource(fake_resource_list, resource) == NULL) {
         const char *longname = crm_element_value(action_rsc, PCMK__XA_LONG_ID);
 
         if ((longname != NULL)
             && (pe_find_resource(fake_resource_list, longname) != NULL)) {
             resource = longname;
         }
     }
 
     // Certain actions need to be displayed but don't need history entries
     if (pcmk__strcase_any_of(operation, PCMK_ACTION_DELETE,
                              PCMK_ACTION_META_DATA, NULL)) {
         out->message(out, "inject-rsc-action", resource, operation, node,
                      (guint) 0);
         goto done; // Confirm action and update graph
     }
 
     rclass = crm_element_value(action_rsc, PCMK_XA_CLASS);
     rtype = crm_element_value(action_rsc, PCMK_XA_TYPE);
     rprovider = crm_element_value(action_rsc, PCMK_XA_PROVIDER);
 
     pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
 
     CRM_ASSERT(fake_cib->cmds->query(fake_cib, NULL, NULL,
                                      cib_sync_call|cib_scope_local) == pcmk_ok);
 
     // Ensure the action node is in the CIB
     uuid = crm_element_value_copy(action->xml, PCMK__META_ON_NODE_UUID);
     cib_node = pcmk__inject_node(fake_cib, node,
                                  ((router_node == NULL)? uuid: node));
     free(uuid);
     CRM_ASSERT(cib_node != NULL);
 
     // Add a history entry for the action
     cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
                                                  resource_config_name,
                                                  rclass, rtype, rprovider);
     if (cib_resource == NULL) {
         crm_err("Could not simulate action %d history for resource %s",
                 action->id, resource);
         free(node);
         free_xml(cib_node);
         return EINVAL;
     }
 
     // Simulate and display an executor event for the action result
     op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
                                        target_outcome, "User-injected result");
     out->message(out, "inject-rsc-action", resource, op->op_type, node,
                  op->interval_ms);
 
     // Check whether action is in a list of desired simulated failures
     for (const GList *iter = fake_op_fail_list;
          iter != NULL; iter = iter->next) {
         const char *spec = (const char *) iter->data;
         char *key = NULL;
         const char *match_name = NULL;
 
         // Allow user to specify anonymous clone with or without instance number
         key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
                                 op->interval_ms, node);
         if (strncasecmp(key, spec, strlen(key)) == 0) {
             match_name = resource;
         }
         free(key);
 
         // If not found, try the resource's name in the configuration
         if ((match_name == NULL)
             && (strcmp(resource, resource_config_name) != 0)) {
 
             key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
                                     op->op_type, op->interval_ms, node);
             if (strncasecmp(key, spec, strlen(key)) == 0) {
                 match_name = resource_config_name;
             }
             free(key);
         }
 
         if (match_name == NULL) {
             continue; // This failed action entry doesn't match
         }
 
         // ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
         rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
         if (rc != 1) {
             out->err(out, "Invalid failed operation '%s' "
                           "(result code must be integer)", spec);
             continue; // Keep checking other list entries
         }
 
         out->info(out, "Pretending action %d failed with rc=%d",
                   action->id, op->rc);
         pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
-        graph->abort_priority = INFINITY;
+        graph->abort_priority = PCMK_SCORE_INFINITY;
         pcmk__inject_failcount(out, cib_node, match_name, op->op_type,
                                op->interval_ms, op->rc);
         break;
     }
 
     pcmk__inject_action_result(cib_resource, op, target_outcome);
     lrmd_free_event(op);
     rc = fake_cib->cmds->modify(fake_cib, PCMK_XE_STATUS, cib_node,
                                 cib_sync_call|cib_scope_local);
     CRM_ASSERT(rc == pcmk_ok);
 
   done:
     free(node);
     free_xml(cib_node);
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a cluster action
  *
  * \param[in,out] graph   Graph to update with action result
  * \param[in,out] action  Cluster action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
     const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION);
     xmlNode *rsc = first_named_child(action->xml, PCMK_XE_PRIMITIVE);
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     out->message(out, "inject-cluster-action", node, task, rsc);
     pcmk__update_graph(graph, action);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Simulate successfully executing a fencing action
  *
  * \param[in,out] graph   Graph to update with action result
  * \param[in,out] action  Fencing action to simulate
  *
  * \return Standard Pacemaker return code
  */
 static int
 simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
 {
     const char *op = crm_meta_value(action->params, PCMK__META_STONITH_ACTION);
     char *target = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
 
     out->message(out, "inject-fencing-action", target, op);
 
     if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
         int rc = pcmk_ok;
         GString *xpath = g_string_sized_new(512);
 
         // Set node state to offline
         xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
                                                            false);
 
         CRM_ASSERT(cib_node != NULL);
         crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, __func__);
         rc = fake_cib->cmds->replace(fake_cib, PCMK_XE_STATUS, cib_node,
                                      cib_sync_call|cib_scope_local);
         CRM_ASSERT(rc == pcmk_ok);
 
         // Simulate controller clearing node's resource history and attributes
         pcmk__g_strcat(xpath,
                        "//" PCMK__XE_NODE_STATE
                        "[@" PCMK_XA_UNAME "='", target, "']/" PCMK__XE_LRM,
                        NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call|cib_scope_local);
 
         g_string_truncate(xpath, 0);
         pcmk__g_strcat(xpath,
                        "//" PCMK__XE_NODE_STATE
                        "[@" PCMK_XA_UNAME "='", target, "']"
                        "/" PCMK__XE_TRANSIENT_ATTRIBUTES, NULL);
         fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
                                cib_xpath|cib_sync_call|cib_scope_local);
 
         free_xml(cib_node);
         g_string_free(xpath, TRUE);
     }
 
     pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
     pcmk__update_graph(graph, action);
     free(target);
     return pcmk_rc_ok;
 }
 
 enum pcmk__graph_status
 pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib,
                           const GList *op_fail_list)
 {
     pcmk__graph_t *transition = NULL;
     enum pcmk__graph_status graph_rc;
 
     pcmk__graph_functions_t simulation_fns = {
         simulate_pseudo_action,
         simulate_resource_action,
         simulate_cluster_action,
         simulate_fencing_action,
     };
 
     out = scheduler->priv;
 
     fake_cib = cib;
     fake_op_fail_list = op_fail_list;
 
     if (!out->is_quiet(out)) {
         out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
     }
 
     pcmk__set_graph_functions(&simulation_fns);
     transition = pcmk__unpack_graph(scheduler->graph, crm_system_name);
     pcmk__log_graph(LOG_DEBUG, transition);
 
     fake_resource_list = scheduler->resources;
     do {
         graph_rc = pcmk__execute_graph(transition);
     } while (graph_rc == pcmk__graph_active);
     fake_resource_list = NULL;
 
     if (graph_rc != pcmk__graph_complete) {
         out->err(out, "Transition failed: %s",
                  pcmk__graph_status2text(graph_rc));
         pcmk__log_graph(LOG_ERR, transition);
         out->err(out, "An invalid transition was produced");
     }
     pcmk__free_graph(transition);
 
     if (!out->is_quiet(out)) {
         // If not quiet, we'll need the resulting CIB for later display
         xmlNode *cib_object = NULL;
         int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
                                        cib_sync_call|cib_scope_local);
 
         CRM_ASSERT(rc == pcmk_ok);
         pe_reset_working_set(scheduler);
         scheduler->input = cib_object;
         out->end_list(out);
     }
     return graph_rc;
 }
 
 int
 pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out,
                const pcmk_injections_t *injections, unsigned int flags,
                uint32_t section_opts, const char *use_date,
                const char *input_file, const char *graph_file,
                const char *dot_file)
 {
     int printed = pcmk_rc_no_output;
     int rc = pcmk_rc_ok;
     xmlNodePtr input = NULL;
     cib_t *cib = NULL;
 
     rc = cib__signon_query(out, &cib, &input);
     if (rc != pcmk_rc_ok) {
         goto simulate_done;
     }
 
     reset(scheduler, input, out, use_date, flags);
     cluster_status(scheduler);
 
     if ((cib->variant == cib_native)
         && pcmk_is_set(section_opts, pcmk_section_times)) {
         if (pcmk__our_nodename == NULL) {
             // Currently used only in the times section
             pcmk__query_node_name(out, 0, &pcmk__our_nodename, 0);
         }
         scheduler->localhost = pcmk__our_nodename;
     }
 
     if (!out->is_quiet(out)) {
         const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending);
 
         if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
             printed = out->message(out, "maint-mode", scheduler->flags);
         }
 
         if (scheduler->disabled_resources || scheduler->blocked_resources) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             printed = out->info(out,
                                 "%d of %d resource instances DISABLED and "
                                 "%d BLOCKED from further action due to failure",
                                 scheduler->disabled_resources,
                                 scheduler->ninstances,
                                 scheduler->blocked_resources);
         }
 
         /* Most formatted output headers use caps for each word, but this one
          * only has the first word capitalized for compatibility with pcs.
          */
         print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0),
                              section_opts, "Current cluster status",
                              (printed == pcmk_rc_ok));
         printed = pcmk_rc_ok;
     }
 
     // If the user requested any injections, handle them
     if ((injections->node_down != NULL)
         || (injections->node_fail != NULL)
         || (injections->node_up != NULL)
         || (injections->op_inject != NULL)
         || (injections->ticket_activate != NULL)
         || (injections->ticket_grant != NULL)
         || (injections->ticket_revoke != NULL)
         || (injections->ticket_standby != NULL)
         || (injections->watchdog != NULL)) {
 
         PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
         pcmk__inject_scheduler_input(scheduler, cib, injections);
         printed = pcmk_rc_ok;
 
         rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
         if (rc != pcmk_rc_ok) {
             rc = pcmk_legacy2rc(rc);
             goto simulate_done;
         }
 
         cleanup_calculations(scheduler);
         reset(scheduler, input, out, use_date, flags);
         cluster_status(scheduler);
     }
 
     if (input_file != NULL) {
         rc = write_xml_file(input, input_file, FALSE);
         if (rc < 0) {
             rc = pcmk_legacy2rc(rc);
             goto simulate_done;
         }
     }
 
     if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
         pcmk__output_t *logger_out = NULL;
         unsigned long long scheduler_flags = pcmk_sched_no_compat;
 
         if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
             scheduler_flags |= pcmk_sched_output_scores;
         }
         if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
             scheduler_flags |= pcmk_sched_show_utilization;
         }
 
         if (pcmk_all_flags_set(scheduler->flags,
                                pcmk_sched_output_scores
                                |pcmk_sched_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL,
                             "Assignment Scores and Utilization Information");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(scheduler->flags, pcmk_sched_output_scores)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Assignment Scores");
             printed = pcmk_rc_ok;
 
         } else if (pcmk_is_set(scheduler->flags, pcmk_sched_show_utilization)) {
             PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
             out->begin_list(out, NULL, NULL, "Utilization Information");
             printed = pcmk_rc_ok;
 
         } else {
             rc = pcmk__log_output_new(&logger_out);
             if (rc != pcmk_rc_ok) {
                 goto simulate_done;
             }
             pe__register_messages(logger_out);
             pcmk__register_lib_messages(logger_out);
             scheduler->priv = logger_out;
         }
 
         pcmk__schedule_actions(input, scheduler_flags, scheduler);
 
         if (logger_out == NULL) {
             out->end_list(out);
         } else {
             logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
             pcmk__output_free(logger_out);
             scheduler->priv = out;
         }
 
         input = NULL;           /* Don't try and free it twice */
 
         if (graph_file != NULL) {
             rc = write_xml_file(scheduler->graph, graph_file, FALSE);
             if (rc < 0) {
                 rc = pcmk_rc_graph_error;
                 goto simulate_done;
             }
         }
 
         if (dot_file != NULL) {
             rc = write_sim_dotfile(scheduler, dot_file,
                                    pcmk_is_set(flags, pcmk_sim_all_actions),
                                    pcmk_is_set(flags, pcmk_sim_verbose));
             if (rc != pcmk_rc_ok) {
                 rc = pcmk_rc_dot_error;
                 goto simulate_done;
             }
         }
 
         if (!out->is_quiet(out)) {
             print_transition_summary(scheduler, printed == pcmk_rc_ok);
         }
     }
 
     rc = pcmk_rc_ok;
 
     if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
         goto simulate_done;
     }
 
     PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
     if (pcmk__simulate_transition(scheduler, cib, injections->op_fail)
             != pcmk__graph_complete) {
         rc = pcmk_rc_invalid_transition;
     }
 
     if (out->is_quiet(out)) {
         goto simulate_done;
     }
 
     set_effective_date(scheduler, true, use_date);
 
     if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_output_scores);
     }
     if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_show_utilization);
     }
 
     cluster_status(scheduler);
     print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status",
                          true);
 
 simulate_done:
     cib__clean_up_connection(&cib);
     return rc;
 }
 
 int
 pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler,
               const pcmk_injections_t *injections, unsigned int flags,
               unsigned int section_opts, const char *use_date,
               const char *input_file, const char *graph_file,
               const char *dot_file)
 {
     pcmk__output_t *out = NULL;
     int rc = pcmk_rc_ok;
 
     rc = pcmk__xml_output_new(&out, xml);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     pe__register_messages(out);
     pcmk__register_lib_messages(out);
 
     rc = pcmk__simulate(scheduler, out, injections, flags, section_opts,
                         use_date, input_file, graph_file, dot_file);
     pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
     return rc;
 }
diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c
index a6dfac994b..589ea63ad3 100644
--- a/lib/pengine/bundle.c
+++ b/lib/pengine/bundle.c
@@ -1,2255 +1,2255 @@
 /*
  * 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 <crm_internal.h>
 
 #include <ctype.h>
 #include <stdint.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/output.h>
 #include <crm/common/xml_internal.h>
 #include <pe_status_private.h>
 
 enum pe__bundle_mount_flags {
     pe__bundle_mount_none       = 0x00,
 
     // mount instance-specific subdirectory rather than source directly
     pe__bundle_mount_subdir     = 0x01
 };
 
 typedef struct {
     char *source;
     char *target;
     char *options;
     uint32_t flags; // bitmask of pe__bundle_mount_flags
 } pe__bundle_mount_t;
 
 typedef struct {
     char *source;
     char *target;
 } pe__bundle_port_t;
 
 enum pe__container_agent {
     PE__CONTAINER_AGENT_UNKNOWN,
     PE__CONTAINER_AGENT_DOCKER,
     PE__CONTAINER_AGENT_RKT,
     PE__CONTAINER_AGENT_PODMAN,
 };
 
 #define PE__CONTAINER_AGENT_UNKNOWN_S "unknown"
 #define PE__CONTAINER_AGENT_DOCKER_S  "docker"
 #define PE__CONTAINER_AGENT_RKT_S     "rkt"
 #define PE__CONTAINER_AGENT_PODMAN_S  "podman"
 
 typedef struct pe__bundle_variant_data_s {
         int promoted_max;
         int nreplicas;
         int nreplicas_per_host;
         char *prefix;
         char *image;
         const char *ip_last;
         char *host_network;
         char *host_netmask;
         char *control_port;
         char *container_network;
         char *ip_range_start;
         gboolean add_host;
         gchar *container_host_options;
         char *container_command;
         char *launcher_options;
         const char *attribute_target;
 
         pcmk_resource_t *child;
 
         GList *replicas;    // pcmk__bundle_replica_t *
         GList *ports;       // pe__bundle_port_t *
         GList *mounts;      // pe__bundle_mount_t *
 
         enum pe__container_agent agent_type;
 } pe__bundle_variant_data_t;
 
 #define get_bundle_variant_data(data, rsc)                      \
     CRM_ASSERT(rsc != NULL);                                    \
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_bundle);        \
     CRM_ASSERT(rsc->variant_opaque != NULL);                    \
     data = (pe__bundle_variant_data_t *) rsc->variant_opaque;
 
 /*!
  * \internal
  * \brief Get maximum number of bundle replicas allowed to run
  *
  * \param[in] rsc  Bundle or bundled resource to check
  *
  * \return Maximum replicas for bundle corresponding to \p rsc
  */
 int
 pe__bundle_max(const pcmk_resource_t *rsc)
 {
     const pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true));
     return bundle_data->nreplicas;
 }
 
 /*!
  * \internal
  * \brief Get the resource inside a bundle
  *
  * \param[in] bundle  Bundle to check
  *
  * \return Resource inside \p bundle if any, otherwise NULL
  */
 pcmk_resource_t *
 pe__bundled_resource(const pcmk_resource_t *rsc)
 {
     const pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true));
     return bundle_data->child;
 }
 
 /*!
  * \internal
  * \brief Get containerized resource corresponding to a given bundle container
  *
  * \param[in] instance  Collective instance that might be a bundle container
  *
  * \return Bundled resource instance inside \p instance if it is a bundle
  *         container instance, otherwise NULL
  */
 const pcmk_resource_t *
 pe__get_rsc_in_container(const pcmk_resource_t *instance)
 {
     const pe__bundle_variant_data_t *data = NULL;
     const pcmk_resource_t *top = pe__const_top_resource(instance, true);
 
     if ((top == NULL) || (top->variant != pcmk_rsc_variant_bundle)) {
         return NULL;
     }
     get_bundle_variant_data(data, top);
 
     for (const GList *iter = data->replicas; iter != NULL; iter = iter->next) {
         const pcmk__bundle_replica_t *replica = iter->data;
 
         if (instance == replica->container) {
             return replica->child;
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Check whether a given node is created by a bundle
  *
  * \param[in] bundle  Bundle resource to check
  * \param[in] node    Node to check
  *
  * \return true if \p node is an instance of \p bundle, otherwise false
  */
 bool
 pe__node_is_bundle_instance(const pcmk_resource_t *bundle,
                             const pcmk_node_t *node)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, bundle);
     for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
         pcmk__bundle_replica_t *replica = iter->data;
 
         if (pcmk__same_node(node, replica->node)) {
             return true;
         }
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Get the container of a bundle's first replica
  *
  * \param[in] bundle  Bundle resource to get container for
  *
  * \return Container resource from first replica of \p bundle if any,
  *         otherwise NULL
  */
 pcmk_resource_t *
 pe__first_container(const pcmk_resource_t *bundle)
 {
     const pe__bundle_variant_data_t *bundle_data = NULL;
     const pcmk__bundle_replica_t *replica = NULL;
 
     get_bundle_variant_data(bundle_data, bundle);
     if (bundle_data->replicas == NULL) {
         return NULL;
     }
     replica = bundle_data->replicas->data;
     return replica->container;
 }
 
 /*!
  * \internal
  * \brief Iterate over bundle replicas
  *
  * \param[in,out] bundle     Bundle to iterate over
  * \param[in]     fn         Function to call for each replica (its return value
  *                           indicates whether to continue iterating)
  * \param[in,out] user_data  Pointer to pass to \p fn
  */
 void
 pe__foreach_bundle_replica(pcmk_resource_t *bundle,
                            bool (*fn)(pcmk__bundle_replica_t *, void *),
                            void *user_data)
 {
     const pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, bundle);
     for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
         if (!fn((pcmk__bundle_replica_t *) iter->data, user_data)) {
             break;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Iterate over const bundle replicas
  *
  * \param[in]     bundle     Bundle to iterate over
  * \param[in]     fn         Function to call for each replica (its return value
  *                           indicates whether to continue iterating)
  * \param[in,out] user_data  Pointer to pass to \p fn
  */
 void
 pe__foreach_const_bundle_replica(const pcmk_resource_t *bundle,
                                  bool (*fn)(const pcmk__bundle_replica_t *,
                                             void *),
                                  void *user_data)
 {
     const pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, bundle);
     for (const GList *iter = bundle_data->replicas; iter != NULL;
          iter = iter->next) {
 
         if (!fn((const pcmk__bundle_replica_t *) iter->data, user_data)) {
             break;
         }
     }
 }
 
 static char *
 next_ip(const char *last_ip)
 {
     unsigned int oct1 = 0;
     unsigned int oct2 = 0;
     unsigned int oct3 = 0;
     unsigned int oct4 = 0;
     int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4);
 
     if (rc != 4) {
         /*@ TODO check for IPv6 */
         return NULL;
 
     } else if (oct3 > 253) {
         return NULL;
 
     } else if (oct4 > 253) {
         ++oct3;
         oct4 = 1;
 
     } else {
         ++oct4;
     }
 
     return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4);
 }
 
 static void
 allocate_ip(pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica,
             GString *buffer)
 {
     if(data->ip_range_start == NULL) {
         return;
 
     } else if(data->ip_last) {
         replica->ipaddr = next_ip(data->ip_last);
 
     } else {
         replica->ipaddr = strdup(data->ip_range_start);
     }
 
     data->ip_last = replica->ipaddr;
     switch (data->agent_type) {
         case PE__CONTAINER_AGENT_DOCKER:
         case PE__CONTAINER_AGENT_PODMAN:
             if (data->add_host) {
                 g_string_append_printf(buffer, " --add-host=%s-%d:%s",
                                        data->prefix, replica->offset,
                                        replica->ipaddr);
             } else {
                 g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d",
                                        replica->ipaddr, data->prefix,
                                        replica->offset);
             }
             break;
 
         case PE__CONTAINER_AGENT_RKT:
             g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d",
                                    replica->ipaddr, data->prefix,
                                    replica->offset);
             break;
 
         default: // PE__CONTAINER_AGENT_UNKNOWN
             break;
     }
 }
 
 static xmlNode *
 create_resource(const char *name, const char *provider, const char *kind)
 {
     xmlNode *rsc = create_xml_node(NULL, PCMK_XE_PRIMITIVE);
 
     crm_xml_add(rsc, PCMK_XA_ID, name);
     crm_xml_add(rsc, PCMK_XA_CLASS, PCMK_RESOURCE_CLASS_OCF);
     crm_xml_add(rsc, PCMK_XA_PROVIDER, provider);
     crm_xml_add(rsc, PCMK_XA_TYPE, kind);
 
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Check whether cluster can manage resource inside container
  *
  * \param[in,out] data  Container variant data
  *
  * \return TRUE if networking configuration is acceptable, FALSE otherwise
  *
  * \note The resource is manageable if an IP range or control port has been
  *       specified. If a control port is used without an IP range, replicas per
  *       host must be 1.
  */
 static bool
 valid_network(pe__bundle_variant_data_t *data)
 {
     if(data->ip_range_start) {
         return TRUE;
     }
     if(data->control_port) {
         if(data->nreplicas_per_host > 1) {
             pcmk__config_err("Specifying the '" PCMK_XA_CONTROL_PORT "' for %s "
                              "requires '" PCMK_XA_REPLICAS_PER_HOST "=1'",
                              data->prefix);
             data->nreplicas_per_host = 1;
             // @TODO to be sure:
             // pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique);
         }
         return TRUE;
     }
     return FALSE;
 }
 
 static int
 create_ip_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data,
                    pcmk__bundle_replica_t *replica)
 {
     if(data->ip_range_start) {
         char *id = NULL;
         xmlNode *xml_ip = NULL;
         xmlNode *xml_obj = NULL;
 
         id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr);
         crm_xml_sanitize_id(id);
         xml_ip = create_resource(id, "heartbeat", "IPaddr2");
         free(id);
 
         xml_obj = create_xml_node(xml_ip, PCMK_XE_INSTANCE_ATTRIBUTES);
         crm_xml_set_id(xml_obj, "%s-attributes-%d",
                        data->prefix, replica->offset);
 
         crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr);
         if(data->host_network) {
             crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network);
         }
 
         if(data->host_netmask) {
             crm_create_nvpair_xml(xml_obj, NULL,
                                   "cidr_netmask", data->host_netmask);
 
         } else {
             crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32");
         }
 
         xml_obj = create_xml_node(xml_ip, PCMK_XE_OPERATIONS);
         crm_create_op_xml(xml_obj, pcmk__xe_id(xml_ip), PCMK_ACTION_MONITOR,
                           "60s", NULL);
 
         // TODO: Other ops? Timeouts and intervals from underlying resource?
 
         if (pe__unpack_resource(xml_ip, &replica->ip, parent,
                                 parent->cluster) != pcmk_rc_ok) {
             return pcmk_rc_unpack_error;
         }
 
         parent->children = g_list_append(parent->children, replica->ip);
     }
     return pcmk_rc_ok;
 }
 
 static const char*
 container_agent_str(enum pe__container_agent t)
 {
     switch (t) {
         case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S;
         case PE__CONTAINER_AGENT_RKT:    return PE__CONTAINER_AGENT_RKT_S;
         case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S;
         default: // PE__CONTAINER_AGENT_UNKNOWN
             break;
     }
     return PE__CONTAINER_AGENT_UNKNOWN_S;
 }
 
 static int
 create_container_resource(pcmk_resource_t *parent,
                           const pe__bundle_variant_data_t *data,
                           pcmk__bundle_replica_t *replica)
 {
     char *id = NULL;
     xmlNode *xml_container = NULL;
     xmlNode *xml_obj = NULL;
 
     // Agent-specific
     const char *hostname_opt = NULL;
     const char *env_opt = NULL;
     const char *agent_str = NULL;
     int volid = 0;  // rkt-only
 
     GString *buffer = NULL;
     GString *dbuffer = NULL;
 
     // Where syntax differences are drop-in replacements, set them now
     switch (data->agent_type) {
         case PE__CONTAINER_AGENT_DOCKER:
         case PE__CONTAINER_AGENT_PODMAN:
             hostname_opt = "-h ";
             env_opt = "-e ";
             break;
         case PE__CONTAINER_AGENT_RKT:
             hostname_opt = "--hostname=";
             env_opt = "--environment=";
             break;
         default:    // PE__CONTAINER_AGENT_UNKNOWN
             return pcmk_rc_unpack_error;
     }
     agent_str = container_agent_str(data->agent_type);
 
     buffer = g_string_sized_new(4096);
 
     id = crm_strdup_printf("%s-%s-%d", data->prefix, agent_str,
                            replica->offset);
     crm_xml_sanitize_id(id);
     xml_container = create_resource(id, "heartbeat", agent_str);
     free(id);
 
     xml_obj = create_xml_node(xml_container, PCMK_XE_INSTANCE_ATTRIBUTES);
     crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset);
 
     crm_create_nvpair_xml(xml_obj, NULL, "image", data->image);
     crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", PCMK_VALUE_TRUE);
     crm_create_nvpair_xml(xml_obj, NULL, "force_kill", PCMK_VALUE_FALSE);
     crm_create_nvpair_xml(xml_obj, NULL, "reuse", PCMK_VALUE_FALSE);
 
     if (data->agent_type == PE__CONTAINER_AGENT_DOCKER) {
         g_string_append(buffer, " --restart=no");
     }
 
     /* Set a container hostname only if we have an IP to map it to. The user can
      * set -h or --uts=host themselves if they want a nicer name for logs, but
      * this makes applications happy who need their  hostname to match the IP
      * they bind to.
      */
     if (data->ip_range_start != NULL) {
         g_string_append_printf(buffer, " %s%s-%d", hostname_opt, data->prefix,
                                replica->offset);
     }
     pcmk__g_strcat(buffer, " ", env_opt, "PCMK_stderr=1", NULL);
 
     if (data->container_network != NULL) {
         pcmk__g_strcat(buffer, " --net=", data->container_network, NULL);
     }
 
     if (data->control_port != NULL) {
         pcmk__g_strcat(buffer, " ", env_opt, "PCMK_" PCMK__ENV_REMOTE_PORT "=",
                        data->control_port, NULL);
     } else {
         g_string_append_printf(buffer, " %sPCMK_" PCMK__ENV_REMOTE_PORT "=%d",
                                env_opt, DEFAULT_REMOTE_PORT);
     }
 
     for (GList *iter = data->mounts; iter != NULL; iter = iter->next) {
         pe__bundle_mount_t *mount = (pe__bundle_mount_t *) iter->data;
         char *source = NULL;
 
         if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) {
             source = crm_strdup_printf("%s/%s-%d", mount->source, data->prefix,
                                        replica->offset);
             pcmk__add_separated_word(&dbuffer, 1024, source, ",");
         }
 
         switch (data->agent_type) {
             case PE__CONTAINER_AGENT_DOCKER:
             case PE__CONTAINER_AGENT_PODMAN:
                 pcmk__g_strcat(buffer,
                                " -v ", pcmk__s(source, mount->source),
                                ":", mount->target, NULL);
 
                 if (mount->options != NULL) {
                     pcmk__g_strcat(buffer, ":", mount->options, NULL);
                 }
                 break;
             case PE__CONTAINER_AGENT_RKT:
                 g_string_append_printf(buffer,
                                        " --volume vol%d,kind=host,"
                                        "source=%s%s%s "
                                        "--mount volume=vol%d,target=%s",
                                        volid, pcmk__s(source, mount->source),
                                        (mount->options != NULL)? "," : "",
                                        pcmk__s(mount->options, ""),
                                        volid, mount->target);
                 volid++;
                 break;
             default:
                 break;
         }
         free(source);
     }
 
     for (GList *iter = data->ports; iter != NULL; iter = iter->next) {
         pe__bundle_port_t *port = (pe__bundle_port_t *) iter->data;
 
         switch (data->agent_type) {
             case PE__CONTAINER_AGENT_DOCKER:
             case PE__CONTAINER_AGENT_PODMAN:
                 if (replica->ipaddr != NULL) {
                     pcmk__g_strcat(buffer,
                                    " -p ", replica->ipaddr, ":", port->source,
                                    ":", port->target, NULL);
 
                 } else if (!pcmk__str_eq(data->container_network,
                                          PCMK_VALUE_HOST, pcmk__str_none)) {
                     // No need to do port mapping if net == host
                     pcmk__g_strcat(buffer,
                                    " -p ", port->source, ":", port->target,
                                    NULL);
                 }
                 break;
             case PE__CONTAINER_AGENT_RKT:
                 if (replica->ipaddr != NULL) {
                     pcmk__g_strcat(buffer,
                                    " --port=", port->target,
                                    ":", replica->ipaddr, ":", port->source,
                                    NULL);
                 } else {
                     pcmk__g_strcat(buffer,
                                    " --port=", port->target, ":", port->source,
                                    NULL);
                 }
                 break;
             default:
                 break;
         }
     }
 
     /* @COMPAT: We should use pcmk__add_word() here, but we can't yet, because
      * it would cause restarts during rolling upgrades.
      *
      * In a previous version of the container resource creation logic, if
      * data->launcher_options is not NULL, we append
      * (" %s", data->launcher_options) even if data->launcher_options is an
      * empty string. Likewise for data->container_host_options. Using
      *
      *     pcmk__add_word(buffer, 0, data->launcher_options)
      *
      * removes that extra trailing space, causing a resource definition change.
      */
     if (data->launcher_options != NULL) {
         pcmk__g_strcat(buffer, " ", data->launcher_options, NULL);
     }
 
     if (data->container_host_options != NULL) {
         pcmk__g_strcat(buffer, " ", data->container_host_options, NULL);
     }
 
     crm_create_nvpair_xml(xml_obj, NULL, "run_opts",
                           (const char *) buffer->str);
     g_string_free(buffer, TRUE);
 
     crm_create_nvpair_xml(xml_obj, NULL, "mount_points",
                           (dbuffer != NULL)? (const char *) dbuffer->str : "");
     if (dbuffer != NULL) {
         g_string_free(dbuffer, TRUE);
     }
 
     if (replica->child != NULL) {
         if (data->container_command != NULL) {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   data->container_command);
         } else {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   SBIN_DIR "/pacemaker-remoted");
         }
 
         /* TODO: Allow users to specify their own?
          *
          * We just want to know if the container is alive; we'll monitor the
          * child independently.
          */
         crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
 #if 0
         /* @TODO Consider supporting the use case where we can start and stop
          * resources, but not proxy local commands (such as setting node
          * attributes), by running the local executor in stand-alone mode.
          * However, this would probably be better done via ACLs as with other
          * Pacemaker Remote nodes.
          */
     } else if ((child != NULL) && data->untrusted) {
         crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                               CRM_DAEMON_DIR "/pacemaker-execd");
         crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd",
                               CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke");
 #endif
     } else {
         if (data->container_command != NULL) {
             crm_create_nvpair_xml(xml_obj, NULL, "run_cmd",
                                   data->container_command);
         }
 
         /* TODO: Allow users to specify their own?
          *
          * We don't know what's in the container, so we just want to know if it
          * is alive.
          */
         crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true");
     }
 
     xml_obj = create_xml_node(xml_container, PCMK_XE_OPERATIONS);
     crm_create_op_xml(xml_obj, pcmk__xe_id(xml_container), PCMK_ACTION_MONITOR,
                       "60s", NULL);
 
     // TODO: Other ops? Timeouts and intervals from underlying resource?
     if (pe__unpack_resource(xml_container, &replica->container, parent,
                             parent->cluster) != pcmk_rc_ok) {
         return pcmk_rc_unpack_error;
     }
     pcmk__set_rsc_flags(replica->container, pcmk_rsc_replica_container);
     parent->children = g_list_append(parent->children, replica->container);
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \brief Ban a node from a resource's (and its children's) allowed nodes list
  *
  * \param[in,out] rsc    Resource to modify
  * \param[in]     uname  Name of node to ban
  */
 static void
 disallow_node(pcmk_resource_t *rsc, const char *uname)
 {
     gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname);
 
     if (match) {
-        ((pcmk_node_t *) match)->weight = -INFINITY;
+        ((pcmk_node_t *) match)->weight = -PCMK_SCORE_INFINITY;
         ((pcmk_node_t *) match)->rsc_discover_mode = pcmk_probe_never;
     }
     if (rsc->children) {
         g_list_foreach(rsc->children, (GFunc) disallow_node, (gpointer) uname);
     }
 }
 
 static int
 create_remote_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data,
                        pcmk__bundle_replica_t *replica)
 {
     if (replica->child && valid_network(data)) {
         GHashTableIter gIter;
         pcmk_node_t *node = NULL;
         xmlNode *xml_remote = NULL;
         char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset);
         char *port_s = NULL;
         const char *uname = NULL;
         const char *connect_name = NULL;
 
         if (pe_find_resource(parent->cluster->resources, id) != NULL) {
             free(id);
             // The biggest hammer we have
             id = crm_strdup_printf("pcmk-internal-%s-remote-%d",
                                    replica->child->id, replica->offset);
             //@TODO return error instead of asserting?
             CRM_ASSERT(pe_find_resource(parent->cluster->resources,
                                         id) == NULL);
         }
 
         /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the
          * connection does not have its own IP is a magic string that we use to
          * support nested remotes (i.e. a bundle running on a remote node).
          */
         connect_name = (replica->ipaddr? replica->ipaddr : "#uname");
 
         if (data->control_port == NULL) {
             port_s = pcmk__itoa(DEFAULT_REMOTE_PORT);
         }
 
         /* This sets replica->container as replica->remote's container, which is
          * similar to what happens with guest nodes. This is how the scheduler
          * knows that the bundle node is fenced by recovering the container, and
          * that remote should be ordered relative to the container.
          */
         xml_remote = pe_create_remote_xml(NULL, id, replica->container->id,
                                           NULL, NULL, NULL,
                                           connect_name, (data->control_port?
                                           data->control_port : port_s));
         free(port_s);
 
         /* Abandon our created ID, and pull the copy from the XML, because we
          * need something that will get freed during scheduler data cleanup to
          * use as the node ID and uname.
          */
         free(id);
         id = NULL;
         uname = pcmk__xe_id(xml_remote);
 
         /* Ensure a node has been created for the guest (it may have already
          * been, if it has a permanent node attribute), and ensure its weight is
          * -INFINITY so no other resources can run on it.
          */
         node = pe_find_node(parent->cluster->nodes, uname);
         if (node == NULL) {
             node = pe_create_node(uname, uname, PCMK_VALUE_REMOTE,
                                   PCMK_VALUE_MINUS_INFINITY, parent->cluster);
         } else {
-            node->weight = -INFINITY;
+            node->weight = -PCMK_SCORE_INFINITY;
         }
         node->rsc_discover_mode = pcmk_probe_never;
 
         /* unpack_remote_nodes() ensures that each remote node and guest node
          * has a pcmk_node_t entry. Ideally, it would do the same for bundle
          * nodes. Unfortunately, a bundle has to be mostly unpacked before it's
          * obvious what nodes will be needed, so we do it just above.
          *
          * Worse, that means that the node may have been utilized while
          * unpacking other resources, without our weight correction. The most
          * likely place for this to happen is when pe__unpack_resource() calls
          * resource_location() to set a default score in symmetric clusters.
          * This adds a node *copy* to each resource's allowed nodes, and these
          * copies will have the wrong weight.
          *
          * As a hacky workaround, fix those copies here.
          *
          * @TODO Possible alternative: ensure bundles are unpacked before other
          * resources, so the weight is correct before any copies are made.
          */
         g_list_foreach(parent->cluster->resources, (GFunc) disallow_node,
                        (gpointer) uname);
 
         replica->node = pe__copy_node(node);
         replica->node->weight = 500;
         replica->node->rsc_discover_mode = pcmk_probe_exclusive;
 
         /* Ensure the node shows up as allowed and with the correct discovery set */
         if (replica->child->allowed_nodes != NULL) {
             g_hash_table_destroy(replica->child->allowed_nodes);
         }
         replica->child->allowed_nodes = pcmk__strkey_table(NULL, free);
         g_hash_table_insert(replica->child->allowed_nodes,
                             (gpointer) replica->node->details->id,
                             pe__copy_node(replica->node));
 
         {
             pcmk_node_t *copy = pe__copy_node(replica->node);
-            copy->weight = -INFINITY;
+            copy->weight = -PCMK_SCORE_INFINITY;
             g_hash_table_insert(replica->child->parent->allowed_nodes,
                                 (gpointer) replica->node->details->id, copy);
         }
         if (pe__unpack_resource(xml_remote, &replica->remote, parent,
                                 parent->cluster) != pcmk_rc_ok) {
             return pcmk_rc_unpack_error;
         }
 
         g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes);
         while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) {
             if (pcmk__is_pacemaker_remote_node(node)) {
                 /* Remote resources can only run on 'normal' cluster node */
-                node->weight = -INFINITY;
+                node->weight = -PCMK_SCORE_INFINITY;
             }
         }
 
         replica->node->details->remote_rsc = replica->remote;
 
         // Ensure pcmk__is_guest_or_bundle_node() functions correctly
         replica->remote->container = replica->container;
 
         /* A bundle's #kind is closer to "container" (guest node) than the
          * "remote" set by pe_create_node().
          */
         pcmk__insert_dup(replica->node->details->attrs,
                          CRM_ATTR_KIND, "container");
 
         /* One effect of this is that setup_container() will add
          * replica->remote to replica->container's fillers, which will make
          * pe__resource_contains_guest_node() true for replica->container.
          *
          * replica->child does NOT get added to replica->container's fillers.
          * The only noticeable effect if it did would be for its fail count to
          * be taken into account when checking replica->container's migration
          * threshold.
          */
         parent->children = g_list_append(parent->children, replica->remote);
     }
     return pcmk_rc_ok;
 }
 
 static int
 create_replica_resources(pcmk_resource_t *parent,
                          pe__bundle_variant_data_t *data,
                          pcmk__bundle_replica_t *replica)
 {
     int rc = pcmk_rc_ok;
 
     rc = create_container_resource(parent, data, replica);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = create_ip_resource(parent, data, replica);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     rc = create_remote_resource(parent, data, replica);
     if (rc != pcmk_rc_ok) {
         return rc;
     }
 
     if ((replica->child != NULL) && (replica->ipaddr != NULL)) {
         pcmk__insert_meta(replica->child, "external-ip", replica->ipaddr);
     }
 
     if (replica->remote != NULL) {
         /*
          * Allow the remote connection resource to be allocated to a
          * different node than the one on which the container is active.
          *
          * This makes it possible to have Pacemaker Remote nodes running
          * containers with pacemaker-remoted inside in order to start
          * services inside those containers.
          */
         pcmk__set_rsc_flags(replica->remote, pcmk_rsc_remote_nesting_allowed);
     }
     return rc;
 }
 
 static void
 mount_add(pe__bundle_variant_data_t *bundle_data, const char *source,
           const char *target, const char *options, uint32_t flags)
 {
     pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t));
 
     CRM_ASSERT(mount != NULL);
     mount->source = strdup(source);
     mount->target = strdup(target);
     pcmk__str_update(&mount->options, options);
     mount->flags = flags;
     bundle_data->mounts = g_list_append(bundle_data->mounts, mount);
 }
 
 static void
 mount_free(pe__bundle_mount_t *mount)
 {
     free(mount->source);
     free(mount->target);
     free(mount->options);
     free(mount);
 }
 
 static void
 port_free(pe__bundle_port_t *port)
 {
     free(port->source);
     free(port->target);
     free(port);
 }
 
 static pcmk__bundle_replica_t *
 replica_for_remote(pcmk_resource_t *remote)
 {
     pcmk_resource_t *top = remote;
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     if (top == NULL) {
         return NULL;
     }
 
     while (top->parent != NULL) {
         top = top->parent;
     }
 
     get_bundle_variant_data(bundle_data, top);
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
 
         if (replica->remote == remote) {
             return replica;
         }
     }
     CRM_LOG_ASSERT(FALSE);
     return NULL;
 }
 
 bool
 pe__bundle_needs_remote_name(pcmk_resource_t *rsc)
 {
     const char *value;
     GHashTable *params = NULL;
 
     if (rsc == NULL) {
         return false;
     }
 
     // Use NULL node since pcmk__bundle_expand() uses that to set value
     params = pe_rsc_params(rsc, NULL, rsc->cluster);
     value = g_hash_table_lookup(params, PCMK_REMOTE_RA_ADDR);
 
     return pcmk__str_eq(value, "#uname", pcmk__str_casei)
            && xml_contains_remote_node(rsc->xml);
 }
 
 const char *
 pe__add_bundle_remote_name(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler,
                            xmlNode *xml, const char *field)
 {
     // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside
 
     pcmk_node_t *node = NULL;
     pcmk__bundle_replica_t *replica = NULL;
 
     if (!pe__bundle_needs_remote_name(rsc)) {
         return NULL;
     }
 
     replica = replica_for_remote(rsc);
     if (replica == NULL) {
         return NULL;
     }
 
     node = replica->container->allocated_to;
     if (node == NULL) {
         /* If it won't be running anywhere after the
          * transition, go with where it's running now.
          */
         node = pcmk__current_node(replica->container);
     }
 
     if(node == NULL) {
         crm_trace("Cannot determine address for bundle connection %s", rsc->id);
         return NULL;
     }
 
     crm_trace("Setting address for bundle connection %s to bundle host %s",
               rsc->id, pcmk__node_name(node));
     if(xml != NULL && field != NULL) {
         crm_xml_add(xml, field, node->details->uname);
     }
 
     return node->details->uname;
 }
 
 #define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do {     \
         flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,           \
                                    "Bundle mount", pcmk__xe_id(mount_xml),  \
                                    flags, (flags_to_set), #flags_to_set);   \
     } while (0)
 
 gboolean
 pe__unpack_bundle(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     const char *value = NULL;
     xmlNode *xml_obj = NULL;
     const xmlNode *xml_child = NULL;
     xmlNode *xml_resource = NULL;
     pe__bundle_variant_data_t *bundle_data = NULL;
     bool need_log_mount = TRUE;
 
     CRM_ASSERT(rsc != NULL);
     pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t));
     rsc->variant_opaque = bundle_data;
     bundle_data->prefix = strdup(rsc->id);
 
     xml_obj = first_named_child(rsc->xml, PCMK_XE_DOCKER);
     if (xml_obj != NULL) {
         bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER;
     } else {
         xml_obj = first_named_child(rsc->xml, PCMK__XE_RKT);
         if (xml_obj != NULL) {
             pcmk__warn_once(pcmk__wo_rkt,
                             "Support for " PCMK__XE_RKT " in bundles "
                             "(such as %s) is deprecated and will be "
                             "removed in a future release", rsc->id);
             bundle_data->agent_type = PE__CONTAINER_AGENT_RKT;
         } else {
             xml_obj = first_named_child(rsc->xml, PCMK_XE_PODMAN);
             if (xml_obj != NULL) {
                 bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN;
             } else {
                 return FALSE;
             }
         }
     }
 
     // Use 0 for default, minimum, and invalid PCMK_XA_PROMOTED_MAX
     value = crm_element_value(xml_obj, PCMK_XA_PROMOTED_MAX);
     if (value == NULL) {
         // @COMPAT deprecated since 2.0.0
         value = crm_element_value(xml_obj, PCMK__XA_PROMOTED_MAX_LEGACY);
     }
     pcmk__scan_min_int(value, &bundle_data->promoted_max, 0);
 
     /* Default replicas to PCMK_XA_PROMOTED_MAX if it was specified and 1
      * otherwise
      */
     value = crm_element_value(xml_obj, PCMK_XA_REPLICAS);
     if ((value == NULL) && (bundle_data->promoted_max > 0)) {
         bundle_data->nreplicas = bundle_data->promoted_max;
     } else {
         pcmk__scan_min_int(value, &bundle_data->nreplicas, 1);
     }
 
     /*
      * Communication between containers on the same host via the
      * floating IPs only works if the container is started with:
      *   --userland-proxy=false --ip-masq=false
      */
     value = crm_element_value(xml_obj, PCMK_XA_REPLICAS_PER_HOST);
     pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1);
     if (bundle_data->nreplicas_per_host == 1) {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique);
     }
 
     bundle_data->container_command =
         crm_element_value_copy(xml_obj, PCMK_XA_RUN_COMMAND);
     bundle_data->launcher_options = crm_element_value_copy(xml_obj,
                                                            PCMK_XA_OPTIONS);
     bundle_data->image = crm_element_value_copy(xml_obj, PCMK_XA_IMAGE);
     bundle_data->container_network = crm_element_value_copy(xml_obj,
                                                             PCMK_XA_NETWORK);
 
     xml_obj = first_named_child(rsc->xml, PCMK_XE_NETWORK);
     if(xml_obj) {
         bundle_data->ip_range_start =
             crm_element_value_copy(xml_obj, PCMK_XA_IP_RANGE_START);
         bundle_data->host_netmask =
             crm_element_value_copy(xml_obj, PCMK_XA_HOST_NETMASK);
         bundle_data->host_network =
             crm_element_value_copy(xml_obj, PCMK_XA_HOST_INTERFACE);
         bundle_data->control_port =
             crm_element_value_copy(xml_obj, PCMK_XA_CONTROL_PORT);
         value = crm_element_value(xml_obj, PCMK_XA_ADD_HOST);
         if (crm_str_to_boolean(value, &bundle_data->add_host) != 1) {
             bundle_data->add_host = TRUE;
         }
 
         for (xml_child = first_named_child(xml_obj, PCMK_XE_PORT_MAPPING);
              xml_child != NULL; xml_child = crm_next_same_xml(xml_child)) {
 
             pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t));
             port->source = crm_element_value_copy(xml_child, PCMK_XA_PORT);
 
             if(port->source == NULL) {
                 port->source = crm_element_value_copy(xml_child, PCMK_XA_RANGE);
             } else {
                 port->target = crm_element_value_copy(xml_child,
                                                       PCMK_XA_INTERNAL_PORT);
             }
 
             if(port->source != NULL && strlen(port->source) > 0) {
                 if(port->target == NULL) {
                     port->target = strdup(port->source);
                 }
                 bundle_data->ports = g_list_append(bundle_data->ports, port);
 
             } else {
                 pcmk__config_err("Invalid " PCMK_XA_PORT " directive %s",
                                  pcmk__xe_id(xml_child));
                 port_free(port);
             }
         }
     }
 
     xml_obj = first_named_child(rsc->xml, PCMK_XE_STORAGE);
     for (xml_child = first_named_child(xml_obj, PCMK_XE_STORAGE_MAPPING);
          xml_child != NULL; xml_child = crm_next_same_xml(xml_child)) {
 
         const char *source = crm_element_value(xml_child, PCMK_XA_SOURCE_DIR);
         const char *target = crm_element_value(xml_child, PCMK_XA_TARGET_DIR);
         const char *options = crm_element_value(xml_child, PCMK_XA_OPTIONS);
         int flags = pe__bundle_mount_none;
 
         if (source == NULL) {
             source = crm_element_value(xml_child, PCMK_XA_SOURCE_DIR_ROOT);
             pe__set_bundle_mount_flags(xml_child, flags,
                                        pe__bundle_mount_subdir);
         }
 
         if (source && target) {
             mount_add(bundle_data, source, target, options, flags);
             if (strcmp(target, "/var/log") == 0) {
                 need_log_mount = FALSE;
             }
         } else {
             pcmk__config_err("Invalid mount directive %s",
                              pcmk__xe_id(xml_child));
         }
     }
 
     xml_obj = first_named_child(rsc->xml, PCMK_XE_PRIMITIVE);
     if (xml_obj && valid_network(bundle_data)) {
         char *value = NULL;
         xmlNode *xml_set = NULL;
 
         xml_resource = create_xml_node(NULL, PCMK_XE_CLONE);
 
         /* @COMPAT We no longer use the <master> tag, but we need to keep it as
          * part of the resource name, so that bundles don't restart in a rolling
          * upgrade. (It also avoids needing to change regression tests.)
          */
         crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix,
                       (bundle_data->promoted_max? "master"
                       : (const char *)xml_resource->name));
 
         xml_set = create_xml_node(xml_resource, PCMK_XE_META_ATTRIBUTES);
         crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name);
 
         crm_create_nvpair_xml(xml_set, NULL,
                               PCMK_META_ORDERED, PCMK_VALUE_TRUE);
 
         value = pcmk__itoa(bundle_data->nreplicas);
         crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_MAX, value);
         free(value);
 
         value = pcmk__itoa(bundle_data->nreplicas_per_host);
         crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_NODE_MAX, value);
         free(value);
 
         crm_create_nvpair_xml(xml_set, NULL, PCMK_META_GLOBALLY_UNIQUE,
                               pcmk__btoa(bundle_data->nreplicas_per_host > 1));
 
         if (bundle_data->promoted_max) {
             crm_create_nvpair_xml(xml_set, NULL,
                                   PCMK_META_PROMOTABLE, PCMK_VALUE_TRUE);
 
             value = pcmk__itoa(bundle_data->promoted_max);
             crm_create_nvpair_xml(xml_set, NULL, PCMK_META_PROMOTED_MAX, value);
             free(value);
         }
 
         //crm_xml_add(xml_obj, PCMK_XA_ID, bundle_data->prefix);
         add_node_copy(xml_resource, xml_obj);
 
     } else if(xml_obj) {
         pcmk__config_err("Cannot control %s inside %s without either "
                          PCMK_XA_IP_RANGE_START " or " PCMK_XA_CONTROL_PORT,
                          rsc->id, pcmk__xe_id(xml_obj));
         return FALSE;
     }
 
     if(xml_resource) {
         int lpc = 0;
         GList *childIter = NULL;
         pe__bundle_port_t *port = NULL;
         GString *buffer = NULL;
 
         if (pe__unpack_resource(xml_resource, &(bundle_data->child), rsc,
                                 scheduler) != pcmk_rc_ok) {
             return FALSE;
         }
 
         /* Currently, we always map the default authentication key location
          * into the same location inside the container.
          *
          * Ideally, we would respect the host's PCMK_authkey_location, but:
          * - it may be different on different nodes;
          * - the actual connection will do extra checking to make sure the key
          *   file exists and is readable, that we can't do here on the DC
          * - tools such as crm_resource and crm_simulate may not have the same
          *   environment variables as the cluster, causing operation digests to
          *   differ
          *
          * Always using the default location inside the container is fine,
          * because we control the pacemaker_remote environment, and it avoids
          * having to pass another environment variable to the container.
          *
          * @TODO A better solution may be to have only pacemaker_remote use the
          * environment variable, and have the cluster nodes use a new
          * cluster option for key location. This would introduce the limitation
          * of the location being the same on all cluster nodes, but that's
          * reasonable.
          */
         mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION,
                   DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none);
 
         if (need_log_mount) {
             mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL,
                       pe__bundle_mount_subdir);
         }
 
         port = calloc(1, sizeof(pe__bundle_port_t));
         if(bundle_data->control_port) {
             port->source = strdup(bundle_data->control_port);
         } else {
             /* If we wanted to respect PCMK_remote_port, we could use
              * crm_default_remote_port() here and elsewhere in this file instead
              * of DEFAULT_REMOTE_PORT.
              *
              * However, it gains nothing, since we control both the container
              * environment and the connection resource parameters, and the user
              * can use a different port if desired by setting
              * PCMK_XA_CONTROL_PORT.
              */
             port->source = pcmk__itoa(DEFAULT_REMOTE_PORT);
         }
         port->target = strdup(port->source);
         bundle_data->ports = g_list_append(bundle_data->ports, port);
 
         buffer = g_string_sized_new(1024);
         for (childIter = bundle_data->child->children; childIter != NULL;
              childIter = childIter->next) {
 
             pcmk__bundle_replica_t *replica = NULL;
 
             replica = calloc(1, sizeof(pcmk__bundle_replica_t));
             replica->child = childIter->data;
             replica->child->exclusive_discover = TRUE;
             replica->offset = lpc++;
 
             // Ensure the child's notify gets set based on the underlying primitive's value
             if (pcmk_is_set(replica->child->flags, pcmk_rsc_notify)) {
                 pcmk__set_rsc_flags(bundle_data->child, pcmk_rsc_notify);
             }
 
             allocate_ip(bundle_data, replica, buffer);
             bundle_data->replicas = g_list_append(bundle_data->replicas,
                                                   replica);
             bundle_data->attribute_target =
                 g_hash_table_lookup(replica->child->meta,
                                     PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
         }
         bundle_data->container_host_options = g_string_free(buffer, FALSE);
 
         if (bundle_data->attribute_target) {
             pcmk__insert_dup(rsc->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
                              bundle_data->attribute_target);
             pcmk__insert_dup(bundle_data->child->meta,
                              PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
                              bundle_data->attribute_target);
         }
 
     } else {
         // Just a naked container, no pacemaker-remote
         GString *buffer = g_string_sized_new(1024);
 
         for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) {
             pcmk__bundle_replica_t *replica = NULL;
 
             replica = calloc(1, sizeof(pcmk__bundle_replica_t));
             replica->offset = lpc;
             allocate_ip(bundle_data, replica, buffer);
             bundle_data->replicas = g_list_append(bundle_data->replicas,
                                                   replica);
         }
         bundle_data->container_host_options = g_string_free(buffer, FALSE);
     }
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
 
         if (create_replica_resources(rsc, bundle_data, replica) != pcmk_rc_ok) {
             pcmk__config_err("Failed unpacking resource %s", rsc->id);
             rsc->fns->free(rsc);
             return FALSE;
         }
 
         /* Utilization needs special handling for bundles. It makes no sense for
          * the inner primitive to have utilization, because it is tied
          * one-to-one to the guest node created by the container resource -- and
          * there's no way to set capacities for that guest node anyway.
          *
          * What the user really wants is to configure utilization for the
          * container. However, the schema only allows utilization for
          * primitives, and the container resource is implicit anyway, so the
          * user can *only* configure utilization for the inner primitive. If
          * they do, move the primitive's utilization values to the container.
          *
          * @TODO This means that bundles without an inner primitive can't have
          * utilization. An alternative might be to allow utilization values in
          * the top-level bundle XML in the schema, and copy those to each
          * container.
          */
         if (replica->child != NULL) {
             GHashTable *empty = replica->container->utilization;
 
             replica->container->utilization = replica->child->utilization;
             replica->child->utilization = empty;
         }
     }
 
     if (bundle_data->child) {
         rsc->children = g_list_append(rsc->children, bundle_data->child);
     }
     return TRUE;
 }
 
 static int
 replica_resource_active(pcmk_resource_t *rsc, gboolean all)
 {
     if (rsc) {
         gboolean child_active = rsc->fns->active(rsc, all);
 
         if (child_active && !all) {
             return TRUE;
         } else if (!child_active && all) {
             return FALSE;
         }
     }
     return -1;
 }
 
 gboolean
 pe__bundle_active(pcmk_resource_t *rsc, gboolean all)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     GList *iter = NULL;
 
     get_bundle_variant_data(bundle_data, rsc);
     for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) {
         pcmk__bundle_replica_t *replica = iter->data;
         int rsc_active;
 
         rsc_active = replica_resource_active(replica->ip, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->child, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->container, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
 
         rsc_active = replica_resource_active(replica->remote, all);
         if (rsc_active >= 0) {
             return (gboolean) rsc_active;
         }
     }
 
     /* If "all" is TRUE, we've already checked that no resources were inactive,
      * so return TRUE; if "all" is FALSE, we didn't find any active resources,
      * so return FALSE.
      */
     return all;
 }
 
 /*!
  * \internal
  * \brief Find the bundle replica corresponding to a given node
  *
  * \param[in] bundle  Top-level bundle resource
  * \param[in] node    Node to search for
  *
  * \return Bundle replica if found, NULL otherwise
  */
 pcmk_resource_t *
 pe__find_bundle_replica(const pcmk_resource_t *bundle, const pcmk_node_t *node)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     CRM_ASSERT(bundle && node);
 
     get_bundle_variant_data(bundle_data, bundle);
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica && replica->node);
         if (pcmk__same_node(replica->node, node)) {
             return replica->child;
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 print_rsc_in_list(pcmk_resource_t *rsc, const char *pre_text, long options,
                   void *print_data)
 {
     if (rsc != NULL) {
         if (options & pe_print_html) {
             status_print("<li>");
         }
         rsc->fns->print(rsc, pre_text, options, print_data);
         if (options & pe_print_html) {
             status_print("</li>\n");
         }
     }
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 bundle_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options,
                  void *print_data)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     char *child_text = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     if (pre_text == NULL) {
         pre_text = "";
     }
     child_text = crm_strdup_printf("%s        ", pre_text);
 
     get_bundle_variant_data(bundle_data, rsc);
 
     status_print("%s<bundle ", pre_text);
     status_print(PCMK_XA_ID "=\"%s\" ", rsc->id);
     status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type));
     status_print("image=\"%s\" ", bundle_data->image);
     status_print("unique=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
     status_print("managed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_managed));
     status_print("failed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_failed));
     status_print(">\n");
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica);
         status_print("%s    <replica " PCMK_XA_ID "=\"%d\">\n",
                      pre_text, replica->offset);
         print_rsc_in_list(replica->ip, child_text, options, print_data);
         print_rsc_in_list(replica->child, child_text, options, print_data);
         print_rsc_in_list(replica->container, child_text, options, print_data);
         print_rsc_in_list(replica->remote, child_text, options, print_data);
         status_print("%s    </replica>\n", pre_text);
     }
     status_print("%s</bundle>\n", pre_text);
     free(child_text);
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__bundle_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean printed_header = FALSE;
     gboolean print_everything = TRUE;
 
     const char *desc = NULL;
 
     CRM_ASSERT(rsc != NULL);
     
     get_bundle_variant_data(bundle_data, rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
         char *id = NULL;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) {
             continue;
         }
 
         if (!printed_header) {
             const char *type = container_agent_str(bundle_data->agent_type);
             const char *unique = pcmk__flag_text(rsc->flags, pcmk_rsc_unique);
             const char *maintenance = pcmk__flag_text(rsc->flags,
                                                       pcmk_rsc_maintenance);
             const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed);
             const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed);
 
             printed_header = TRUE;
 
             desc = pe__resource_description(rsc, show_opts);
 
             rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_BUNDLE, 8,
                                           PCMK_XA_ID, rsc->id,
                                           PCMK_XA_TYPE, type,
                                           PCMK_XA_IMAGE, bundle_data->image,
                                           PCMK_XA_UNIQUE, unique,
                                           PCMK_XA_MAINTENANCE, maintenance,
                                           PCMK_XA_MANAGED, managed,
                                           PCMK_XA_FAILED, failed,
                                           PCMK_XA_DESCRIPTION, desc);
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
 
         id = pcmk__itoa(replica->offset);
         rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, 1,
                                       PCMK_XA_ID, id);
         free(id);
         CRM_ASSERT(rc == pcmk_rc_ok);
 
         if (print_ip) {
             out->message(out, crm_map_element_name(replica->ip->xml), show_opts,
                          replica->ip, only_node, only_rsc);
         }
 
         if (print_child) {
             out->message(out, crm_map_element_name(replica->child->xml), show_opts,
                          replica->child, only_node, only_rsc);
         }
 
         if (print_ctnr) {
             out->message(out, crm_map_element_name(replica->container->xml), show_opts,
                          replica->container, only_node, only_rsc);
         }
 
         if (print_remote) {
             out->message(out, crm_map_element_name(replica->remote->xml), show_opts,
                          replica->remote, only_node, only_rsc);
         }
 
         pcmk__output_xml_pop_parent(out); // replica
     }
 
     if (printed_header) {
         pcmk__output_xml_pop_parent(out); // bundle
     }
 
     return rc;
 }
 
 static void
 pe__bundle_replica_output_html(pcmk__output_t *out,
                                pcmk__bundle_replica_t *replica,
                                pcmk_node_t *node, uint32_t show_opts)
 {
     pcmk_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     pe__common_output_html(out, rsc, buffer, node, show_opts);
 }
 
 /*!
  * \internal
  * \brief Get a string describing a resource's unmanaged state or lack thereof
  *
  * \param[in] rsc  Resource to describe
  *
  * \return A string indicating that a resource is in maintenance mode or
  *         otherwise unmanaged, or an empty string otherwise
  */
 static const char *
 get_unmanaged_str(const pcmk_resource_t *rsc)
 {
     if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) {
         return " (maintenance)";
     }
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         return " (unmanaged)";
     }
     return "";
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__bundle_html(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     const char *desc = NULL;
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     CRM_ASSERT(rsc != NULL);
 
     get_bundle_variant_data(bundle_data, rsc);
 
     desc = pe__resource_description(rsc, show_opts);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) ||
             (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) {
             /* The text output messages used below require pe_print_implicit to
              * be set to do anything.
              */
             uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
 
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                                      desc ? " (" : "", desc ? desc : "", desc ? ")" : "",
                                      get_unmanaged_str(rsc));
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset);
             }
 
             if (print_ip) {
                 out->message(out, crm_map_element_name(replica->ip->xml),
                              new_show_opts, replica->ip, only_node, only_rsc);
             }
 
             if (print_child) {
                 out->message(out, crm_map_element_name(replica->child->xml),
                              new_show_opts, replica->child, only_node, only_rsc);
             }
 
             if (print_ctnr) {
                 out->message(out, crm_map_element_name(replica->container->xml),
                              new_show_opts, replica->container, only_node, only_rsc);
             }
 
             if (print_remote) {
                 out->message(out, crm_map_element_name(replica->remote->xml),
                              new_show_opts, replica->remote, only_node, only_rsc);
             }
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->end_list(out);
             }
         } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
             continue;
         } else {
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                                      desc ? " (" : "", desc ? desc : "", desc ? ")" : "",
                                      get_unmanaged_str(rsc));
 
             pe__bundle_replica_output_html(out, replica,
                                            pcmk__current_node(replica->container),
                                            show_opts);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 static void
 pe__bundle_replica_output_text(pcmk__output_t *out,
                                pcmk__bundle_replica_t *replica,
                                pcmk_node_t *node, uint32_t show_opts)
 {
     const pcmk_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     pe__common_output_text(out, rsc, buffer, node, show_opts);
 }
 
 PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__bundle_text(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     const char *desc = NULL;
     pe__bundle_variant_data_t *bundle_data = NULL;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     desc = pe__resource_description(rsc, show_opts);
     
     get_bundle_variant_data(bundle_data, rsc);
 
     CRM_ASSERT(rsc != NULL);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches);
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
         gboolean print_ip, print_child, print_ctnr, print_remote;
 
         CRM_ASSERT(replica);
 
         if (pcmk__rsc_filtered_by_node(replica->container, only_node)) {
             continue;
         }
 
         print_ip = replica->ip != NULL &&
                    !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything);
         print_child = replica->child != NULL &&
                       !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything);
         print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything);
         print_remote = replica->remote != NULL &&
                        !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything);
 
         if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) ||
             (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) {
             /* The text output messages used below require pe_print_implicit to
              * be set to do anything.
              */
             uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs;
 
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                                      desc ? " (" : "", desc ? desc : "", desc ? ")" : "",
                                      get_unmanaged_str(rsc));
 
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 out->list_item(out, NULL, "Replica[%d]", replica->offset);
             }
 
             out->begin_list(out, NULL, NULL, NULL);
 
             if (print_ip) {
                 out->message(out, crm_map_element_name(replica->ip->xml),
                              new_show_opts, replica->ip, only_node, only_rsc);
             }
 
             if (print_child) {
                 out->message(out, crm_map_element_name(replica->child->xml),
                              new_show_opts, replica->child, only_node, only_rsc);
             }
 
             if (print_ctnr) {
                 out->message(out, crm_map_element_name(replica->container->xml),
                              new_show_opts, replica->container, only_node, only_rsc);
             }
 
             if (print_remote) {
                 out->message(out, crm_map_element_name(replica->remote->xml),
                              new_show_opts, replica->remote, only_node, only_rsc);
             }
 
             out->end_list(out);
         } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) {
             continue;
         } else {
             PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s",
                                      (bundle_data->nreplicas > 1)? " set" : "",
                                      rsc->id, bundle_data->image,
                                      pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                                      desc ? " (" : "", desc ? desc : "", desc ? ")" : "",
                                      get_unmanaged_str(rsc));
 
             pe__bundle_replica_output_text(out, replica,
                                            pcmk__current_node(replica->container),
                                            show_opts);
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 print_bundle_replica(pcmk__bundle_replica_t *replica, const char *pre_text,
                      long options, void *print_data)
 {
     pcmk_node_t *node = NULL;
     pcmk_resource_t *rsc = replica->child;
 
     int offset = 0;
     char buffer[LINE_MAX];
 
     if(rsc == NULL) {
         rsc = replica->container;
     }
 
     if (replica->remote) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->remote));
     } else {
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s",
                            rsc_printable_id(replica->container));
     }
     if (replica->ipaddr) {
         offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)",
                            replica->ipaddr);
     }
 
     node = pcmk__current_node(replica->container);
     common_print(rsc, pre_text, buffer, node, options, print_data);
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 void
 pe__print_bundle(pcmk_resource_t *rsc, const char *pre_text, long options,
                  void *print_data)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     char *child_text = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     if (options & pe_print_xml) {
         bundle_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     get_bundle_variant_data(bundle_data, rsc);
 
     if (pre_text == NULL) {
         pre_text = " ";
     }
 
     status_print("%sContainer bundle%s: %s [%s]%s%s\n",
                  pre_text, ((bundle_data->nreplicas > 1)? " set" : ""),
                  rsc->id, bundle_data->image,
                  pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                  pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : " (unmanaged)");
     if (options & pe_print_html) {
         status_print("<br />\n<ul>\n");
     }
 
 
     for (GList *gIter = bundle_data->replicas; gIter != NULL;
          gIter = gIter->next) {
         pcmk__bundle_replica_t *replica = gIter->data;
 
         CRM_ASSERT(replica);
         if (options & pe_print_html) {
             status_print("<li>");
         }
 
         if (pcmk_is_set(options, pe_print_implicit)) {
             child_text = crm_strdup_printf("     %s", pre_text);
             if (pcmk__list_of_multiple(bundle_data->replicas)) {
                 status_print("  %sReplica[%d]\n", pre_text, replica->offset);
             }
             if (options & pe_print_html) {
                 status_print("<br />\n<ul>\n");
             }
             print_rsc_in_list(replica->ip, child_text, options, print_data);
             print_rsc_in_list(replica->container, child_text, options, print_data);
             print_rsc_in_list(replica->remote, child_text, options, print_data);
             print_rsc_in_list(replica->child, child_text, options, print_data);
             if (options & pe_print_html) {
                 status_print("</ul>\n");
             }
         } else {
             child_text = crm_strdup_printf("%s  ", pre_text);
             print_bundle_replica(replica, child_text, options, print_data);
         }
         free(child_text);
 
         if (options & pe_print_html) {
             status_print("</li>\n");
         }
     }
     if (options & pe_print_html) {
         status_print("</ul>\n");
     }
 }
 
 static void
 free_bundle_replica(pcmk__bundle_replica_t *replica)
 {
     if (replica == NULL) {
         return;
     }
 
     if (replica->node) {
         free(replica->node);
         replica->node = NULL;
     }
 
     if (replica->ip) {
         free_xml(replica->ip->xml);
         replica->ip->xml = NULL;
         replica->ip->fns->free(replica->ip);
         replica->ip = NULL;
     }
     if (replica->container) {
         free_xml(replica->container->xml);
         replica->container->xml = NULL;
         replica->container->fns->free(replica->container);
         replica->container = NULL;
     }
     if (replica->remote) {
         free_xml(replica->remote->xml);
         replica->remote->xml = NULL;
         replica->remote->fns->free(replica->remote);
         replica->remote = NULL;
     }
     free(replica->ipaddr);
     free(replica);
 }
 
 void
 pe__free_bundle(pcmk_resource_t *rsc)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
     CRM_CHECK(rsc != NULL, return);
 
     get_bundle_variant_data(bundle_data, rsc);
     pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
 
     free(bundle_data->prefix);
     free(bundle_data->image);
     free(bundle_data->control_port);
     free(bundle_data->host_network);
     free(bundle_data->host_netmask);
     free(bundle_data->ip_range_start);
     free(bundle_data->container_network);
     free(bundle_data->launcher_options);
     free(bundle_data->container_command);
     g_free(bundle_data->container_host_options);
 
     g_list_free_full(bundle_data->replicas,
                      (GDestroyNotify) free_bundle_replica);
     g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free);
     g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free);
     g_list_free(rsc->children);
 
     if(bundle_data->child) {
         free_xml(bundle_data->child->xml);
         bundle_data->child->xml = NULL;
         bundle_data->child->fns->free(bundle_data->child);
     }
     common_free(rsc);
 }
 
 enum rsc_role_e
 pe__bundle_resource_state(const pcmk_resource_t *rsc, gboolean current)
 {
     enum rsc_role_e container_role = pcmk_role_unknown;
     return container_role;
 }
 
 /*!
  * \brief Get the number of configured replicas in a bundle
  *
  * \param[in] rsc  Bundle resource
  *
  * \return Number of configured replicas, or 0 on error
  */
 int
 pe_bundle_replicas(const pcmk_resource_t *rsc)
 {
     if ((rsc == NULL) || (rsc->variant != pcmk_rsc_variant_bundle)) {
         return 0;
     } else {
         pe__bundle_variant_data_t *bundle_data = NULL;
 
         get_bundle_variant_data(bundle_data, rsc);
         return bundle_data->nreplicas;
     }
 }
 
 void
 pe__count_bundle(pcmk_resource_t *rsc)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, rsc);
     for (GList *item = bundle_data->replicas; item != NULL; item = item->next) {
         pcmk__bundle_replica_t *replica = item->data;
 
         if (replica->ip) {
             replica->ip->fns->count(replica->ip);
         }
         if (replica->child) {
             replica->child->fns->count(replica->child);
         }
         if (replica->container) {
             replica->container->fns->count(replica->container);
         }
         if (replica->remote) {
             replica->remote->fns->count(replica->remote);
         }
     }
 }
 
 gboolean
 pe__bundle_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
                        gboolean check_parent)
 {
     gboolean passes = FALSE;
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else {
         get_bundle_variant_data(bundle_data, rsc);
 
         for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) {
             pcmk__bundle_replica_t *replica = gIter->data;
 
             if (replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (!replica->container->fns->is_filtered(replica->container, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             } else if (replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, FALSE)) {
                 passes = TRUE;
                 break;
             }
         }
     }
 
     return !passes;
 }
 
 /*!
  * \internal
  * \brief Get a list of a bundle's containers
  *
  * \param[in] bundle  Bundle resource
  *
  * \return Newly created list of \p bundle's containers
  * \note It is the caller's responsibility to free the result with
  *       g_list_free().
  */
 GList *
 pe__bundle_containers(const pcmk_resource_t *bundle)
 {
     GList *containers = NULL;
     const pe__bundle_variant_data_t *data = NULL;
 
     get_bundle_variant_data(data, bundle);
     for (GList *iter = data->replicas; iter != NULL; iter = iter->next) {
         pcmk__bundle_replica_t *replica = iter->data;
 
         containers = g_list_append(containers, replica->container);
     }
     return containers;
 }
 
 // Bundle implementation of pcmk_rsc_methods_t:active_node()
 pcmk_node_t *
 pe__bundle_active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
                        unsigned int *count_clean)
 {
     pcmk_node_t *active = NULL;
     pcmk_node_t *node = NULL;
     pcmk_resource_t *container = NULL;
     GList *containers = NULL;
     GList *iter = NULL;
     GHashTable *nodes = NULL;
     const pe__bundle_variant_data_t *data = NULL;
 
     if (count_all != NULL) {
         *count_all = 0;
     }
     if (count_clean != NULL) {
         *count_clean = 0;
     }
     if (rsc == NULL) {
         return NULL;
     }
 
     /* For the purposes of this method, we only care about where the bundle's
      * containers are active, so build a list of active containers.
      */
     get_bundle_variant_data(data, rsc);
     for (iter = data->replicas; iter != NULL; iter = iter->next) {
         pcmk__bundle_replica_t *replica = iter->data;
 
         if (replica->container->running_on != NULL) {
             containers = g_list_append(containers, replica->container);
         }
     }
     if (containers == NULL) {
         return NULL;
     }
 
     /* If the bundle has only a single active container, just use that
      * container's method. If live migration is ever supported for bundle
      * containers, this will allow us to prefer the migration source when there
      * is only one container and it is migrating. For now, this just lets us
      * avoid creating the nodes table.
      */
     if (pcmk__list_of_1(containers)) {
         container = containers->data;
         node = container->fns->active_node(container, count_all, count_clean);
         g_list_free(containers);
         return node;
     }
 
     // Add all containers' active nodes to a hash table (for uniqueness)
     nodes = g_hash_table_new(NULL, NULL);
     for (iter = containers; iter != NULL; iter = iter->next) {
         container = iter->data;
 
         for (GList *node_iter = container->running_on; node_iter != NULL;
              node_iter = node_iter->next) {
             node = node_iter->data;
 
             // If insert returns true, we haven't counted this node yet
             if (g_hash_table_insert(nodes, (gpointer) node->details,
                                     (gpointer) node)
                 && !pe__count_active_node(rsc, node, &active, count_all,
                                           count_clean)) {
                 goto done;
             }
         }
     }
 
 done:
     g_list_free(containers);
     g_hash_table_destroy(nodes);
     return active;
 }
 
 /*!
  * \internal
  * \brief Get maximum bundle resource instances per node
  *
  * \param[in] rsc  Bundle resource to check
  *
  * \return Maximum number of \p rsc instances that can be active on one node
  */
 unsigned int
 pe__bundle_max_per_node(const pcmk_resource_t *rsc)
 {
     pe__bundle_variant_data_t *bundle_data = NULL;
 
     get_bundle_variant_data(bundle_data, rsc);
     CRM_ASSERT(bundle_data->nreplicas_per_host >= 0);
     return (unsigned int) bundle_data->nreplicas_per_host;
 }
diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c
index 54c3bb09c7..d4f8dfbcdf 100644
--- a/lib/pengine/clone.c
+++ b/lib/pengine/clone.c
@@ -1,1540 +1,1540 @@
 /*
  * 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 <crm_internal.h>
 
 #include <stdint.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 #include <crm/common/xml.h>
 #include <crm/common/output.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/scheduler_internal.h>
 
 #ifdef PCMK__COMPAT_2_0
 #define PROMOTED_INSTANCES   PCMK__ROLE_PROMOTED_LEGACY "s"
 #define UNPROMOTED_INSTANCES PCMK__ROLE_UNPROMOTED_LEGACY "s"
 #else
 #define PROMOTED_INSTANCES   PCMK__ROLE_PROMOTED
 #define UNPROMOTED_INSTANCES PCMK__ROLE_UNPROMOTED
 #endif
 
 typedef struct clone_variant_data_s {
     int clone_max;
     int clone_node_max;
 
     int promoted_max;
     int promoted_node_max;
 
     int total_clones;
 
     uint32_t flags; // Group of enum pcmk__clone_flags
 
     notify_data_t *stop_notify;
     notify_data_t *start_notify;
     notify_data_t *demote_notify;
     notify_data_t *promote_notify;
 
     xmlNode *xml_obj_child;
 } clone_variant_data_t;
 
 #define get_clone_variant_data(data, rsc)                                  \
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_clone)); \
     data = (clone_variant_data_t *) rsc->variant_opaque;
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed to be run
  *
  * \param[in] clone  Clone or clone instance to check
  *
  * \return Maximum instances for \p clone
  */
 int
 pe__clone_max(const pcmk_resource_t *clone)
 {
     const clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
     return clone_data->clone_max;
 }
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed per node
  *
  * \param[in] clone  Promotable clone or clone instance to check
  *
  * \return Maximum allowed instances per node for \p clone
  */
 int
 pe__clone_node_max(const pcmk_resource_t *clone)
 {
     const clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
     return clone_data->clone_node_max;
 }
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed to be promoted
  *
  * \param[in] clone  Promotable clone or clone instance to check
  *
  * \return Maximum promoted instances for \p clone
  */
 int
 pe__clone_promoted_max(const pcmk_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
     return clone_data->promoted_max;
 }
 
 /*!
  * \internal
  * \brief Return the maximum number of clone instances allowed to be promoted
  *
  * \param[in] clone  Promotable clone or clone instance to check
  *
  * \return Maximum promoted instances for \p clone
  */
 int
 pe__clone_promoted_node_max(const pcmk_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
     return clone_data->promoted_node_max;
 }
 
 static GList *
 sorted_hash_table_values(GHashTable *table)
 {
     GList *retval = NULL;
     GHashTableIter iter;
     gpointer key, value;
 
     g_hash_table_iter_init(&iter, table);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
             retval = g_list_prepend(retval, (char *) value);
         }
     }
 
     retval = g_list_sort(retval, (GCompareFunc) strcmp);
     return retval;
 }
 
 static GList *
 nodes_with_status(GHashTable *table, const char *status)
 {
     GList *retval = NULL;
     GHashTableIter iter;
     gpointer key, value;
 
     g_hash_table_iter_init(&iter, table);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         if (!strcmp((char *) value, status)) {
             retval = g_list_prepend(retval, key);
         }
     }
 
     retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
     return retval;
 }
 
 static GString *
 node_list_to_str(const GList *list)
 {
     GString *retval = NULL;
 
     for (const GList *iter = list; iter != NULL; iter = iter->next) {
         pcmk__add_word(&retval, 1024, (const char *) iter->data);
     }
 
     return retval;
 }
 
 static void
 clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
              clone_variant_data_t *clone_data, const char *desc)
 {
     GString *attrs = NULL;
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         pcmk__add_separated_word(&attrs, 64, "promotable", ", ");
     }
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         pcmk__add_separated_word(&attrs, 64, "unique", ", ");
     }
 
     if (pe__resource_is_disabled(rsc)) {
         pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
     }
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) {
         pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
 
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
     }
 
     if (attrs != NULL) {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s",
                                  rsc->id,
                                  pcmk__xe_id(clone_data->xml_obj_child),
                                  (const char *) attrs->str, desc ? " (" : "",
                                  desc ? desc : "", desc ? ")" : "");
         g_string_free(attrs, TRUE);
     } else {
         PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s",
                                  rsc->id,
                                  pcmk__xe_id(clone_data->xml_obj_child),
                                  desc ? " (" : "", desc ? desc : "",
                                  desc ? ")" : "");
     }
 }
 
 void
 pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid,
                pcmk_scheduler_t *scheduler)
 {
     if (pcmk__is_clone(rsc)) {
         clone_variant_data_t *clone_data = rsc->variant_opaque;
 
         pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s "
                           "because %s resources such as %s can be used only as "
                           "anonymous clones", rsc->id, standard, rid);
 
         clone_data->clone_node_max = 1;
         clone_data->clone_max = QB_MIN(clone_data->clone_max,
                                        g_list_length(scheduler->nodes));
     }
 }
 
 pcmk_resource_t *
 find_clone_instance(const pcmk_resource_t *rsc, const char *sub_id)
 {
     char *child_id = NULL;
     pcmk_resource_t *child = NULL;
     const char *child_base = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     child_base = pcmk__xe_id(clone_data->xml_obj_child);
     child_id = crm_strdup_printf("%s:%s", child_base, sub_id);
     child = pe_find_resource(rsc->children, child_id);
 
     free(child_id);
     return child;
 }
 
 pcmk_resource_t *
 pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     gboolean as_orphan = FALSE;
     char *inc_num = NULL;
     char *inc_max = NULL;
     pcmk_resource_t *child_rsc = NULL;
     xmlNode *child_copy = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
 
     if (clone_data->total_clones >= clone_data->clone_max) {
         // If we've already used all available instances, this is an orphan
         as_orphan = TRUE;
     }
 
     // Allocate instance numbers in numerical order (starting at 0)
     inc_num = pcmk__itoa(clone_data->total_clones);
     inc_max = pcmk__itoa(clone_data->clone_max);
 
     child_copy = copy_xml(clone_data->xml_obj_child);
 
     crm_xml_add(child_copy, PCMK__META_CLONE, inc_num);
 
     if (pe__unpack_resource(child_copy, &child_rsc, rsc,
                             scheduler) != pcmk_rc_ok) {
         goto bail;
     }
 /*  child_rsc->globally_unique = rsc->globally_unique; */
 
     CRM_ASSERT(child_rsc);
     clone_data->total_clones += 1;
     pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s",
                     child_rsc->id);
     rsc->children = g_list_append(rsc->children, child_rsc);
     if (as_orphan) {
         pe__set_resource_flags_recursive(child_rsc, pcmk_rsc_removed);
     }
 
     pcmk__insert_meta(child_rsc, PCMK_META_CLONE_MAX, inc_max);
     pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
 
   bail:
     free(inc_num);
     free(inc_max);
 
     return child_rsc;
 }
 
 /*!
  * \internal
  * \brief Unpack a nonnegative integer value from a resource meta-attribute
  *
  * \param[in]  rsc              Resource with meta-attribute
  * \param[in]  meta_name        Name of meta-attribute to unpack
  * \param[in]  deprecated_name  If not NULL, try unpacking this
  *                              if \p meta_name is unset
  * \param[in]  default_value    Value to use if unset
  *
  * \return Integer parsed from resource's specified meta-attribute if a valid
  *         nonnegative integer, \p default_value if unset, or 0 if invalid
  */
 static int
 unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name,
                 const char *deprecated_name, int default_value)
 {
     int integer = default_value;
     const char *value = g_hash_table_lookup(rsc->meta, meta_name);
 
     if ((value == NULL) && (deprecated_name != NULL)) {
         value = g_hash_table_lookup(rsc->meta, deprecated_name);
     }
     if (value != NULL) {
         pcmk__scan_min_int(value, &integer, 0);
     }
     return integer;
 }
 
 gboolean
 clone_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     int lpc = 0;
     xmlNode *a_child = NULL;
     xmlNode *xml_obj = rsc->xml;
     clone_variant_data_t *clone_data = NULL;
 
     pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     clone_data = calloc(1, sizeof(clone_variant_data_t));
     rsc->variant_opaque = clone_data;
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
         // Use 1 as default but 0 for minimum and invalid
         // @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0
         clone_data->promoted_max =
             unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX,
                             PCMK__META_PROMOTED_MAX_LEGACY, 1);
 
         // Use 1 as default but 0 for minimum and invalid
         // @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0
         clone_data->promoted_node_max =
             unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX,
                             PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1);
     }
 
     // Implied by calloc()
     /* clone_data->xml_obj_child = NULL; */
 
     // Use 1 as default but 0 for minimum and invalid
     clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX,
                                                  NULL, 1);
 
     /* Use number of nodes (but always at least 1, which is handy for crm_verify
      * for a CIB without nodes) as default, but 0 for minimum and invalid
      */
     clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL,
                                             QB_MAX(1, g_list_length(scheduler->nodes)));
 
     if (crm_is_true(g_hash_table_lookup(rsc->meta, PCMK_META_ORDERED))) {
         clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
                                                "Clone", rsc->id,
                                                clone_data->flags,
                                                pcmk__clone_ordered,
                                                "pcmk__clone_ordered");
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)
         && (clone_data->clone_node_max > 1)) {
 
         pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s "
                          "because anonymous clones support only one instance "
                          "per node", clone_data->clone_node_max, rsc->id);
         clone_data->clone_node_max = 1;
     }
 
     pcmk__rsc_trace(rsc, "Options for %s", rsc->id);
     pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
     pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
     pcmk__rsc_trace(rsc, "\tClone is unique: %s",
                     pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
     pcmk__rsc_trace(rsc, "\tClone is promotable: %s",
                     pcmk__flag_text(rsc->flags, pcmk_rsc_promotable));
 
     // Clones may contain a single group or primitive
     for (a_child = pcmk__xe_first_child(xml_obj); a_child != NULL;
          a_child = pcmk__xe_next(a_child)) {
 
         if (pcmk__str_any_of((const char *) a_child->name,
                              PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) {
             clone_data->xml_obj_child = a_child;
             break;
         }
     }
 
     if (clone_data->xml_obj_child == NULL) {
         pcmk__config_err("%s has nothing to clone", rsc->id);
         return FALSE;
     }
 
     /*
      * Make clones ever so slightly sticky by default
      *
      * This helps ensure clone instances are not shuffled around the cluster
      * for no benefit in situations when pre-allocation is not appropriate
      */
     if (g_hash_table_lookup(rsc->meta, PCMK_META_RESOURCE_STICKINESS) == NULL) {
         pcmk__insert_meta(rsc, PCMK_META_RESOURCE_STICKINESS, "1");
     }
 
     /* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for
      * children to inherit when being unpacked, as well as in resource agents'
      * environment.
      */
     pcmk__insert_meta(rsc, PCMK_META_GLOBALLY_UNIQUE,
                       pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
 
     if (clone_data->clone_max <= 0) {
         /* Create one child instance so that unpack_find_resource() will hook up
          * any orphans up to the parent correctly.
          */
         if (pe__create_clone_child(rsc, scheduler) == NULL) {
             return FALSE;
         }
 
     } else {
         // Create a child instance for each available instance number
         for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
             if (pe__create_clone_child(rsc, scheduler) == NULL) {
                 return FALSE;
             }
         }
     }
 
     pcmk__rsc_trace(rsc, "Added %d children to resource %s...",
                     clone_data->clone_max, rsc->id);
     return TRUE;
 }
 
 gboolean
 clone_active(pcmk_resource_t * rsc, gboolean all)
 {
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
         gboolean child_active = child_rsc->fns->active(child_rsc, all);
 
         if (all == FALSE && child_active) {
             return TRUE;
         } else if (all && child_active == FALSE) {
             return FALSE;
         }
     }
 
     if (all) {
         return TRUE;
     } else {
         return FALSE;
     }
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 short_print(const char *list, const char *prefix, const char *type,
             const char *suffix, long options, void *print_data)
 {
     if(suffix == NULL) {
         suffix = "";
     }
 
     if (!pcmk__str_empty(list)) {
         if (options & pe_print_html) {
             status_print("<li>");
         }
         status_print("%s%s: [ %s ]%s", prefix, type, list, suffix);
 
         if (options & pe_print_html) {
             status_print("</li>\n");
 
         } else if (options & pe_print_suppres_nl) {
             /* nothing */
         } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) {
             status_print("\n");
         }
 
     }
 }
 
 static const char *
 configured_role_str(pcmk_resource_t * rsc)
 {
     const char *target_role = g_hash_table_lookup(rsc->meta,
                                                   PCMK_META_TARGET_ROLE);
 
     if ((target_role == NULL) && rsc->children && rsc->children->data) {
         pcmk_resource_t *instance = rsc->children->data; // Any instance will do
 
         target_role = g_hash_table_lookup(instance->meta,
                                           PCMK_META_TARGET_ROLE);
     }
     return target_role;
 }
 
 static enum rsc_role_e
 configured_role(pcmk_resource_t *rsc)
 {
     enum rsc_role_e role = pcmk_role_unknown;
     const char *target_role = configured_role_str(rsc);
 
     if (target_role != NULL) {
         role = pcmk_parse_role(target_role);
         if (role == pcmk_role_unknown) {
             pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
                              " for resource %s", rsc->id);
         }
     }
     return role;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 clone_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options,
                 void *print_data)
 {
     char *child_text = crm_strdup_printf("%s    ", pre_text);
     const char *target_role = configured_role_str(rsc);
     GList *gIter = rsc->children;
 
     status_print("%s<clone ", pre_text);
     status_print(PCMK_XA_ID "=\"%s\" ", rsc->id);
     status_print("multi_state=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_promotable));
     status_print("unique=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_unique));
     status_print("managed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_managed));
     status_print("failed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_failed));
     status_print("failure_ignored=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure));
     if (target_role) {
         status_print("target_role=\"%s\" ", target_role);
     }
     status_print(">\n");
 
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 
         child_rsc->fns->print(child_rsc, child_text, options, print_data);
     }
 
     status_print("%s</clone>\n", pre_text);
     free(child_text);
 }
 
 bool
 is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any)
 {
     GList *gIter;
     bool all = !any;
 
     if (pcmk_is_set(rsc->flags, flag)) {
         if(any) {
             return TRUE;
         }
     } else if(all) {
         return FALSE;
     }
 
     for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         if(is_set_recursive(gIter->data, flag, any)) {
             if(any) {
                 return TRUE;
             }
 
         } else if(all) {
             return FALSE;
         }
     }
 
     if(all) {
         return TRUE;
     }
     return FALSE;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 void
 clone_print(pcmk_resource_t *rsc, const char *pre_text, long options,
             void *print_data)
 {
     GString *list_text = NULL;
     char *child_text = NULL;
     GString *stopped_list = NULL;
 
     GList *promoted_list = NULL;
     GList *started_list = NULL;
     GList *gIter = rsc->children;
 
     clone_variant_data_t *clone_data = NULL;
     int active_instances = 0;
 
     if (pre_text == NULL) {
         pre_text = " ";
     }
 
     if (options & pe_print_xml) {
         clone_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     get_clone_variant_data(clone_data, rsc);
 
     child_text = crm_strdup_printf("%s    ", pre_text);
 
     status_print("%sClone Set: %s [%s]%s%s%s",
                  pcmk__s(pre_text, ""), rsc->id,
                  pcmk__xe_id(clone_data->xml_obj_child),
                  pcmk_is_set(rsc->flags, pcmk_rsc_promotable)? " (promotable)" : "",
                  pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "",
                  pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : " (unmanaged)");
 
     if (options & pe_print_html) {
         status_print("\n<ul>\n");
 
     } else if ((options & pe_print_log) == 0) {
         status_print("\n");
     }
 
     for (; gIter != NULL; gIter = gIter->next) {
         gboolean print_full = FALSE;
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 
         if (options & pe_print_clone_details) {
             print_full = TRUE;
         }
 
         if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
             // Print individual instance when unique (except stopped orphans)
             if (partially_active
                 || !pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
                 print_full = TRUE;
             }
 
         // Everything else in this block is for anonymous clones
 
         } else if (pcmk_is_set(options, pe_print_pending)
                    && (child_rsc->pending_task != NULL)
                    && strcmp(child_rsc->pending_task, "probe")) {
             // Print individual instance when non-probe action is pending
             print_full = TRUE;
 
         } else if (partially_active == FALSE) {
             // List stopped instances when requested (except orphans)
             if (!pcmk_is_set(child_rsc->flags, pcmk_rsc_removed)
                 && !pcmk_is_set(options, pe_print_clone_active)) {
 
                 pcmk__add_word(&stopped_list, 1024, child_rsc->id);
             }
 
         } else if (is_set_recursive(child_rsc, pcmk_rsc_removed, TRUE)
                    || !is_set_recursive(child_rsc, pcmk_rsc_managed, FALSE)
                    || is_set_recursive(child_rsc, pcmk_rsc_failed, TRUE)) {
 
             // Print individual instance when active orphaned/unmanaged/failed
             print_full = TRUE;
 
         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
             // Instance of fully active anonymous clone
 
             pcmk_node_t *location = NULL;
 
             location = child_rsc->fns->location(child_rsc, NULL, TRUE);
             if (location) {
                 // Instance is active on a single node
 
                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 
                 if (location->details->online == FALSE && location->details->unclean) {
                     print_full = TRUE;
 
                 } else if (a_role > pcmk_role_unpromoted) {
                     promoted_list = g_list_append(promoted_list, location);
 
                 } else {
                     started_list = g_list_append(started_list, location);
                 }
 
             } else {
                 /* uncolocated group - bleh */
                 print_full = TRUE;
             }
 
         } else {
             // Instance of partially active anonymous clone
             print_full = TRUE;
         }
 
         if (print_full) {
             if (options & pe_print_html) {
                 status_print("<li>\n");
             }
             child_rsc->fns->print(child_rsc, child_text, options, print_data);
             if (options & pe_print_html) {
                 status_print("</li>\n");
             }
         }
     }
 
     /* Promoted */
     promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
     for (gIter = promoted_list; gIter; gIter = gIter->next) {
         pcmk_node_t *host = gIter->data;
 
         pcmk__add_word(&list_text, 1024, host->details->uname);
         active_instances++;
     }
 
     if (list_text != NULL) {
         short_print((const char *) list_text->str, child_text,
                     PROMOTED_INSTANCES, NULL, options, print_data);
         g_string_truncate(list_text, 0);
     }
     g_list_free(promoted_list);
 
     /* Started/Unpromoted */
     started_list = g_list_sort(started_list, pe__cmp_node_name);
     for (gIter = started_list; gIter; gIter = gIter->next) {
         pcmk_node_t *host = gIter->data;
 
         pcmk__add_word(&list_text, 1024, host->details->uname);
         active_instances++;
     }
 
     if (list_text != NULL) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
             enum rsc_role_e role = configured_role(rsc);
 
             if (role == pcmk_role_unpromoted) {
                 short_print((const char *) list_text->str, child_text,
                             UNPROMOTED_INSTANCES " (" PCMK_META_TARGET_ROLE ")",
                             NULL, options, print_data);
             } else {
                 short_print((const char *) list_text->str, child_text,
                             UNPROMOTED_INSTANCES, NULL, options, print_data);
             }
 
         } else {
             short_print((const char *) list_text->str, child_text, "Started",
                         NULL, options, print_data);
         }
     }
 
     g_list_free(started_list);
 
     if (!pcmk_is_set(options, pe_print_clone_active)) {
         const char *state = "Stopped";
         enum rsc_role_e role = configured_role(rsc);
 
         if (role == pcmk_role_stopped) {
             state = "Stopped (disabled)";
         }
 
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)
             && (clone_data->clone_max > active_instances)) {
 
             GList *nIter;
             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 
             /* Custom stopped list for non-unique clones */
             if (stopped_list != NULL) {
                 g_string_truncate(stopped_list, 0);
             }
 
             if (list == NULL) {
                 /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
                  * calculated allowed_nodes yet. If we've not probed for them
                  * yet, the Stopped list will be empty.
                  */
                 list = g_hash_table_get_values(rsc->known_on);
             }
 
             list = g_list_sort(list, pe__cmp_node_name);
             for (nIter = list; nIter != NULL; nIter = nIter->next) {
                 pcmk_node_t *node = (pcmk_node_t *) nIter->data;
 
                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL) {
                     pcmk__add_word(&stopped_list, 1024, node->details->uname);
                 }
             }
             g_list_free(list);
         }
 
         if (stopped_list != NULL) {
             short_print((const char *) stopped_list->str, child_text, state,
                         NULL, options, print_data);
         }
     }
 
     if (options & pe_print_html) {
         status_print("</ul>\n");
     }
 
     if (list_text != NULL) {
         g_string_free(list_text, TRUE);
     }
 
     if (stopped_list != NULL) {
         g_string_free(stopped_list, TRUE);
     }
     free(child_text);
 }
 
 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__clone_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     GList *gIter = rsc->children;
     GList *all = NULL;
     int rc = pcmk_rc_no_output;
     gboolean printed_header = FALSE;
     gboolean print_everything = TRUE;
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     all = g_list_prepend(all, (gpointer) "*");
 
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 
         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
             continue;
         }
 
         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
             continue;
         }
 
         if (!printed_header) {
             const char *multi_state = pcmk__flag_text(rsc->flags,
                                                       pcmk_rsc_promotable);
             const char *unique = pcmk__flag_text(rsc->flags, pcmk_rsc_unique);
             const char *maintenance = pcmk__flag_text(rsc->flags,
                                                       pcmk_rsc_maintenance);
             const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed);
             const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
             const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed);
             const char *ignored = pcmk__flag_text(rsc->flags,
                                                   pcmk_rsc_ignore_failure);
             const char *target_role = configured_role_str(rsc);
             const char *desc = pe__resource_description(rsc, show_opts);
 
             printed_header = TRUE;
 
             rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE, 10,
                                           PCMK_XA_ID, rsc->id,
                                           PCMK_XA_MULTI_STATE, multi_state,
                                           PCMK_XA_UNIQUE, unique,
                                           PCMK_XA_MAINTENANCE, maintenance,
                                           PCMK_XA_MANAGED, managed,
                                           PCMK_XA_DISABLED, disabled,
                                           PCMK_XA_FAILED, failed,
                                           PCMK_XA_FAILURE_IGNORED, ignored,
                                           PCMK_XA_TARGET_ROLE, target_role,
                                           PCMK_XA_DESCRIPTION, desc);
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
 
         out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
                      child_rsc, only_node, all);
     }
 
     if (printed_header) {
         pcmk__output_xml_pop_parent(out);
     }
 
     g_list_free(all);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__clone_default(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     GHashTable *stopped = NULL;
 
     GString *list_text = NULL;
 
     GList *promoted_list = NULL;
     GList *started_list = NULL;
     GList *gIter = rsc->children;
 
     const char *desc = NULL;
 
     clone_variant_data_t *clone_data = NULL;
     int active_instances = 0;
     int rc = pcmk_rc_no_output;
     gboolean print_everything = TRUE;
 
     desc = pe__resource_description(rsc, show_opts);
 
     get_clone_variant_data(clone_data, rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return rc;
     }
 
     print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
                        (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches));
 
     for (; gIter != NULL; gIter = gIter->next) {
         gboolean print_full = FALSE;
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
         gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE);
 
         if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
             continue;
         }
 
         if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) {
             continue;
         }
 
         if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
             print_full = TRUE;
         }
 
         if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
             // Print individual instance when unique (except stopped orphans)
             if (partially_active
                 || !pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
                 print_full = TRUE;
             }
 
         // Everything else in this block is for anonymous clones
 
         } else if (pcmk_is_set(show_opts, pcmk_show_pending)
                    && (child_rsc->pending_task != NULL)
                    && strcmp(child_rsc->pending_task, "probe")) {
             // Print individual instance when non-probe action is pending
             print_full = TRUE;
 
         } else if (partially_active == FALSE) {
             // List stopped instances when requested (except orphans)
             if (!pcmk_is_set(child_rsc->flags, pcmk_rsc_removed)
                 && !pcmk_is_set(show_opts, pcmk_show_clone_detail)
                 && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
                 if (stopped == NULL) {
                     stopped = pcmk__strkey_table(free, free);
                 }
                 pcmk__insert_dup(stopped, child_rsc->id, "Stopped");
             }
 
         } else if (is_set_recursive(child_rsc, pcmk_rsc_removed, TRUE)
                    || !is_set_recursive(child_rsc, pcmk_rsc_managed, FALSE)
                    || is_set_recursive(child_rsc, pcmk_rsc_failed, TRUE)) {
 
             // Print individual instance when active orphaned/unmanaged/failed
             print_full = TRUE;
 
         } else if (child_rsc->fns->active(child_rsc, TRUE)) {
             // Instance of fully active anonymous clone
 
             pcmk_node_t *location = NULL;
 
             location = child_rsc->fns->location(child_rsc, NULL, TRUE);
             if (location) {
                 // Instance is active on a single node
 
                 enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE);
 
                 if (location->details->online == FALSE && location->details->unclean) {
                     print_full = TRUE;
 
                 } else if (a_role > pcmk_role_unpromoted) {
                     promoted_list = g_list_append(promoted_list, location);
 
                 } else {
                     started_list = g_list_append(started_list, location);
                 }
 
             } else {
                 /* uncolocated group - bleh */
                 print_full = TRUE;
             }
 
         } else {
             // Instance of partially active anonymous clone
             print_full = TRUE;
         }
 
         if (print_full) {
             GList *all = NULL;
 
             clone_header(out, &rc, rsc, clone_data, desc);
 
             /* Print every resource that's a child of this clone. */
             all = g_list_prepend(all, (gpointer) "*");
             out->message(out, crm_map_element_name(child_rsc->xml), show_opts,
                          child_rsc, only_node, all);
             g_list_free(all);
         }
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) {
         PCMK__OUTPUT_LIST_FOOTER(out, rc);
         return pcmk_rc_ok;
     }
 
     /* Promoted */
     promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
     for (gIter = promoted_list; gIter; gIter = gIter->next) {
         pcmk_node_t *host = gIter->data;
 
         if (!pcmk__str_in_list(host->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         pcmk__add_word(&list_text, 1024, host->details->uname);
         active_instances++;
     }
     g_list_free(promoted_list);
 
     if ((list_text != NULL) && (list_text->len > 0)) {
         clone_header(out, &rc, rsc, clone_data, desc);
 
         out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]",
                        (const char *) list_text->str);
         g_string_truncate(list_text, 0);
     }
 
     /* Started/Unpromoted */
     started_list = g_list_sort(started_list, pe__cmp_node_name);
     for (gIter = started_list; gIter; gIter = gIter->next) {
         pcmk_node_t *host = gIter->data;
 
         if (!pcmk__str_in_list(host->details->uname, only_node,
                                pcmk__str_star_matches|pcmk__str_casei)) {
             continue;
         }
 
         pcmk__add_word(&list_text, 1024, host->details->uname);
         active_instances++;
     }
     g_list_free(started_list);
 
     if ((list_text != NULL) && (list_text->len > 0)) {
         clone_header(out, &rc, rsc, clone_data, desc);
 
         if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) {
             enum rsc_role_e role = configured_role(rsc);
 
             if (role == pcmk_role_unpromoted) {
                 out->list_item(out, NULL,
                                UNPROMOTED_INSTANCES
                                " (" PCMK_META_TARGET_ROLE "): [ %s ]",
                                (const char *) list_text->str);
             } else {
                 out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]",
                                (const char *) list_text->str);
             }
 
         } else {
             out->list_item(out, NULL, "Started: [ %s ]",
                            (const char *) list_text->str);
         }
     }
 
     if (list_text != NULL) {
         g_string_free(list_text, TRUE);
     }
 
     if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)
             && (clone_data->clone_max > active_instances)) {
 
             GList *nIter;
             GList *list = g_hash_table_get_values(rsc->allowed_nodes);
 
             /* Custom stopped table for non-unique clones */
             if (stopped != NULL) {
                 g_hash_table_destroy(stopped);
                 stopped = NULL;
             }
 
             if (list == NULL) {
                 /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
                  * calculated allowed_nodes yet. If we've not probed for them
                  * yet, the Stopped list will be empty.
                  */
                 list = g_hash_table_get_values(rsc->known_on);
             }
 
             list = g_list_sort(list, pe__cmp_node_name);
             for (nIter = list; nIter != NULL; nIter = nIter->next) {
                 pcmk_node_t *node = (pcmk_node_t *) nIter->data;
 
                 if (pe_find_node(rsc->running_on, node->details->uname) == NULL &&
                     pcmk__str_in_list(node->details->uname, only_node,
                                       pcmk__str_star_matches|pcmk__str_casei)) {
                     xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname);
                     const char *state = "Stopped";
 
                     if (configured_role(rsc) == pcmk_role_stopped) {
                         state = "Stopped (disabled)";
                     }
 
                     if (stopped == NULL) {
                         stopped = pcmk__strkey_table(free, free);
                     }
                     if (probe_op != NULL) {
                         int rc;
 
                         pcmk__scan_min_int(crm_element_value(probe_op,
                                                              PCMK__XA_RC_CODE),
                                            &rc, 0);
                         g_hash_table_insert(stopped, strdup(node->details->uname),
                                             crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc)));
                     } else {
                         pcmk__insert_dup(stopped, node->details->uname, state);
                     }
                 }
             }
             g_list_free(list);
         }
 
         if (stopped != NULL) {
             GList *list = sorted_hash_table_values(stopped);
 
             clone_header(out, &rc, rsc, clone_data, desc);
 
             for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
                 const char *status = status_iter->data;
                 GList *nodes = nodes_with_status(stopped, status);
                 GString *nodes_str = node_list_to_str(nodes);
 
                 if (nodes_str != NULL) {
                     if (nodes_str->len > 0) {
                         out->list_item(out, NULL, "%s: [ %s ]", status,
                                        (const char *) nodes_str->str);
                     }
                     g_string_free(nodes_str, TRUE);
                 }
 
                 g_list_free(nodes);
             }
 
             g_list_free(list);
             g_hash_table_destroy(stopped);
 
         /* If there are no instances of this clone (perhaps because there are no
          * nodes configured), simply output the clone header by itself.  This can
          * come up in PCS testing.
          */
         } else if (active_instances == 0) {
             clone_header(out, &rc, rsc, clone_data, desc);
             PCMK__OUTPUT_LIST_FOOTER(out, rc);
             return rc;
         }
     }
 
     PCMK__OUTPUT_LIST_FOOTER(out, rc);
     return rc;
 }
 
 void
 clone_free(pcmk_resource_t * rsc)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
 
     pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
 
     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
 
         CRM_ASSERT(child_rsc);
         pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
         free_xml(child_rsc->xml);
         child_rsc->xml = NULL;
         /* There could be a saved unexpanded xml */
         free_xml(child_rsc->orig_xml);
         child_rsc->orig_xml = NULL;
         child_rsc->fns->free(child_rsc);
     }
 
     g_list_free(rsc->children);
 
     if (clone_data) {
         CRM_ASSERT(clone_data->demote_notify == NULL);
         CRM_ASSERT(clone_data->stop_notify == NULL);
         CRM_ASSERT(clone_data->start_notify == NULL);
         CRM_ASSERT(clone_data->promote_notify == NULL);
     }
 
     common_free(rsc);
 }
 
 enum rsc_role_e
 clone_resource_state(const pcmk_resource_t * rsc, gboolean current)
 {
     enum rsc_role_e clone_role = pcmk_role_unknown;
     GList *gIter = rsc->children;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
         enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, current);
 
         if (a_role > clone_role) {
             clone_role = a_role;
         }
     }
 
     pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role));
     return clone_role;
 }
 
 /*!
  * \internal
  * \brief Check whether a clone has an instance for every node
  *
  * \param[in] rsc        Clone to check
  * \param[in] scheduler  Scheduler data
  */
 bool
 pe__is_universal_clone(const pcmk_resource_t *rsc,
                        const pcmk_scheduler_t *scheduler)
 {
     if (pcmk__is_clone(rsc)) {
         clone_variant_data_t *clone_data = rsc->variant_opaque;
 
         if (clone_data->clone_max == g_list_length(scheduler->nodes)) {
             return TRUE;
         }
     }
     return FALSE;
 }
 
 gboolean
 pe__clone_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
                       gboolean check_parent)
 {
     gboolean passes = FALSE;
     clone_variant_data_t *clone_data = NULL;
 
     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) {
         passes = TRUE;
     } else {
         get_clone_variant_data(clone_data, rsc);
         passes = pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child),
                                    only_rsc, pcmk__str_star_matches);
 
         if (!passes) {
             for (const GList *iter = rsc->children;
                  iter != NULL; iter = iter->next) {
 
                 const pcmk_resource_t *child_rsc = NULL;
 
                 child_rsc = (const pcmk_resource_t *) iter->data;
                 if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) {
                     passes = TRUE;
                     break;
                 }
             }
         }
     }
     return !passes;
 }
 
 const char *
 pe__clone_child_id(const pcmk_resource_t *rsc)
 {
     clone_variant_data_t *clone_data = NULL;
     get_clone_variant_data(clone_data, rsc);
     return pcmk__xe_id(clone_data->xml_obj_child);
 }
 
 /*!
  * \internal
  * \brief Check whether a clone is ordered
  *
  * \param[in] clone  Clone resource to check
  *
  * \return true if clone is ordered, otherwise false
  */
 bool
 pe__clone_is_ordered(const pcmk_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
     return pcmk_is_set(clone_data->flags, pcmk__clone_ordered);
 }
 
 /*!
  * \internal
  * \brief Set a clone flag
  *
  * \param[in,out] clone  Clone resource to set flag for
  * \param[in]     flag   Clone flag to set
  *
  * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
  *         already set or pcmk_rc_already if it was)
  */
 int
 pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
     if (pcmk_is_set(clone_data->flags, flag)) {
         return pcmk_rc_already;
     }
     clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
                                            "Clone", clone->id,
                                            clone_data->flags, flag, "flag");
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Check whether a clone flag is set
  *
  * \param[in] group  Clone resource to check
  * \param[in] flags  Flag or flags to check
  *
  * \return \c true if all \p flags are set for \p clone, otherwise \c false
  */
 bool
 pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
     CRM_ASSERT(clone_data != NULL);
 
     return pcmk_all_flags_set(clone_data->flags, flags);
 }
 
 /*!
  * \internal
  * \brief Create pseudo-actions needed for promotable clones
  *
  * \param[in,out] clone          Promotable clone to create actions for
  * \param[in]     any_promoting  Whether any instances will be promoted
  * \param[in]     any_demoting   Whether any instance will be demoted
  */
 void
 pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting,
                                  bool any_demoting)
 {
     pcmk_action_t *action = NULL;
     pcmk_action_t *action_complete = NULL;
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
 
     // Create a "promote" action for the clone itself
     action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE,
                                        !any_promoting, true);
 
     // Create a "promoted" action for when all promotions are done
     action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED,
                                                 !any_promoting, true);
-    action_complete->priority = INFINITY;
+    action_complete->priority = PCMK_SCORE_INFINITY;
 
     // Create notification pseudo-actions for promotion
     if (clone_data->promote_notify == NULL) {
         clone_data->promote_notify = pe__action_notif_pseudo_ops(clone,
                                                                  PCMK_ACTION_PROMOTE,
                                                                  action,
                                                                  action_complete);
     }
 
     // Create a "demote" action for the clone itself
     action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE,
                                        !any_demoting, true);
 
     // Create a "demoted" action for when all demotions are done
     action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED,
                                                 !any_demoting, true);
-    action_complete->priority = INFINITY;
+    action_complete->priority = PCMK_SCORE_INFINITY;
 
     // Create notification pseudo-actions for demotion
     if (clone_data->demote_notify == NULL) {
         clone_data->demote_notify = pe__action_notif_pseudo_ops(clone,
                                                                 PCMK_ACTION_DEMOTE,
                                                                 action,
                                                                 action_complete);
 
         if (clone_data->promote_notify != NULL) {
             order_actions(clone_data->stop_notify->post_done,
                           clone_data->promote_notify->pre, pcmk__ar_ordered);
             order_actions(clone_data->start_notify->post_done,
                           clone_data->promote_notify->pre, pcmk__ar_ordered);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->promote_notify->pre, pcmk__ar_ordered);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->start_notify->pre, pcmk__ar_ordered);
             order_actions(clone_data->demote_notify->post_done,
                           clone_data->stop_notify->pre, pcmk__ar_ordered);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Create all notification data and actions for a clone
  *
  * \param[in,out] clone  Clone to create notifications for
  */
 void
 pe__create_clone_notifications(pcmk_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
 
     pe__create_action_notifications(clone, clone_data->start_notify);
     pe__create_action_notifications(clone, clone_data->stop_notify);
     pe__create_action_notifications(clone, clone_data->promote_notify);
     pe__create_action_notifications(clone, clone_data->demote_notify);
 }
 
 /*!
  * \internal
  * \brief Free all notification data for a clone
  *
  * \param[in,out] clone  Clone to free notification data for
  */
 void
 pe__free_clone_notification_data(pcmk_resource_t *clone)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
 
     pe__free_action_notification_data(clone_data->demote_notify);
     clone_data->demote_notify = NULL;
 
     pe__free_action_notification_data(clone_data->stop_notify);
     clone_data->stop_notify = NULL;
 
     pe__free_action_notification_data(clone_data->start_notify);
     clone_data->start_notify = NULL;
 
     pe__free_action_notification_data(clone_data->promote_notify);
     clone_data->promote_notify = NULL;
 }
 
 /*!
  * \internal
  * \brief Create pseudo-actions for clone start/stop notifications
  *
  * \param[in,out] clone    Clone to create pseudo-actions for
  * \param[in,out] start    Start action for \p clone
  * \param[in,out] stop     Stop action for \p clone
  * \param[in,out] started  Started action for \p clone
  * \param[in,out] stopped  Stopped action for \p clone
  */
 void
 pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
                                   pcmk_action_t *start, pcmk_action_t *started,
                                   pcmk_action_t *stop, pcmk_action_t *stopped)
 {
     clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, clone);
 
     if (clone_data->start_notify == NULL) {
         clone_data->start_notify = pe__action_notif_pseudo_ops(clone,
                                                                PCMK_ACTION_START,
                                                                start, started);
     }
 
     if (clone_data->stop_notify == NULL) {
         clone_data->stop_notify = pe__action_notif_pseudo_ops(clone,
                                                               PCMK_ACTION_STOP,
                                                               stop, stopped);
         if ((clone_data->start_notify != NULL)
             && (clone_data->stop_notify != NULL)) {
             order_actions(clone_data->stop_notify->post_done,
                           clone_data->start_notify->pre, pcmk__ar_ordered);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Get maximum clone resource instances per node
  *
  * \param[in] rsc  Clone resource to check
  *
  * \return Maximum number of \p rsc instances that can be active on one node
  */
 unsigned int
 pe__clone_max_per_node(const pcmk_resource_t *rsc)
 {
     const clone_variant_data_t *clone_data = NULL;
 
     get_clone_variant_data(clone_data, rsc);
     return clone_data->clone_node_max;
 }
diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c
index 57401bd619..7094e94550 100644
--- a/lib/pengine/complex.c
+++ b/lib/pengine/complex.c
@@ -1,1223 +1,1223 @@
 /*
  * 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 <crm_internal.h>
 
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include <crm/common/scheduler_internal.h>
 
 #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);
 
 pcmk_rsc_methods_t resource_class_functions[] = {
     {
          native_unpack,
          native_find_rsc,
          native_parameter,
          native_print,
          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_print,
          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_print,
          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__print_bundle,
          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 pe_obj_types
 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_PROMOTABLE_LEGACY,
                             pcmk__str_casei)) {
         // @COMPAT deprecated since 2.0.0
         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(table != NULL, return);
     if ((key != NULL)
         && !pcmk__str_eq((const char *) value, "#default",
                          pcmk__str_casei|pcmk__str_null_matches)
         && (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->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->xml, PCMK_XE_META_ATTRIBUTES, rule_data,
                                    parent_orig_meta, NULL, FALSE, scheduler);
         p = p->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 ;
 
 }
 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->xml, PCMK_XA_CLASS),
         .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER),
         .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE)
     };
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = &rsc_rule_data,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->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->xml, PCMK_XE_META_ATTRIBUTES, &rule_data,
                                meta_hash, NULL, FALSE, 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->parent != NULL) {
         expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, scheduler);
     }
 
     /* check the defaults */
     pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, PCMK_XE_META_ATTRIBUTES,
                                &rule_data, meta_hash, NULL, FALSE, 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->parent) {
         g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash);
     }
 }
 
 void
 get_rsc_attributes(GHashTable *meta_hash, 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 = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     if (node) {
         rule_data.node_hash = node->details->attrs;
     }
 
     pe__unpack_dataset_nvpairs(rsc->xml, PCMK_XE_INSTANCE_ATTRIBUTES,
                                &rule_data, meta_hash, NULL, FALSE, scheduler);
 
     /* set anything else based on the parent */
     if (rsc->parent != NULL) {
         get_rsc_attributes(meta_hash, rsc->parent, node, scheduler);
 
     } else {
         /* and finally check the defaults */
         pe__unpack_dataset_nvpairs(scheduler->rsc_defaults,
                                    PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data,
                                    meta_hash, NULL, FALSE, 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_match(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 = copy_xml(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 = find_xml_node(new_xml, PCMK_XE_OPERATIONS, FALSE);
 
     for (child_xml = pcmk__xe_first_child(xml_obj); child_xml != NULL;
          child_xml = pcmk__xe_next(child_xml)) {
         xmlNode *new_child = NULL;
 
         new_child = add_node_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); 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); op != NULL;
              op = pcmk__xe_next(op)) {
 
             char *key = template_op_key(op);
 
             if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
                 add_node_copy(rsc_ops, op);
             }
 
             free(key);
         }
 
         if (rsc_ops_hash) {
             g_hash_table_destroy(rsc_ops_hash);
         }
 
         free_xml(template_ops);
     }
 
     /*free_xml(*expanded_xml); */
     *expanded_xml = new_xml;
 
 #if 0 /* Disable multi-level templates for now */
     if (!unpack_template(new_xml, expanded_xml, scheduler)) {
        free_xml(*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;
     }
 
     if (add_tag_ref(scheduler->template_rsc_sets, template_ref, id) == FALSE) {
         return FALSE;
     }
 
     return TRUE;
 }
 
 static bool
 detect_promotable(pcmk_resource_t *rsc)
 {
     const char *promotable = g_hash_table_lookup(rsc->meta,
                                                  PCMK_META_PROMOTABLE);
 
     if (crm_is_true(promotable)) {
         return TRUE;
     }
 
     // @COMPAT deprecated since 2.0.0
     if (pcmk__xe_is(rsc->xml, PCMK__XE_PROMOTABLE_LEGACY)) {
         /* @TODO in some future version, pcmk__warn_once() here,
          *       then drop support in even later version
          */
         pcmk__insert_dup(rsc->meta, PCMK_META_PROMOTABLE, PCMK_VALUE_TRUE);
         return TRUE;
     }
     return FALSE;
 }
 
 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->details->uname != NULL)) {
         node_name = node->details->uname;
     }
 
     // Find the parameter table for given node
     if (rsc->parameter_cache == NULL) {
         rsc->parameter_cache = pcmk__strikey_table(free, free_params_table);
     } else {
         params_on_node = g_hash_table_lookup(rsc->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->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)
 {
     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(rsc->cluster->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(rsc->cluster->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 ((rsc->variant == pcmk_rsc_variant_primitive)
                    && xml_contains_remote_node(rsc->xml)) {
             value = PCMK_VALUE_QUORUM;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_enable_unfencing)) {
             value = PCMK_VALUE_UNFENCING;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_fencing_enabled)) {
             value = PCMK_VALUE_FENCING;
 
         } else if (rsc->cluster->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)" : ""));
 }
 
 #ifndef PCMK__COMPAT_2_0
 static void
 warn_about_deprecated_classes(pcmk_resource_t *rsc)
 {
     const char *std = crm_element_value(rsc->xml, PCMK_XA_CLASS);
 
     if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_none)) {
         pcmk__warn_once(pcmk__wo_upstart,
                         "Support for Upstart resources (such as %s) is "
                         "deprecated and will be removed in a future release",
                         rsc->id);
 
     } else if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_none)) {
         pcmk__warn_once(pcmk__wo_nagios,
                         "Support for Nagios resources (such as %s) is "
                         "deprecated and will be removed in a future release",
                         rsc->id);
     }
 }
 #endif
 
 /*!
  * \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;
 
     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->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("Unable to allocate memory for resource '%s'", id);
         return ENOMEM;
     }
     (*rsc)->cluster = scheduler;
 
     if (expanded_xml) {
         crm_log_xml_trace(expanded_xml, "[expanded XML]");
         (*rsc)->xml = expanded_xml;
         (*rsc)->orig_xml = xml_obj;
 
     } else {
         (*rsc)->xml = xml_obj;
         (*rsc)->orig_xml = NULL;
     }
 
     /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
 
     (*rsc)->parent = parent;
 
     ops = find_xml_node((*rsc)->xml, PCMK_XE_OPERATIONS, FALSE);
     (*rsc)->ops_xml = expand_idref(ops, scheduler->input);
 
     (*rsc)->variant = get_resource_type((const char *) (*rsc)->xml->name);
     if ((*rsc)->variant == pcmk_rsc_variant_unknown) {
         pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
                          id, (*rsc)->xml->name);
         common_free(*rsc);
         *rsc = NULL;
         return pcmk_rc_unpack_error;
     }
 
 #ifndef PCMK__COMPAT_2_0
     warn_about_deprecated_classes(*rsc);
 #endif
 
     (*rsc)->meta = pcmk__strkey_table(free, free);
     (*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free);
     (*rsc)->known_on = pcmk__strkey_table(NULL, free);
 
     value = crm_element_value((*rsc)->xml, PCMK__META_CLONE);
     if (value) {
         (*rsc)->id = crm_strdup_printf("%s:%s", id, value);
         pcmk__insert_meta(*rsc, PCMK__META_CLONE, value);
 
     } else {
         (*rsc)->id = strdup(id);
     }
 
     (*rsc)->fns = &resource_class_functions[(*rsc)->variant];
 
     get_meta_attributes((*rsc)->meta, *rsc, NULL, scheduler);
     (*rsc)->parameters = pe_rsc_params(*rsc, NULL, scheduler); // \deprecated
 
     (*rsc)->flags = 0;
     pcmk__set_rsc_flags(*rsc, pcmk_rsc_runnable|pcmk_rsc_unassigned);
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed);
     }
 
     (*rsc)->rsc_cons = NULL;
     (*rsc)->rsc_tickets = NULL;
     (*rsc)->actions = NULL;
     (*rsc)->role = pcmk_role_stopped;
     (*rsc)->next_role = pcmk_role_unknown;
 
     (*rsc)->recovery_type = pcmk_multiply_active_restart;
     (*rsc)->stickiness = 0;
-    (*rsc)->migration_threshold = INFINITY;
+    (*rsc)->migration_threshold = PCMK_SCORE_INFINITY;
     (*rsc)->failure_timeout = 0;
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_PRIORITY);
     (*rsc)->priority = char2score(value);
 
     value = g_hash_table_lookup((*rsc)->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)->meta, PCMK_META_NOTIFY);
     if (crm_is_true(value)) {
         pcmk__set_rsc_flags(*rsc, pcmk_rsc_notify);
     }
 
     if (xml_contains_remote_node((*rsc)->xml)) {
         (*rsc)->is_remote_node = TRUE;
         if (g_hash_table_lookup((*rsc)->meta, PCMK__META_CONTAINER)) {
             guest_node = true;
         } else {
             remote_node = true;
         }
     }
 
     value = g_hash_table_lookup((*rsc)->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)->meta, PCMK_META_IS_MANAGED);
     if ((value != NULL)
         && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
 
         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)->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))) {
         value = g_hash_table_lookup((*rsc)->meta, PCMK_META_GLOBALLY_UNIQUE);
         if (crm_is_true(value)) {
             pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique);
         }
         if (detect_promotable(*rsc)) {
             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)->meta, PCMK__META_RESTART_TYPE);
     if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
         (*rsc)->restart_type = pe_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)->restart_type = pe_restart_ignore;
         pcmk__rsc_trace(*rsc, "%s dependency restart handling: ignore",
                         (*rsc)->id);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MULTIPLE_ACTIVE);
     if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) {
         (*rsc)->recovery_type = 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)->recovery_type = pcmk_multiply_active_block;
         pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block",
                         (*rsc)->id);
 
     } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) {
         (*rsc)->recovery_type = pcmk_multiply_active_unexpected;
         pcmk__rsc_trace(*rsc,
                         "%s multiple running resource recovery: "
                         "stop unexpected instances",
                         (*rsc)->id);
 
     } else { // "stop_start"
         if (!pcmk__str_eq(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 \"stop_start\"", value);
         }
         (*rsc)->recovery_type = pcmk_multiply_active_restart;
         pcmk__rsc_trace(*rsc,
                         "%s multiple running resource recovery: stop/start",
                         (*rsc)->id);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_RESOURCE_STICKINESS);
     if ((value != NULL)
         && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
 
         (*rsc)->stickiness = char2score(value);
     }
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MIGRATION_THRESHOLD);
     if ((value != NULL)
         && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
 
         (*rsc)->migration_threshold = char2score(value);
         if ((*rsc)->migration_threshold < 0) {
             /* @TODO We use 1 here to preserve previous behavior, but this
              * should probably use the default (INFINITY) or 0 (to disable)
              * instead.
              */
             pcmk__warn_once(pcmk__wo_neg_threshold,
                             PCMK_META_MIGRATION_THRESHOLD
                             " must be non-negative, using 1 instead");
             (*rsc)->migration_threshold = 1;
         }
     }
 
     if (pcmk__str_eq(crm_element_value((*rsc)->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)->meta, PCMK_META_REQUIRES);
     unpack_requires(*rsc, value, false);
 
     value = g_hash_table_lookup((*rsc)->meta, PCMK_META_FAILURE_TIMEOUT);
     if (value != NULL) {
         guint interval_ms = 0U;
 
         // Stored as seconds
         pcmk_parse_interval_spec(value, &interval_ms);
         (*rsc)->failure_timeout = (int) (interval_ms / 1000);
     }
 
     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)->remote_reconnect_ms));
 
             /* We want to override any default failure_timeout in use when remote
              * PCMK_REMOTE_RA_RECONNECT_INTERVAL is in use.
              */
             (*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000;
         }
     }
 
     get_target_role(*rsc, &((*rsc)->next_role));
     pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id,
                     ((*rsc)->next_role == pcmk_role_unknown)?
                         "default" : pcmk_role_text((*rsc)->next_role));
 
     if ((*rsc)->fns->unpack(*rsc, scheduler) == FALSE) {
         (*rsc)->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");
 
     (*rsc)->utilization = pcmk__strkey_table(free, free);
 
     pe__unpack_dataset_nvpairs((*rsc)->xml, PCMK_XE_UTILIZATION, &rule_data,
                                (*rsc)->utilization, NULL, FALSE, scheduler);
 
     if (expanded_xml) {
         if (add_template_rsc(xml_obj, scheduler) == FALSE) {
             (*rsc)->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->parent != NULL) {
         if (parent->parent == rsc) {
             return TRUE;
         }
         parent = parent->parent;
     }
     return FALSE;
 }
 
 pcmk_resource_t *
 uber_parent(pcmk_resource_t *rsc)
 {
     pcmk_resource_t *parent = rsc;
 
     if (parent == NULL) {
         return NULL;
     }
     while ((parent->parent != NULL)
            && (parent->parent->variant != pcmk_rsc_variant_bundle)) {
         parent = parent->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->parent != NULL) {
         if (!include_bundle
             && (parent->parent->variant == pcmk_rsc_variant_bundle)) {
             break;
         }
         parent = parent->parent;
     }
     return parent;
 }
 
 void
 common_free(pcmk_resource_t * rsc)
 {
     if (rsc == NULL) {
         return;
     }
 
     pcmk__rsc_trace(rsc, "Freeing %s %d", rsc->id, rsc->variant);
 
     g_list_free(rsc->rsc_cons);
     g_list_free(rsc->rsc_cons_lhs);
     g_list_free(rsc->rsc_tickets);
     g_list_free(rsc->dangling_migrations);
 
     if (rsc->parameter_cache != NULL) {
         g_hash_table_destroy(rsc->parameter_cache);
     }
     if (rsc->meta != NULL) {
         g_hash_table_destroy(rsc->meta);
     }
     if (rsc->utilization != NULL) {
         g_hash_table_destroy(rsc->utilization);
     }
 
     if ((rsc->parent == NULL)
         && pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
 
         free_xml(rsc->xml);
         rsc->xml = NULL;
         free_xml(rsc->orig_xml);
         rsc->orig_xml = NULL;
 
         /* if rsc->orig_xml, then rsc->xml is an expanded xml from a template */
     } else if (rsc->orig_xml) {
         free_xml(rsc->xml);
         rsc->xml = NULL;
     }
     if (rsc->running_on) {
         g_list_free(rsc->running_on);
         rsc->running_on = NULL;
     }
     if (rsc->known_on) {
         g_hash_table_destroy(rsc->known_on);
         rsc->known_on = NULL;
     }
     if (rsc->actions) {
         g_list_free(rsc->actions);
         rsc->actions = NULL;
     }
     if (rsc->allowed_nodes) {
         g_hash_table_destroy(rsc->allowed_nodes);
         rsc->allowed_nodes = NULL;
     }
     g_list_free(rsc->fillers);
     g_list_free(rsc->rsc_location);
     pcmk__rsc_trace(rsc, "Resource freed");
     free(rsc->id);
     free(rsc->clone_name);
     free(rsc->allocated_to);
     free(rsc->variant_opaque);
     free(rsc->pending_task);
     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->partial_migration_source != NULL) {
         if (pcmk__same_node(node, rsc->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->running_on; 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;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) {
         return rsc->fns->active_node(rsc, count, NULL);
 
     } else {
         return rsc->fns->active_node(rsc, NULL, count);
     }
 }
 
 void
 pe__count_common(pcmk_resource_t *rsc)
 {
     if (rsc->children != NULL) {
         for (GList *item = rsc->children; item != NULL; item = item->next) {
             ((pcmk_resource_t *) item->data)->fns->count(item->data);
         }
 
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed)
                || (rsc->role > pcmk_role_stopped)) {
         rsc->cluster->ninstances++;
         if (pe__resource_is_disabled(rsc)) {
             rsc->cluster->disabled_resources++;
         }
         if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
             rsc->cluster->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)
 {
     CRM_ASSERT((rsc != NULL) && (why != NULL));
     if (rsc->next_role != role) {
         pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
                         rsc->id, pcmk_role_text(rsc->next_role),
                         pcmk_role_text(role), why);
         rsc->next_role = role;
     }
 }
diff --git a/lib/pengine/native.c b/lib/pengine/native.c
index 3828a4cbaf..14c7339736 100644
--- a/lib/pengine/native.c
+++ b/lib/pengine/native.c
@@ -1,1477 +1,1477 @@
 /*
  * 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 <crm_internal.h>
 
 #include <stdint.h>
 
 #include <crm/common/output.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/status.h>
 #include <crm/pengine/complex.h>
 #include <crm/pengine/internal.h>
 #include <crm/common/xml.h>
 #include <pe_status_private.h>
 
 #ifdef PCMK__COMPAT_2_0
 #define PROVIDER_SEP "::"
 #else
 #define PROVIDER_SEP ":"
 #endif
 
 /*!
  * \internal
  * \brief Check whether a resource is active on multiple nodes
  */
 static bool
 is_multiply_active(const pcmk_resource_t *rsc)
 {
     unsigned int count = 0;
 
     if (rsc->variant == pcmk_rsc_variant_primitive) {
         pe__find_active_requires(rsc, &count);
     }
     return count > 1;
 }
 
 static void
 native_priority_to_node(pcmk_resource_t *rsc, pcmk_node_t *node,
                         gboolean failed)
 {
     int priority = 0;
 
     if ((rsc->priority == 0) || (failed == TRUE)) {
         return;
     }
 
     if (rsc->role == pcmk_role_promoted) {
         // Promoted instance takes base priority + 1
         priority = rsc->priority + 1;
 
     } else {
         priority = rsc->priority;
     }
 
     node->details->priority += priority;
     pcmk__rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s)",
                     pcmk__node_name(node), node->details->priority,
                     (rsc->role == pcmk_role_promoted)? "promoted " : "",
                     rsc->id, rsc->priority,
                     (rsc->role == pcmk_role_promoted)? " + 1" : "");
 
     /* Priority of a resource running on a guest node is added to the cluster
      * node as well. */
     if (node->details->remote_rsc
         && node->details->remote_rsc->container) {
         GList *gIter = node->details->remote_rsc->container->running_on;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_node_t *a_node = gIter->data;
 
             a_node->details->priority += priority;
             pcmk__rsc_trace(rsc,
                             "%s now has priority %d with %s'%s' "
                             "(priority: %d%s) from guest node %s",
                             pcmk__node_name(a_node), a_node->details->priority,
                             (rsc->role == pcmk_role_promoted)? "promoted " : "",
                             rsc->id, rsc->priority,
                             (rsc->role == pcmk_role_promoted)? " + 1" : "",
                             pcmk__node_name(node));
         }
     }
 }
 
 void
 native_add_running(pcmk_resource_t *rsc, pcmk_node_t *node,
                    pcmk_scheduler_t *scheduler, gboolean failed)
 {
     GList *gIter = rsc->running_on;
 
     CRM_CHECK(node != NULL, return);
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_node_t *a_node = (pcmk_node_t *) gIter->data;
 
         CRM_CHECK(a_node != NULL, return);
         if (pcmk__str_eq(a_node->details->id, node->details->id, pcmk__str_casei)) {
             return;
         }
     }
 
     pcmk__rsc_trace(rsc, "Adding %s to %s %s", rsc->id, pcmk__node_name(node),
                     pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : "(unmanaged)");
 
     rsc->running_on = g_list_append(rsc->running_on, node);
     if (rsc->variant == pcmk_rsc_variant_primitive) {
         node->details->running_rsc = g_list_append(node->details->running_rsc, rsc);
 
         native_priority_to_node(rsc, node, failed);
     }
 
     if ((rsc->variant == pcmk_rsc_variant_primitive)
         && node->details->maintenance) {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_maintenance);
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         pcmk_resource_t *p = rsc->parent;
 
         pcmk__rsc_info(rsc, "resource %s isn't managed", rsc->id);
-        resource_location(rsc, node, INFINITY, "not_managed_default",
-                          scheduler);
+        resource_location(rsc, node, PCMK_SCORE_INFINITY,
+                          "not_managed_default", scheduler);
 
         while(p && node->details->online) {
             /* add without the additional location constraint */
             p->running_on = g_list_append(p->running_on, node);
             p = p->parent;
         }
         return;
     }
 
     if (is_multiply_active(rsc)) {
         switch (rsc->recovery_type) {
             case pcmk_multiply_active_stop:
                 {
                     GHashTableIter gIter;
                     pcmk_node_t *local_node = NULL;
 
                     /* make sure it doesn't come up again */
                     if (rsc->allowed_nodes != NULL) {
                         g_hash_table_destroy(rsc->allowed_nodes);
                     }
                     rsc->allowed_nodes = pe__node_list2table(scheduler->nodes);
                     g_hash_table_iter_init(&gIter, rsc->allowed_nodes);
                     while (g_hash_table_iter_next(&gIter, NULL, (void **)&local_node)) {
-                        local_node->weight = -INFINITY;
+                        local_node->weight = -PCMK_SCORE_INFINITY;
                     }
                 }
                 break;
             case pcmk_multiply_active_block:
                 pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
                 pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
 
                 /* If the resource belongs to a group or bundle configured with
                  * PCMK_META_MULTIPLE_ACTIVE=PCMK_VALUE_BLOCK, block the entire
                  * entity.
                  */
                 if (rsc->parent
                     && ((rsc->parent->variant == pcmk_rsc_variant_group)
                         || (rsc->parent->variant == pcmk_rsc_variant_bundle))
                     && (rsc->parent->recovery_type == pcmk_multiply_active_block)) {
                     GList *gIter = rsc->parent->children;
 
                     for (; gIter != NULL; gIter = gIter->next) {
                         pcmk_resource_t *child = gIter->data;
 
                         pcmk__clear_rsc_flags(child, pcmk_rsc_managed);
                         pcmk__set_rsc_flags(child, pcmk_rsc_blocked);
                     }
                 }
                 break;
 
             // pcmk_multiply_active_restart, pcmk_multiply_active_unexpected
             default:
                 /* The scheduler will do the right thing because the relevant
                  * variables and flags are set when unpacking the history.
                  */
                 break;
         }
         crm_debug("%s is active on multiple nodes including %s: %s",
                   rsc->id, pcmk__node_name(node),
                   pcmk_multiply_active_text(rsc->recovery_type));
 
     } else {
         pcmk__rsc_trace(rsc, "Resource %s is active on %s",
                         rsc->id, pcmk__node_name(node));
     }
 
     if (rsc->parent != NULL) {
         native_add_running(rsc->parent, node, scheduler, FALSE);
     }
 }
 
 static void
 recursive_clear_unique(pcmk_resource_t *rsc, gpointer user_data)
 {
     pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique);
     pcmk__insert_meta(rsc, PCMK_META_GLOBALLY_UNIQUE, PCMK_VALUE_FALSE);
     g_list_foreach(rsc->children, (GFunc) recursive_clear_unique, NULL);
 }
 
 gboolean
 native_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *parent = uber_parent(rsc);
     const char *standard = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     uint32_t ra_caps = pcmk_get_ra_caps(standard);
 
     pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
 
     // Only some agent standards support unique and promotable clones
     if (!pcmk_is_set(ra_caps, pcmk_ra_cap_unique)
         && pcmk_is_set(rsc->flags, pcmk_rsc_unique)
         && pcmk__is_clone(parent)) {
 
         /* @COMPAT We should probably reject this situation as an error (as we
          * do for promotable below) rather than warn and convert, but that would
          * be a backward-incompatible change that we should probably do with a
          * transform at a schema major version bump.
          */
         pe__force_anon(standard, parent, rsc->id, scheduler);
 
         /* Clear PCMK_META_GLOBALLY_UNIQUE on the parent and all its descendants
          * unpacked so far (clearing the parent should make any future children
          * unpacking correct). We have to clear this resource explicitly because
          * it isn't hooked into the parent's children yet.
          */
         recursive_clear_unique(parent, NULL);
         recursive_clear_unique(rsc, NULL);
     }
     if (!pcmk_is_set(ra_caps, pcmk_ra_cap_promotable)
         && pcmk_is_set(parent->flags, pcmk_rsc_promotable)) {
 
         pcmk__config_err("Resource %s is of type %s and therefore "
                          "cannot be used as a promotable clone resource",
                          rsc->id, standard);
         return FALSE;
     }
     return TRUE;
 }
 
 static bool
 rsc_is_on_node(pcmk_resource_t *rsc, const pcmk_node_t *node, int flags)
 {
     pcmk__rsc_trace(rsc, "Checking whether %s is on %s",
                     rsc->id, pcmk__node_name(node));
 
     if (pcmk_is_set(flags, pcmk_rsc_match_current_node)
         && (rsc->running_on != NULL)) {
 
         for (GList *iter = rsc->running_on; iter; iter = iter->next) {
             if (pcmk__same_node((pcmk_node_t *) iter->data, node)) {
                 return true;
             }
         }
 
     } else if (pcmk_is_set(flags, pe_find_inactive) // @COMPAT deprecated
                && (rsc->running_on == NULL)) {
         return true;
 
     } else if (!pcmk_is_set(flags, pcmk_rsc_match_current_node)
                && (rsc->allocated_to != NULL)
                && pcmk__same_node(rsc->allocated_to, node)) {
         return true;
     }
     return false;
 }
 
 pcmk_resource_t *
 native_find_rsc(pcmk_resource_t *rsc, const char *id,
                 const pcmk_node_t *on_node, int flags)
 {
     bool match = false;
     pcmk_resource_t *result = NULL;
 
     CRM_CHECK(id && rsc && rsc->id, return NULL);
 
     if (pcmk_is_set(flags, pcmk_rsc_match_clone_only)) {
         const char *rid = pcmk__xe_id(rsc->xml);
 
         if (!pcmk__is_clone(pe__const_top_resource(rsc, false))) {
             match = false;
 
         } else if (!strcmp(id, rsc->id) || pcmk__str_eq(id, rid, pcmk__str_none)) {
             match = true;
         }
 
     } else if (!strcmp(id, rsc->id)) {
         match = true;
 
     } else if (pcmk_is_set(flags, pcmk_rsc_match_history)
                && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) {
         match = true;
 
     } else if (pcmk_is_set(flags, pcmk_rsc_match_basename)
                || (pcmk_is_set(flags, pcmk_rsc_match_anon_basename)
                    && !pcmk_is_set(rsc->flags, pcmk_rsc_unique))) {
         match = pe_base_name_eq(rsc, id);
     }
 
     if (match && on_node) {
         if (!rsc_is_on_node(rsc, on_node, flags)) {
             match = false;
         }
     }
 
     if (match) {
         return rsc;
     }
 
     for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *child = (pcmk_resource_t *) gIter->data;
 
         result = rsc->fns->find_rsc(child, id, on_node, flags);
         if (result) {
             return result;
         }
     }
     return NULL;
 }
 
 // create is ignored
 char *
 native_parameter(pcmk_resource_t *rsc, pcmk_node_t *node, gboolean create,
                  const char *name, pcmk_scheduler_t *scheduler)
 {
     char *value_copy = NULL;
     const char *value = NULL;
     GHashTable *params = NULL;
 
     CRM_CHECK(rsc != NULL, return NULL);
     CRM_CHECK(name != NULL && strlen(name) != 0, return NULL);
 
     pcmk__rsc_trace(rsc, "Looking up %s in %s", name, rsc->id);
     params = pe_rsc_params(rsc, node, scheduler);
     value = g_hash_table_lookup(params, name);
     if (value == NULL) {
         /* try meta attributes instead */
         value = g_hash_table_lookup(rsc->meta, name);
     }
     pcmk__str_update(&value_copy, value);
     return value_copy;
 }
 
 gboolean
 native_active(pcmk_resource_t * rsc, gboolean all)
 {
     for (GList *gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) {
         pcmk_node_t *a_node = (pcmk_node_t *) gIter->data;
 
         if (a_node->details->unclean) {
             pcmk__rsc_trace(rsc, "Resource %s: %s is unclean",
                             rsc->id, pcmk__node_name(a_node));
             return TRUE;
         } else if (!a_node->details->online
                    && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             pcmk__rsc_trace(rsc, "Resource %s: %s is offline",
                             rsc->id, pcmk__node_name(a_node));
         } else {
             pcmk__rsc_trace(rsc, "Resource %s active on %s",
                             rsc->id, pcmk__node_name(a_node));
             return TRUE;
         }
     }
     return FALSE;
 }
 
 struct print_data_s {
     long options;
     void *print_data;
 };
 
 static const char *
 native_pending_state(const pcmk_resource_t *rsc)
 {
     const char *pending_state = NULL;
 
     if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_START, pcmk__str_casei)) {
         pending_state = "Starting";
 
     } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_STOP,
                             pcmk__str_casei)) {
         pending_state = "Stopping";
 
     } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_TO,
                             pcmk__str_casei)) {
         pending_state = "Migrating";
 
     } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_FROM,
                             pcmk__str_casei)) {
        /* Work might be done in here. */
         pending_state = "Migrating";
 
     } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_PROMOTE,
                             pcmk__str_casei)) {
         pending_state = "Promoting";
 
     } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_DEMOTE,
                             pcmk__str_casei)) {
         pending_state = "Demoting";
     }
 
     return pending_state;
 }
 
 static const char *
 native_pending_task(const pcmk_resource_t *rsc)
 {
     const char *pending_task = NULL;
 
     if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
         pending_task = "Monitoring";
 
     /* Pending probes are not printed, even if pending
      * operations are requested. If someone ever requests that
      * behavior, uncomment this and the corresponding part of
      * unpack.c:unpack_rsc_op().
      */
     /*
     } else if (pcmk__str_eq(rsc->pending_task, "probe", pcmk__str_casei)) {
         pending_task = "Checking";
     */
     }
 
     return pending_task;
 }
 
 static enum rsc_role_e
 native_displayable_role(const pcmk_resource_t *rsc)
 {
     enum rsc_role_e role = rsc->role;
 
     if ((role == pcmk_role_started)
         && pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
                        pcmk_rsc_promotable)) {
 
         role = pcmk_role_unpromoted;
     }
     return role;
 }
 
 static const char *
 native_displayable_state(const pcmk_resource_t *rsc, bool print_pending)
 {
     const char *rsc_state = NULL;
 
     if (print_pending) {
         rsc_state = native_pending_state(rsc);
     }
     if (rsc_state == NULL) {
         rsc_state = pcmk_role_text(native_displayable_role(rsc));
     }
     return rsc_state;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 static void
 native_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options,
                  void *print_data)
 {
     const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
     const char *rsc_state = native_displayable_state(rsc, pcmk_is_set(options, pe_print_pending));
     const char *target_role = NULL;
 
     /* resource information. */
     status_print("%s<resource ", pre_text);
     status_print(PCMK_XA_ID "=\"%s\" ", rsc_printable_id(rsc));
     status_print("resource_agent=\"%s%s%s:%s\" ", class,
                  ((prov == NULL)? "" : PROVIDER_SEP),
                  ((prov == NULL)? "" : prov),
                  crm_element_value(rsc->xml, PCMK_XA_TYPE));
 
     status_print("role=\"%s\" ", rsc_state);
     if (rsc->meta) {
         target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE);
     }
     if (target_role) {
         status_print("target_role=\"%s\" ", target_role);
     }
     status_print("active=\"%s\" ", pcmk__btoa(rsc->fns->active(rsc, TRUE)));
     status_print("orphaned=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_removed));
     status_print("blocked=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_blocked));
     status_print("managed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_managed));
     status_print("failed=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_failed));
     status_print("failure_ignored=\"%s\" ",
                  pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure));
     status_print("nodes_running_on=\"%d\" ", g_list_length(rsc->running_on));
 
     if (options & pe_print_pending) {
         const char *pending_task = native_pending_task(rsc);
 
         if (pending_task) {
             status_print("pending=\"%s\" ", pending_task);
         }
     }
 
     /* print out the nodes this resource is running on */
     if (options & pe_print_rsconly) {
         status_print("/>\n");
         /* do nothing */
     } else if (rsc->running_on != NULL) {
         GList *gIter = rsc->running_on;
 
         status_print(">\n");
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_node_t *node = (pcmk_node_t *) gIter->data;
 
             status_print("%s    <node name=\"%s\" " PCMK_XA_ID "=\"%s\" "
                          "cached=\"%s\"/>\n",
                          pre_text, pcmk__s(node->details->uname, ""),
                          node->details->id, pcmk__btoa(!node->details->online));
         }
         status_print("%s</resource>\n", pre_text);
     } else {
         status_print("/>\n");
     }
 }
 
 // Append a flag to resource description string's flags list
 static bool
 add_output_flag(GString *s, const char *flag_desc, bool have_flags)
 {
     g_string_append(s, (have_flags? ", " : " ("));
     g_string_append(s, flag_desc);
     return true;
 }
 
 // Append a node name to resource description string's node list
 static bool
 add_output_node(GString *s, const char *node, bool have_nodes)
 {
     g_string_append(s, (have_nodes? " " : " [ "));
     g_string_append(s, node);
     return true;
 }
 
 /*!
  * \internal
  * \brief Create a string description of a resource
  *
  * \param[in] rsc          Resource to describe
  * \param[in] name         Desired identifier for the resource
  * \param[in] node         If not NULL, node that resource is "on"
  * \param[in] show_opts    Bitmask of pcmk_show_opt_e.
  * \param[in] target_role  Resource's target role
  * \param[in] show_nodes   Whether to display nodes when multiply active
  *
  * \return Newly allocated string description of resource
  * \note Caller must free the result with g_free().
  */
 gchar *
 pcmk__native_output_string(const pcmk_resource_t *rsc, const char *name,
                            const pcmk_node_t *node, uint32_t show_opts,
                            const char *target_role, bool show_nodes)
 {
     const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     const char *provider = NULL;
     const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE);
     GString *outstr = NULL;
     bool have_flags = false;
 
     if (rsc->variant != pcmk_rsc_variant_primitive) {
         return NULL;
     }
 
     CRM_CHECK(name != NULL, name = "unknown");
     CRM_CHECK(kind != NULL, kind = "unknown");
     CRM_CHECK(class != NULL, class = "unknown");
 
     if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) {
         provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
     }
 
     if ((node == NULL) && (rsc->lock_node != NULL)) {
         node = rsc->lock_node;
     }
     if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only)
         || pcmk__list_of_multiple(rsc->running_on)) {
         node = NULL;
     }
 
     outstr = g_string_sized_new(128);
 
     // Resource name and agent
     pcmk__g_strcat(outstr,
                    name, "\t(", class, ((provider == NULL)? "" : PROVIDER_SEP),
                    pcmk__s(provider, ""), ":", kind, "):\t", NULL);
 
     // State on node
     if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
         g_string_append(outstr, " ORPHANED");
     }
     if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         enum rsc_role_e role = native_displayable_role(rsc);
 
         g_string_append(outstr, " FAILED");
         if (role > pcmk_role_unpromoted) {
             pcmk__add_word(&outstr, 0, pcmk_role_text(role));
         }
     } else {
         bool show_pending = pcmk_is_set(show_opts, pcmk_show_pending);
 
         pcmk__add_word(&outstr, 0, native_displayable_state(rsc, show_pending));
     }
     if (node) {
         pcmk__add_word(&outstr, 0, pcmk__node_name(node));
     }
 
     // Failed probe operation
     if (native_displayable_role(rsc) == pcmk_role_stopped) {
         xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node ? node->details->uname : NULL);
         if (probe_op != NULL) {
             int rc;
 
             pcmk__scan_min_int(crm_element_value(probe_op, PCMK__XA_RC_CODE),
                                &rc, 0);
             pcmk__g_strcat(outstr, " (", services_ocf_exitcode_str(rc), ") ",
                            NULL);
         }
     }
 
     // Flags, as: (<flag> [...])
     if (node && !(node->details->online) && node->details->unclean) {
         have_flags = add_output_flag(outstr, "UNCLEAN", have_flags);
     }
     if (node && (node == rsc->lock_node)) {
         have_flags = add_output_flag(outstr, "LOCKED", have_flags);
     }
     if (pcmk_is_set(show_opts, pcmk_show_pending)) {
         const char *pending_task = native_pending_task(rsc);
 
         if (pending_task) {
             have_flags = add_output_flag(outstr, pending_task, have_flags);
         }
     }
     if (target_role != NULL) {
         switch (pcmk_parse_role(target_role)) {
             case pcmk_role_unknown:
                 pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
                                  " %s for resource %s", target_role, rsc->id);
                 break;
 
             case pcmk_role_stopped:
                 have_flags = add_output_flag(outstr, "disabled", have_flags);
                 break;
 
             case pcmk_role_unpromoted:
                 if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags,
                                 pcmk_rsc_promotable)) {
                     have_flags = add_output_flag(outstr,
                                                  PCMK_META_TARGET_ROLE ":",
                                                  have_flags);
                     g_string_append(outstr, target_role);
                 }
                 break;
 
             default:
                 /* Only show target role if it limits our abilities (i.e. ignore
                  * Started, as it is the default anyways, and doesn't prevent
                  * the resource from becoming promoted).
                  */
                 break;
         }
     }
 
     // Blocked or maintenance implies unmanaged
     if (pcmk_any_flags_set(rsc->flags,
                            pcmk_rsc_blocked|pcmk_rsc_maintenance)) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) {
             have_flags = add_output_flag(outstr, "blocked", have_flags);
 
         } else if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) {
             have_flags = add_output_flag(outstr, "maintenance", have_flags);
         }
     } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         have_flags = add_output_flag(outstr, "unmanaged", have_flags);
     }
 
     if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) {
         have_flags = add_output_flag(outstr, "failure ignored", have_flags);
     }
 
 
     if (have_flags) {
         g_string_append_c(outstr, ')');
     }
 
     // User-supplied description
     if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description)
         || pcmk__list_of_multiple(rsc->running_on)) {
         const char *desc = crm_element_value(rsc->xml, PCMK_XA_DESCRIPTION);
 
         if (desc) {
             g_string_append(outstr, " (");
             g_string_append(outstr, desc);
             g_string_append(outstr, ")");
 
         }
     }
 
     if (show_nodes && !pcmk_is_set(show_opts, pcmk_show_rsc_only)
         && pcmk__list_of_multiple(rsc->running_on)) {
         bool have_nodes = false;
 
         for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) {
             pcmk_node_t *n = (pcmk_node_t *) iter->data;
 
             have_nodes = add_output_node(outstr, n->details->uname, have_nodes);
         }
         if (have_nodes) {
             g_string_append(outstr, " ]");
         }
     }
 
     return g_string_free(outstr, FALSE);
 }
 
 int
 pe__common_output_html(pcmk__output_t *out, const pcmk_resource_t *rsc,
                        const char *name, const pcmk_node_t *node,
                        uint32_t show_opts)
 {
     const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE);
     const char *target_role = NULL;
 
     xmlNodePtr list_node = NULL;
     const char *cl = NULL;
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
     CRM_ASSERT(kind != NULL);
 
     if (rsc->meta) {
         const char *is_internal = g_hash_table_lookup(rsc->meta,
                                                       PCMK__META_INTERNAL_RSC);
 
         if (crm_is_true(is_internal)
             && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) {
 
             crm_trace("skipping print of internal resource %s", rsc->id);
             return pcmk_rc_no_output;
         }
         target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE);
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
         cl = PCMK__VALUE_RSC_MANAGED;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         cl = PCMK__VALUE_RSC_FAILED;
 
     } else if ((rsc->variant == pcmk_rsc_variant_primitive)
                && (rsc->running_on == NULL)) {
         cl = PCMK__VALUE_RSC_FAILED;
 
     } else if (pcmk__list_of_multiple(rsc->running_on)) {
         cl = PCMK__VALUE_RSC_MULTIPLE;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) {
         cl = PCMK__VALUE_RSC_FAILURE_IGNORED;
 
     } else {
         cl = PCMK__VALUE_RSC_OK;
     }
 
     {
         gchar *s = pcmk__native_output_string(rsc, name, node, show_opts,
                                               target_role, true);
 
         list_node = pcmk__output_create_html_node(out, "li", NULL, NULL, NULL);
         pcmk_create_html_node(list_node, PCMK__XE_SPAN, NULL, cl, s);
         g_free(s);
     }
 
     return pcmk_rc_ok;
 }
 
 int
 pe__common_output_text(pcmk__output_t *out, const pcmk_resource_t *rsc,
                        const char *name, const pcmk_node_t *node,
                        uint32_t show_opts)
 {
     const char *target_role = NULL;
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
 
     if (rsc->meta) {
         const char *is_internal = g_hash_table_lookup(rsc->meta,
                                                       PCMK__META_INTERNAL_RSC);
 
         if (crm_is_true(is_internal)
             && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) {
 
             crm_trace("skipping print of internal resource %s", rsc->id);
             return pcmk_rc_no_output;
         }
         target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE);
     }
 
     {
         gchar *s = pcmk__native_output_string(rsc, name, node, show_opts,
                                               target_role, true);
 
         out->list_item(out, NULL, "%s", s);
         g_free(s);
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 void
 common_print(pcmk_resource_t *rsc, const char *pre_text, const char *name,
              const pcmk_node_t *node, long options, void *print_data)
 {
     const char *target_role = NULL;
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
 
     if (rsc->meta) {
         const char *is_internal = g_hash_table_lookup(rsc->meta,
                                                       PCMK__META_INTERNAL_RSC);
 
         if (crm_is_true(is_internal)
             && !pcmk_is_set(options, pe_print_implicit)) {
 
             crm_trace("skipping print of internal resource %s", rsc->id);
             return;
         }
         target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE);
     }
 
     if (options & pe_print_xml) {
         native_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     if ((pre_text == NULL) && (options & pe_print_printf)) {
         pre_text = " ";
     }
 
     if (options & pe_print_html) {
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             status_print("<font color=\"yellow\">");
 
         } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
             status_print("<font color=\"red\">");
 
         } else if (rsc->running_on == NULL) {
             status_print("<font color=\"red\">");
 
         } else if (pcmk__list_of_multiple(rsc->running_on)) {
             status_print("<font color=\"orange\">");
 
         } else if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) {
             status_print("<font color=\"yellow\">");
 
         } else {
             status_print("<font color=\"green\">");
         }
     }
 
     {
         gchar *resource_s = pcmk__native_output_string(rsc, name, node, options,
                                                        target_role, false);
         status_print("%s%s", (pre_text? pre_text : ""), resource_s);
         g_free(resource_s);
     }
 
     if (pcmk_is_set(options, pe_print_html)) {
         status_print(" </font> ");
     }
 
     if (!pcmk_is_set(options, pe_print_rsconly)
         && pcmk__list_of_multiple(rsc->running_on)) {
 
         GList *gIter = rsc->running_on;
         int counter = 0;
 
         if (options & pe_print_html) {
             status_print("<ul>\n");
         } else if ((options & pe_print_printf)
                    || (options & pe_print_ncurses)) {
             status_print("[");
         }
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_node_t *n = (pcmk_node_t *) gIter->data;
 
             counter++;
 
             if (options & pe_print_html) {
                 status_print("<li>\n%s", pcmk__node_name(n));
 
             } else if ((options & pe_print_printf)
                        || (options & pe_print_ncurses)) {
                 status_print(" %s", pcmk__node_name(n));
 
             } else if ((options & pe_print_log)) {
                 status_print("\t%d : %s", counter, pcmk__node_name(n));
 
             } else {
                 status_print("%s", pcmk__node_name(n));
             }
             if (options & pe_print_html) {
                 status_print("</li>\n");
 
             }
         }
 
         if (options & pe_print_html) {
             status_print("</ul>\n");
         } else if ((options & pe_print_printf)
                    || (options & pe_print_ncurses)) {
             status_print(" ]");
         }
     }
 
     if (options & pe_print_html) {
         status_print("<br/>\n");
     } else if (options & pe_print_suppres_nl) {
         /* nothing */
     } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) {
         status_print("\n");
     }
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 void
 native_print(pcmk_resource_t *rsc, const char *pre_text, long options,
              void *print_data)
 {
     const pcmk_node_t *node = NULL;
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
     if (options & pe_print_xml) {
         native_print_xml(rsc, pre_text, options, print_data);
         return;
     }
 
     node = pcmk__current_node(rsc);
 
     if (node == NULL) {
         // This is set only if a non-probe action is pending on this node
         node = rsc->pending_node;
     }
 
     common_print(rsc, pre_text, rsc_printable_id(rsc), node, options, print_data);
 }
 
 PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__resource_xml(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node G_GNUC_UNUSED = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     int rc = pcmk_rc_no_output;
     bool print_pending = pcmk_is_set(show_opts, pcmk_show_pending);
     const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
     const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
 
     char ra_name[LINE_MAX];
     const char *rsc_state = native_displayable_state(rsc, print_pending);
     const char *target_role = NULL;
     const char *active = pcmk__btoa(rsc->fns->active(rsc, TRUE));
     const char *orphaned = pcmk__flag_text(rsc->flags, pcmk_rsc_removed);
     const char *blocked = pcmk__flag_text(rsc->flags, pcmk_rsc_blocked);
     const char *maintenance = pcmk__flag_text(rsc->flags, pcmk_rsc_maintenance);
     const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed);
     const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed);
     const char *ignored = pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure);
     char *nodes_running_on = NULL;
     const char *pending = print_pending? native_pending_task(rsc) : NULL;
     const char *locked_to = NULL;
     const char *desc = pe__resource_description(rsc, show_opts);
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return pcmk_rc_no_output;
     }
 
     // Resource information
     snprintf(ra_name, LINE_MAX, "%s%s%s:%s", class,
             ((prov == NULL)? "" : PROVIDER_SEP), ((prov == NULL)? "" : prov),
             crm_element_value(rsc->xml, PCMK_XA_TYPE));
 
     if (rsc->meta != NULL) {
         target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE);
     }
 
     nodes_running_on = pcmk__itoa(g_list_length(rsc->running_on));
 
     if (rsc->lock_node != NULL) {
         locked_to = rsc->lock_node->details->uname;
     }
 
     rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_RESOURCE, 15,
                                   PCMK_XA_ID, rsc_printable_id(rsc),
                                   PCMK_XA_RESOURCE_AGENT, ra_name,
                                   PCMK_XA_ROLE, rsc_state,
                                   PCMK_XA_TARGET_ROLE, target_role,
                                   PCMK_XA_ACTIVE, active,
                                   PCMK_XA_ORPHANED, orphaned,
                                   PCMK_XA_BLOCKED, blocked,
                                   PCMK_XA_MAINTENANCE, maintenance,
                                   PCMK_XA_MANAGED, managed,
                                   PCMK_XA_FAILED, failed,
                                   PCMK_XA_FAILURE_IGNORED, ignored,
                                   PCMK_XA_NODES_RUNNING_ON, nodes_running_on,
                                   PCMK_XA_PENDING, pending,
                                   PCMK_XA_LOCKED_TO, locked_to,
                                   PCMK_XA_DESCRIPTION, desc);
     free(nodes_running_on);
 
     CRM_ASSERT(rc == pcmk_rc_ok);
 
     if (rsc->running_on != NULL) {
         GList *gIter = rsc->running_on;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_node_t *node = (pcmk_node_t *) gIter->data;
             const char *cached = pcmk__btoa(node->details->online);
 
             rc = pe__name_and_nvpairs_xml(out, false, PCMK_XE_NODE, 3,
                                           PCMK_XA_NAME, node->details->uname,
                                           PCMK_XA_ID, node->details->id,
                                           PCMK_XA_CACHED, cached);
             CRM_ASSERT(rc == pcmk_rc_ok);
         }
     }
 
     pcmk__output_xml_pop_parent(out);
     return rc;
 }
 
 PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__resource_html(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node G_GNUC_UNUSED = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     const pcmk_node_t *node = pcmk__current_node(rsc);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return pcmk_rc_no_output;
     }
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
 
     if (node == NULL) {
         // This is set only if a non-probe action is pending on this node
         node = rsc->pending_node;
     }
     return pe__common_output_html(out, rsc, rsc_printable_id(rsc), node, show_opts);
 }
 
 PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *",
                   "GList *")
 int
 pe__resource_text(pcmk__output_t *out, va_list args)
 {
     uint32_t show_opts = va_arg(args, uint32_t);
     pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
     GList *only_node G_GNUC_UNUSED = va_arg(args, GList *);
     GList *only_rsc = va_arg(args, GList *);
 
     const pcmk_node_t *node = pcmk__current_node(rsc);
 
     CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive);
 
     if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
         return pcmk_rc_no_output;
     }
 
     if (node == NULL) {
         // This is set only if a non-probe action is pending on this node
         node = rsc->pending_node;
     }
     return pe__common_output_text(out, rsc, rsc_printable_id(rsc), node, show_opts);
 }
 
 void
 native_free(pcmk_resource_t * rsc)
 {
     pcmk__rsc_trace(rsc, "Freeing resource action list (not the data)");
     common_free(rsc);
 }
 
 enum rsc_role_e
 native_resource_state(const pcmk_resource_t * rsc, gboolean current)
 {
     enum rsc_role_e role = rsc->next_role;
 
     if (current) {
         role = rsc->role;
     }
     pcmk__rsc_trace(rsc, "%s state: %s", rsc->id, pcmk_role_text(role));
     return role;
 }
 
 /*!
  * \internal
  * \brief List nodes where a resource (or any of its children) is
  *
  * \param[in]  rsc      Resource to check
  * \param[out] list     List to add result to
  * \param[in]  current  0 = where allocated, 1 = where running,
  *                      2 = where running or pending
  *
  * \return If list contains only one node, that node, or NULL otherwise
  */
 pcmk_node_t *
 native_location(const pcmk_resource_t *rsc, GList **list, int current)
 {
     // @COMPAT: Accept a pcmk__rsc_node argument instead of int current
     pcmk_node_t *one = NULL;
     GList *result = NULL;
 
     if (rsc->children) {
         GList *gIter = rsc->children;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_resource_t *child = (pcmk_resource_t *) gIter->data;
 
             child->fns->location(child, &result, current);
         }
 
     } else if (current) {
 
         if (rsc->running_on) {
             result = g_list_copy(rsc->running_on);
         }
         if ((current == 2) && rsc->pending_node
             && !pe_find_node_id(result, rsc->pending_node->details->id)) {
                 result = g_list_append(result, rsc->pending_node);
         }
 
     } else if (current == FALSE && rsc->allocated_to) {
         result = g_list_append(NULL, rsc->allocated_to);
     }
 
     if (result && (result->next == NULL)) {
         one = result->data;
     }
 
     if (list) {
         GList *gIter = result;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_node_t *node = (pcmk_node_t *) gIter->data;
 
             if (*list == NULL || pe_find_node_id(*list, node->details->id) == NULL) {
                 *list = g_list_append(*list, node);
             }
         }
     }
 
     g_list_free(result);
     return one;
 }
 
 static void
 get_rscs_brief(GList *rsc_list, GHashTable * rsc_table, GHashTable * active_table)
 {
     GList *gIter = rsc_list;
 
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
 
         const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS);
         const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE);
 
         int offset = 0;
         char buffer[LINE_MAX];
 
         int *rsc_counter = NULL;
         int *active_counter = NULL;
 
         if (rsc->variant != pcmk_rsc_variant_primitive) {
             continue;
         }
 
         offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", class);
         if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) {
             const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER);
 
             if (prov != NULL) {
                 offset += snprintf(buffer + offset, LINE_MAX - offset,
                                    PROVIDER_SEP "%s", prov);
             }
         }
         offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s", kind);
         CRM_LOG_ASSERT(offset > 0);
 
         if (rsc_table) {
             rsc_counter = g_hash_table_lookup(rsc_table, buffer);
             if (rsc_counter == NULL) {
                 rsc_counter = calloc(1, sizeof(int));
                 *rsc_counter = 0;
                 g_hash_table_insert(rsc_table, strdup(buffer), rsc_counter);
             }
             (*rsc_counter)++;
         }
 
         if (active_table) {
             GList *gIter2 = rsc->running_on;
 
             for (; gIter2 != NULL; gIter2 = gIter2->next) {
                 pcmk_node_t *node = (pcmk_node_t *) gIter2->data;
                 GHashTable *node_table = NULL;
 
                 if (node->details->unclean == FALSE && node->details->online == FALSE &&
                     pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
                     continue;
                 }
 
                 node_table = g_hash_table_lookup(active_table, node->details->uname);
                 if (node_table == NULL) {
                     node_table = pcmk__strkey_table(free, free);
                     g_hash_table_insert(active_table, strdup(node->details->uname), node_table);
                 }
 
                 active_counter = g_hash_table_lookup(node_table, buffer);
                 if (active_counter == NULL) {
                     active_counter = calloc(1, sizeof(int));
                     *active_counter = 0;
                     g_hash_table_insert(node_table, strdup(buffer), active_counter);
                 }
                 (*active_counter)++;
             }
         }
     }
 }
 
 static void
 destroy_node_table(gpointer data)
 {
     GHashTable *node_table = data;
 
     if (node_table) {
         g_hash_table_destroy(node_table);
     }
 }
 
 /*!
  * \internal
  * \deprecated This function will be removed in a future release
  */
 void
 print_rscs_brief(GList *rsc_list, const char *pre_text, long options,
                  void *print_data, gboolean print_all)
 {
     GHashTable *rsc_table = pcmk__strkey_table(free, free);
     GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table);
     GHashTableIter hash_iter;
     char *type = NULL;
     int *rsc_counter = NULL;
 
     get_rscs_brief(rsc_list, rsc_table, active_table);
 
     g_hash_table_iter_init(&hash_iter, rsc_table);
     while (g_hash_table_iter_next(&hash_iter, (gpointer *)&type, (gpointer *)&rsc_counter)) {
         GHashTableIter hash_iter2;
         char *node_name = NULL;
         GHashTable *node_table = NULL;
         int active_counter_all = 0;
 
         g_hash_table_iter_init(&hash_iter2, active_table);
         while (g_hash_table_iter_next(&hash_iter2, (gpointer *)&node_name, (gpointer *)&node_table)) {
             int *active_counter = g_hash_table_lookup(node_table, type);
 
             if (active_counter == NULL || *active_counter == 0) {
                 continue;
 
             } else {
                 active_counter_all += *active_counter;
             }
 
             if (options & pe_print_rsconly) {
                 node_name = NULL;
             }
 
             if (options & pe_print_html) {
                 status_print("<li>\n");
             }
 
             if (print_all) {
                 status_print("%s%d/%d\t(%s):\tActive %s\n", pre_text ? pre_text : "",
                              active_counter ? *active_counter : 0,
                              rsc_counter ? *rsc_counter : 0, type,
                              active_counter && (*active_counter > 0) && node_name ? node_name : "");
             } else {
                 status_print("%s%d\t(%s):\tActive %s\n", pre_text ? pre_text : "",
                              active_counter ? *active_counter : 0, type,
                              active_counter && (*active_counter > 0) && node_name ? node_name : "");
             }
 
             if (options & pe_print_html) {
                 status_print("</li>\n");
             }
         }
 
         if (print_all && active_counter_all == 0) {
             if (options & pe_print_html) {
                 status_print("<li>\n");
             }
 
             status_print("%s%d/%d\t(%s):\tActive\n", pre_text ? pre_text : "",
                          active_counter_all,
                          rsc_counter ? *rsc_counter : 0, type);
 
             if (options & pe_print_html) {
                 status_print("</li>\n");
             }
         }
     }
 
     if (rsc_table) {
         g_hash_table_destroy(rsc_table);
         rsc_table = NULL;
     }
     if (active_table) {
         g_hash_table_destroy(active_table);
         active_table = NULL;
     }
 }
 
 int
 pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, uint32_t show_opts)
 {
     GHashTable *rsc_table = pcmk__strkey_table(free, free);
     GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table);
     GList *sorted_rscs;
     int rc = pcmk_rc_no_output;
 
     get_rscs_brief(rsc_list, rsc_table, active_table);
 
     /* Make a list of the rsc_table keys so that it can be sorted.  This is to make sure
      * output order stays consistent between systems.
      */
     sorted_rscs = g_hash_table_get_keys(rsc_table);
     sorted_rscs = g_list_sort(sorted_rscs, (GCompareFunc) strcmp);
 
     for (GList *gIter = sorted_rscs; gIter; gIter = gIter->next) {
         char *type = (char *) gIter->data;
         int *rsc_counter = g_hash_table_lookup(rsc_table, type);
 
         GList *sorted_nodes = NULL;
         int active_counter_all = 0;
 
         /* Also make a list of the active_table keys so it can be sorted.  If there's
          * more than one instance of a type of resource running, we need the nodes to
          * be sorted to make sure output order stays consistent between systems.
          */
         sorted_nodes = g_hash_table_get_keys(active_table);
         sorted_nodes = g_list_sort(sorted_nodes, (GCompareFunc) pcmk__numeric_strcasecmp);
 
         for (GList *gIter2 = sorted_nodes; gIter2; gIter2 = gIter2->next) {
             char *node_name = (char *) gIter2->data;
             GHashTable *node_table = g_hash_table_lookup(active_table, node_name);
             int *active_counter = NULL;
 
             if (node_table == NULL) {
                 continue;
             }
 
             active_counter = g_hash_table_lookup(node_table, type);
 
             if (active_counter == NULL || *active_counter == 0) {
                 continue;
 
             } else {
                 active_counter_all += *active_counter;
             }
 
             if (pcmk_is_set(show_opts, pcmk_show_rsc_only)) {
                 node_name = NULL;
             }
 
             if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
                 out->list_item(out, NULL, "%d/%d\t(%s):\tActive %s",
                                *active_counter,
                                rsc_counter ? *rsc_counter : 0, type,
                                (*active_counter > 0) && node_name ? node_name : "");
             } else {
                 out->list_item(out, NULL, "%d\t(%s):\tActive %s",
                                *active_counter, type,
                                (*active_counter > 0) && node_name ? node_name : "");
             }
 
             rc = pcmk_rc_ok;
         }
 
         if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs) && active_counter_all == 0) {
             out->list_item(out, NULL, "%d/%d\t(%s):\tActive",
                            active_counter_all,
                            rsc_counter ? *rsc_counter : 0, type);
             rc = pcmk_rc_ok;
         }
 
         if (sorted_nodes) {
             g_list_free(sorted_nodes);
         }
     }
 
     if (rsc_table) {
         g_hash_table_destroy(rsc_table);
         rsc_table = NULL;
     }
     if (active_table) {
         g_hash_table_destroy(active_table);
         active_table = NULL;
     }
     if (sorted_rscs) {
         g_list_free(sorted_rscs);
     }
 
     return rc;
 }
 
 gboolean
 pe__native_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
                        gboolean check_parent)
 {
     if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) ||
         pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) {
         return FALSE;
     } else if (check_parent && rsc->parent) {
         const pcmk_resource_t *up = pe__const_top_resource(rsc, true);
 
         return up->fns->is_filtered(up, only_rsc, FALSE);
     }
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Get maximum primitive resource instances per node
  *
  * \param[in] rsc  Primitive resource to check
  *
  * \return Maximum number of \p rsc instances that can be active on one node
  */
 unsigned int
 pe__primitive_max_per_node(const pcmk_resource_t *rsc)
 {
     CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive));
     return 1U;
 }
diff --git a/lib/pengine/pe_health.c b/lib/pengine/pe_health.c
index be1f48fb24..4f7eb10b5e 100644
--- a/lib/pengine/pe_health.c
+++ b/lib/pengine/pe_health.c
@@ -1,158 +1,158 @@
 /*
  * 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 <crm_internal.h>
 
 #include <crm/pengine/status.h>
 #include <crm/pengine/internal.h>
 #include "pe_status_private.h"
 
 /*!
  * \internal
  * \brief Set the node health values to use for \c PCMK_VALUE_RED,
  *        \c PCMK_VALUE_YELLOW, and \c PCMK_VALUE_GREEN
  *
  * \param[in,out] scheduler  Scheduler data
  */
 void
 pe__unpack_node_health_scores(pcmk_scheduler_t *scheduler)
 {
     switch (pe__health_strategy(scheduler)) {
         case pcmk__health_strategy_none:
             pcmk__score_red = 0;
             pcmk__score_yellow = 0;
             pcmk__score_green = 0;
             break;
 
         case pcmk__health_strategy_no_red:
-            pcmk__score_red = -INFINITY;
+            pcmk__score_red = -PCMK_SCORE_INFINITY;
             pcmk__score_yellow = 0;
             pcmk__score_green = 0;
             break;
 
         case pcmk__health_strategy_only_green:
-            pcmk__score_red = -INFINITY;
-            pcmk__score_yellow = -INFINITY;
+            pcmk__score_red = -PCMK_SCORE_INFINITY;
+            pcmk__score_yellow = -PCMK_SCORE_INFINITY;
             pcmk__score_green = 0;
             break;
 
         default: // progressive or custom
             pcmk__score_red = pe__health_score(PCMK_OPT_NODE_HEALTH_RED,
                                                scheduler);
             pcmk__score_green = pe__health_score(PCMK_OPT_NODE_HEALTH_GREEN,
                                                  scheduler);
             pcmk__score_yellow = pe__health_score(PCMK_OPT_NODE_HEALTH_YELLOW,
                                                   scheduler);
             break;
     }
 
     if ((pcmk__score_red != 0) || (pcmk__score_yellow != 0)
         || (pcmk__score_green != 0)) {
         crm_debug("Values of node health scores: "
                   PCMK_VALUE_RED "=%d "
                   PCMK_VALUE_YELLOW "=%d "
                   PCMK_VALUE_GREEN "=%d",
                   pcmk__score_red, pcmk__score_yellow, pcmk__score_green);
     }
 }
 
 /*!
  * \internal
  * \brief Add node attribute value to an integer, if it is a health attribute
  *
  * \param[in]     key        Name of node attribute
  * \param[in]     value      String value of node attribute
  * \param[in,out] user_data  Address of integer to which \p value should be
  *                           added if \p key is a node health attribute
  */
 static void
 add_node_health_value(gpointer key, gpointer value, gpointer user_data)
 {
     if (pcmk__starts_with((const char *) key, "#health")) {
         int score = char2score((const char *) value);
         int *health = (int *) user_data;
 
         *health = pcmk__add_scores(score, *health);
         crm_trace("Combined '%s' into node health score (now %s)",
                   (const char *) value, pcmk_readable_score(*health));
     }
 }
 
 /*!
  * \internal
  * \brief Sum a node's health attribute scores
  *
  * \param[in] node         Node whose health attributes should be added
  * \param[in] base_health  Add this number to the total
  *
  * \return Sum of all health attribute scores of \p node plus \p base_health
  */
 int
 pe__sum_node_health_scores(const pcmk_node_t *node, int base_health)
 {
     CRM_ASSERT(node != NULL);
     g_hash_table_foreach(node->details->attrs, add_node_health_value,
                          &base_health);
     return base_health;
 }
 
 /*!
  * \internal
  * \brief Check the general health status for a node
  *
  * \param[in,out] node  Node to check
  *
  * \return  A negative value if any health attribute for \p node is red,
  *          otherwise 0 if any attribute is yellow, otherwise a positive value.
  */
 int
 pe__node_health(pcmk_node_t *node)
 {
     GHashTableIter iter;
     const char *name = NULL;
     const char *value = NULL;
     enum pcmk__health_strategy strategy;
     int score = 0;
     int rc = 1;
 
     CRM_ASSERT(node != NULL);
 
     strategy = pe__health_strategy(node->details->data_set);
     if (strategy == pcmk__health_strategy_none) {
         return rc;
     }
 
     g_hash_table_iter_init(&iter, node->details->attrs);
     while (g_hash_table_iter_next(&iter, (gpointer *) &name,
                                   (gpointer *) &value)) {
         if (pcmk__starts_with(name, "#health")) {
             /* It's possible that pcmk__score_red equals pcmk__score_yellow,
              * or pcmk__score_yellow equals pcmk__score_green, so check the
              * textual value first to be able to distinguish those.
              */
             if (pcmk__str_eq(value, PCMK_VALUE_RED, pcmk__str_casei)) {
                 return -1;
             } else if (pcmk__str_eq(value, PCMK_VALUE_YELLOW,
                                     pcmk__str_casei)) {
                 rc = 0;
                 continue;
             }
 
             // The value is an integer, so compare numerically
             score = char2score(value);
             if (score <= pcmk__score_red) {
                 return -1;
             } else if ((score <= pcmk__score_yellow)
                        && (pcmk__score_yellow != pcmk__score_green)) {
                 rc = 0;
             }
         }
     }
     return rc;
 }
diff --git a/lib/pengine/pe_notif.c b/lib/pengine/pe_notif.c
index 8e5ac34bcb..549d49f8ce 100644
--- a/lib/pengine/pe_notif.c
+++ b/lib/pengine/pe_notif.c
@@ -1,1007 +1,1007 @@
 /*
  * Copyright 2004-2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 #include <crm/common/xml.h>
 
 #include <crm/pengine/internal.h>
 #include <pacemaker-internal.h>
 
 #include "pe_status_private.h"
 
 typedef struct notify_entry_s {
     const pcmk_resource_t *rsc;
     const pcmk_node_t *node;
 } notify_entry_t;
 
 /*!
  * \internal
  * \brief Compare two notification entries
  *
  * Compare two notification entries, where the one with the alphabetically first
  * resource name (or if equal, node name) sorts as first, with NULL sorting as
  * less than non-NULL.
  *
  * \param[in] a  First notification entry to compare
  * \param[in] b  Second notification entry to compare
  *
  * \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1
  */
 static gint
 compare_notify_entries(gconstpointer a, gconstpointer b)
 {
     int tmp;
     const notify_entry_t *entry_a = a;
     const notify_entry_t *entry_b = b;
 
     // NULL a or b is not actually possible
     if ((entry_a == NULL) && (entry_b == NULL)) {
         return 0;
     }
     if (entry_a == NULL) {
         return 1;
     }
     if (entry_b == NULL) {
         return -1;
     }
 
     // NULL resources sort first
     if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) {
         return 0;
     }
     if (entry_a->rsc == NULL) {
         return 1;
     }
     if (entry_b->rsc == NULL) {
         return -1;
     }
 
     // Compare resource names
     tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id);
     if (tmp != 0) {
         return tmp;
     }
 
     // Otherwise NULL nodes sort first
     if ((entry_a->node == NULL) && (entry_b->node == NULL)) {
         return 0;
     }
     if (entry_a->node == NULL) {
         return 1;
     }
     if (entry_b->node == NULL) {
         return -1;
     }
 
     // Finally, compare node names
     return strcmp(entry_a->node->details->id, entry_b->node->details->id);
 }
 
 /*!
  * \internal
  * \brief Duplicate a notification entry
  *
  * \param[in] entry  Entry to duplicate
  *
  * \return Newly allocated duplicate of \p entry
  * \note It is the caller's responsibility to free the return value.
  */
 static notify_entry_t *
 dup_notify_entry(const notify_entry_t *entry)
 {
     notify_entry_t *dup = calloc(1, sizeof(notify_entry_t));
 
     CRM_ASSERT(dup != NULL);
     dup->rsc = entry->rsc;
     dup->node = entry->node;
     return dup;
 }
 
 /*!
  * \internal
  * \brief Given a list of nodes, create strings with node names
  *
  * \param[in]  list             List of nodes (as pcmk_node_t *)
  * \param[out] all_node_names   If not NULL, will be set to space-separated list
  *                              of the names of all nodes in \p list
  * \param[out] host_node_names  Same as \p all_node_names, except active
  *                              guest nodes will list the name of their host
  *
  * \note The caller is responsible for freeing the output argument values using
  *       \p g_string_free().
  */
 static void
 get_node_names(const GList *list, GString **all_node_names,
                GString **host_node_names)
 {
     if (all_node_names != NULL) {
         *all_node_names = NULL;
     }
     if (host_node_names != NULL) {
         *host_node_names = NULL;
     }
 
     for (const GList *iter = list; iter != NULL; iter = iter->next) {
         const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
 
         if (node->details->uname == NULL) {
             continue;
         }
 
         // Always add to list of all node names
         if (all_node_names != NULL) {
             pcmk__add_word(all_node_names, 1024, node->details->uname);
         }
 
         // Add to host node name list if appropriate
         if (host_node_names != NULL) {
             if (pcmk__is_guest_or_bundle_node(node)
                 && (node->details->remote_rsc->container->running_on != NULL)) {
                 node = pcmk__current_node(node->details->remote_rsc->container);
                 if (node->details->uname == NULL) {
                     continue;
                 }
             }
             pcmk__add_word(host_node_names, 1024, node->details->uname);
         }
     }
 
     if ((all_node_names != NULL) && (*all_node_names == NULL)) {
         *all_node_names = g_string_new(" ");
     }
     if ((host_node_names != NULL) && (*host_node_names == NULL)) {
         *host_node_names = g_string_new(" ");
     }
 }
 
 /*!
  * \internal
  * \brief Create strings of instance and node names from notification entries
  *
  * \param[in,out] list        List of notification entries (will be sorted here)
  * \param[out]    rsc_names   If not NULL, will be set to space-separated list
  *                            of clone instances from \p list
  * \param[out]    node_names  If not NULL, will be set to space-separated list
  *                            of node names from \p list
  *
  * \return (Possibly new) head of sorted \p list
  * \note The caller is responsible for freeing the output argument values using
  *       \p g_list_free_full() and \p g_string_free().
  */
 static GList *
 notify_entries_to_strings(GList *list, GString **rsc_names,
                           GString **node_names)
 {
     const char *last_rsc_id = NULL;
 
     // Initialize output lists to NULL
     if (rsc_names != NULL) {
         *rsc_names = NULL;
     }
     if (node_names != NULL) {
         *node_names = NULL;
     }
 
     // Sort input list for user-friendliness (and ease of filtering duplicates)
     list = g_list_sort(list, compare_notify_entries);
 
     for (GList *gIter = list; gIter != NULL; gIter = gIter->next) {
         notify_entry_t *entry = (notify_entry_t *) gIter->data;
 
         // Entry must have a resource (with ID)
         CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL)
                        && (entry->rsc->id != NULL));
         if ((entry == NULL) || (entry->rsc == NULL)
             || (entry->rsc->id == NULL)) {
             continue;
         }
 
         // Entry must have a node unless listing inactive resources
         CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL));
         if ((node_names != NULL) && (entry->node == NULL)) {
             continue;
         }
 
         // Don't add duplicates of a particular clone instance
         if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) {
             continue;
         }
         last_rsc_id = entry->rsc->id;
 
         if (rsc_names != NULL) {
             pcmk__add_word(rsc_names, 1024, entry->rsc->id);
         }
         if ((node_names != NULL) && (entry->node->details->uname != NULL)) {
             pcmk__add_word(node_names, 1024, entry->node->details->uname);
         }
     }
 
     // If there are no entries, return "empty" lists
     if ((rsc_names != NULL) && (*rsc_names == NULL)) {
         *rsc_names = g_string_new(" ");
     }
     if ((node_names != NULL) && (*node_names == NULL)) {
         *node_names = g_string_new(" ");
     }
 
     return list;
 }
 
 /*!
  * \internal
  * \brief Copy a meta-attribute into a notify action
  *
  * \param[in]     key        Name of meta-attribute to copy
  * \param[in]     value      Value of meta-attribute to copy
  * \param[in,out] user_data  Notify action to copy into
  */
 static void
 copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk_action_t *notify = (pcmk_action_t *) user_data;
 
     /* Any existing meta-attributes (for example, the action timeout) are for
      * the notify action itself, so don't override those.
      */
     if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) {
         return;
     }
 
     pcmk__insert_dup(notify->meta, (const char *) key, (const char *) value);
 }
 
 static void
 add_notify_data_to_action_meta(const notify_data_t *n_data,
                                pcmk_action_t *action)
 {
     for (const GSList *item = n_data->keys; item; item = item->next) {
         const pcmk_nvpair_t *nvpair = (const pcmk_nvpair_t *) item->data;
 
         pcmk__insert_meta(action, nvpair->name, nvpair->value);
     }
 }
 
 /*!
  * \internal
  * \brief Create a new notify pseudo-action for a clone resource
  *
  * \param[in,out] rsc           Clone resource that notification is for
  * \param[in]     action        Action to use in notify action key
  * \param[in]     notif_action  PCMK_ACTION_NOTIFY or PCMK_ACTION_NOTIFIED
  * \param[in]     notif_type    "pre", "post", "confirmed-pre", "confirmed-post"
  *
  * \return Newly created notify pseudo-action
  */
 static pcmk_action_t *
 new_notify_pseudo_action(pcmk_resource_t *rsc, const pcmk_action_t *action,
                          const char *notif_action, const char *notif_type)
 {
     pcmk_action_t *notify = NULL;
 
     notify = custom_action(rsc,
                            pcmk__notify_key(rsc->id, notif_type, action->task),
                            notif_action, NULL,
                            pcmk_is_set(action->flags, pcmk_action_optional),
                            rsc->cluster);
     pcmk__set_action_flags(notify, pcmk_action_pseudo);
     pcmk__insert_meta(notify, "notify_key_type", notif_type);
     pcmk__insert_meta(notify, "notify_key_operation", action->task);
     return notify;
 }
 
 /*!
  * \internal
  * \brief Create a new notify action for a clone instance
  *
  * \param[in,out] rsc          Clone instance that notification is for
  * \param[in]     node         Node that notification is for
  * \param[in,out] op           Action that notification is for
  * \param[in,out] notify_done  Parent pseudo-action for notifications complete
  * \param[in]     n_data       Notification values to add to action meta-data
  *
  * \return Newly created notify action
  */
 static pcmk_action_t *
 new_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
                   pcmk_action_t *op, pcmk_action_t *notify_done,
                   const notify_data_t *n_data)
 {
     char *key = NULL;
     pcmk_action_t *notify_action = NULL;
     const char *value = NULL;
     const char *task = NULL;
     const char *skip_reason = NULL;
 
     CRM_CHECK((rsc != NULL) && (node != NULL), return NULL);
 
     // Ensure we have all the info we need
     if (op == NULL) {
         skip_reason = "no action";
     } else if (notify_done == NULL) {
         skip_reason = "no parent notification";
     } else if (!node->details->online) {
         skip_reason = "node offline";
     } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
         skip_reason = "original action not runnable";
     }
     if (skip_reason != NULL) {
         pcmk__rsc_trace(rsc, "Skipping notify action for %s on %s: %s",
                         rsc->id, pcmk__node_name(node), skip_reason);
         return NULL;
     }
 
     value = g_hash_table_lookup(op->meta, "notify_type");     // "pre" or "post"
     task = g_hash_table_lookup(op->meta, "notify_operation"); // original action
 
     pcmk__rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)",
                     rsc->id, pcmk__node_name(node), value, task);
 
     // Create the notify action
     key = pcmk__notify_key(rsc->id, value, task);
     notify_action = custom_action(rsc, key, op->task, node,
                                   pcmk_is_set(op->flags, pcmk_action_optional),
                                   rsc->cluster);
 
     // Add meta-data to notify action
     g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action);
     add_notify_data_to_action_meta(n_data, notify_action);
 
     // Order notify after original action and before parent notification
     order_actions(op, notify_action, pcmk__ar_ordered);
     order_actions(notify_action, notify_done, pcmk__ar_ordered);
     return notify_action;
 }
 
 /*!
  * \internal
  * \brief Create a new "post-" notify action for a clone instance
  *
  * \param[in,out] rsc     Clone instance that notification is for
  * \param[in]     node    Node that notification is for
  * \param[in,out] n_data  Notification values to add to action meta-data
  */
 static void
 new_post_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
                        notify_data_t *n_data)
 {
     pcmk_action_t *notify = NULL;
 
     CRM_ASSERT(n_data != NULL);
 
     // Create the "post-" notify action for specified instance
     notify = new_notify_action(rsc, node, n_data->post, n_data->post_done,
                                n_data);
     if (notify != NULL) {
-        notify->priority = INFINITY;
+        notify->priority = PCMK_SCORE_INFINITY;
     }
 
     // Order recurring monitors after all "post-" notifications complete
     if (n_data->post_done == NULL) {
         return;
     }
     for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *mon = (pcmk_action_t *) iter->data;
         const char *interval_ms_s = NULL;
 
         interval_ms_s = g_hash_table_lookup(mon->meta, PCMK_META_INTERVAL);
         if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)
             || pcmk__str_eq(mon->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
             continue; // Not a recurring monitor
         }
         order_actions(n_data->post_done, mon, pcmk__ar_ordered);
     }
 }
 
 /*!
  * \internal
  * \brief Create and order notification pseudo-actions for a clone action
  *
  * In addition to the actual notify actions needed for each clone instance,
  * clone notifications also require pseudo-actions to provide ordering points
  * in the notification process. This creates the notification data, along with
  * appropriate pseudo-actions and their orderings.
  *
  * For example, the ordering sequence for starting a clone is:
  *
  *     "pre-" notify pseudo-action for clone
  *     -> "pre-" notify actions for each clone instance
  *     -> "pre-" notifications complete pseudo-action for clone
  *     -> start actions for each clone instance
  *     -> "started" pseudo-action for clone
  *     -> "post-" notify pseudo-action for clone
  *     -> "post-" notify actions for each clone instance
  *     -> "post-" notifications complete pseudo-action for clone
  *
  * \param[in,out] rsc       Clone that notifications are for
  * \param[in]     task      Name of action that notifications are for
  * \param[in,out] action    If not NULL, create a "pre-" pseudo-action ordered
  *                          before a "pre-" complete pseudo-action, ordered
  *                          before this action
  * \param[in,out] complete  If not NULL, create a "post-" pseudo-action ordered
  *                          after this action, and a "post-" complete
  *                          pseudo-action ordered after that
  *
  * \return Newly created notification data
  */
 notify_data_t *
 pe__action_notif_pseudo_ops(pcmk_resource_t *rsc, const char *task,
                             pcmk_action_t *action, pcmk_action_t *complete)
 {
     notify_data_t *n_data = NULL;
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_notify)) {
         return NULL;
     }
 
     n_data = calloc(1, sizeof(notify_data_t));
     CRM_ASSERT(n_data != NULL);
 
     n_data->action = task;
 
     if (action != NULL) { // Need "pre-" pseudo-actions
 
         // Create "pre-" notify pseudo-action for clone
         n_data->pre = new_notify_pseudo_action(rsc, action, PCMK_ACTION_NOTIFY,
                                                "pre");
         pcmk__set_action_flags(n_data->pre, pcmk_action_runnable);
         pcmk__insert_meta(n_data->pre, "notify_type", "pre");
         pcmk__insert_meta(n_data->pre, "notify_operation", n_data->action);
 
         // Create "pre-" notifications complete pseudo-action for clone
         n_data->pre_done = new_notify_pseudo_action(rsc, action,
                                                     PCMK_ACTION_NOTIFIED,
                                                     "confirmed-pre");
         pcmk__set_action_flags(n_data->pre_done, pcmk_action_runnable);
         pcmk__insert_meta(n_data->pre_done, "notify_type", "pre");
         pcmk__insert_meta(n_data->pre_done, "notify_operation", n_data->action);
 
         // Order "pre-" -> "pre-" complete -> original action
         order_actions(n_data->pre, n_data->pre_done, pcmk__ar_ordered);
         order_actions(n_data->pre_done, action, pcmk__ar_ordered);
     }
 
     if (complete != NULL) { // Need "post-" pseudo-actions
 
         // Create "post-" notify pseudo-action for clone
         n_data->post = new_notify_pseudo_action(rsc, complete,
                                                 PCMK_ACTION_NOTIFY, "post");
-        n_data->post->priority = INFINITY;
+        n_data->post->priority = PCMK_SCORE_INFINITY;
         if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
             pcmk__set_action_flags(n_data->post, pcmk_action_runnable);
         } else {
             pcmk__clear_action_flags(n_data->post, pcmk_action_runnable);
         }
         pcmk__insert_meta(n_data->post, "notify_type", "post");
         pcmk__insert_meta(n_data->post, "notify_operation", n_data->action);
 
         // Create "post-" notifications complete pseudo-action for clone
         n_data->post_done = new_notify_pseudo_action(rsc, complete,
                                                      PCMK_ACTION_NOTIFIED,
                                                      "confirmed-post");
-        n_data->post_done->priority = INFINITY;
+        n_data->post_done->priority = PCMK_SCORE_INFINITY;
         if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
             pcmk__set_action_flags(n_data->post_done, pcmk_action_runnable);
         } else {
             pcmk__clear_action_flags(n_data->post_done, pcmk_action_runnable);
         }
         pcmk__insert_meta(n_data->post_done, "notify_type", "post");
         pcmk__insert_meta(n_data->post_done,
                           "notify_operation", n_data->action);
 
         // Order original action complete -> "post-" -> "post-" complete
         order_actions(complete, n_data->post, pcmk__ar_first_implies_then);
         order_actions(n_data->post, n_data->post_done,
                       pcmk__ar_first_implies_then);
     }
 
     // If we created both, order "pre-" complete -> "post-"
     if ((action != NULL) && (complete != NULL)) {
         order_actions(n_data->pre_done, n_data->post, pcmk__ar_ordered);
     }
     return n_data;
 }
 
 /*!
  * \internal
  * \brief Create a new notification entry
  *
  * \param[in] rsc   Resource for notification
  * \param[in] node  Node for notification
  *
  * \return Newly allocated notification entry
  * \note The caller is responsible for freeing the return value.
  */
 static notify_entry_t *
 new_notify_entry(const pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     notify_entry_t *entry = calloc(1, sizeof(notify_entry_t));
 
     CRM_ASSERT(entry != NULL);
     entry->rsc = rsc;
     entry->node = node;
     return entry;
 }
 
 /*!
  * \internal
  * \brief Add notification data for resource state and optionally actions
  *
  * \param[in]     rsc       Clone or clone instance being notified
  * \param[in]     activity  Whether to add notification entries for actions
  * \param[in,out] n_data    Notification data for clone
  */
 static void
 collect_resource_data(const pcmk_resource_t *rsc, bool activity,
                       notify_data_t *n_data)
 {
     const GList *iter = NULL;
     notify_entry_t *entry = NULL;
     const pcmk_node_t *node = NULL;
 
     if (n_data == NULL) {
         return;
     }
 
     if (n_data->allowed_nodes == NULL) {
         n_data->allowed_nodes = rsc->allowed_nodes;
     }
 
     // If this is a clone, call recursively for each instance
     if (rsc->children != NULL) {
         for (iter = rsc->children; iter != NULL; iter = iter->next) {
             const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
 
             collect_resource_data(child, activity, n_data);
         }
         return;
     }
 
     // This is a notification for a single clone instance
 
     if (rsc->running_on != NULL) {
         node = rsc->running_on->data; // First is sufficient
     }
     entry = new_notify_entry(rsc, node);
 
     // Add notification indicating the resource state
     switch (rsc->role) {
         case pcmk_role_stopped:
             n_data->inactive = g_list_prepend(n_data->inactive, entry);
             break;
 
         case pcmk_role_started:
             n_data->active = g_list_prepend(n_data->active, entry);
             break;
 
         case pcmk_role_unpromoted:
             n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry);
             n_data->active = g_list_prepend(n_data->active,
                                             dup_notify_entry(entry));
             break;
 
         case pcmk_role_promoted:
             n_data->promoted = g_list_prepend(n_data->promoted, entry);
             n_data->active = g_list_prepend(n_data->active,
                                             dup_notify_entry(entry));
             break;
 
         default:
             pcmk__sched_err("Resource %s role on %s (%s) is not supported for "
                             "notifications (bug?)",
                             rsc->id, pcmk__node_name(node),
                             pcmk_role_text(rsc->role));
             free(entry);
             break;
     }
 
     if (!activity) {
         return;
     }
 
     // Add notification entries for each of the resource's actions
     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
         const pcmk_action_t *op = (const pcmk_action_t *) iter->data;
 
         if (!pcmk_is_set(op->flags, pcmk_action_optional)
             && (op->node != NULL)) {
             enum action_tasks task = pcmk_parse_action(op->task);
 
             if ((task == pcmk_action_stop) && op->node->details->unclean) {
                 // Create anyway (additional noise if node can't be fenced)
             } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
                 continue;
             }
 
             entry = new_notify_entry(rsc, op->node);
 
             switch (task) {
                 case pcmk_action_start:
                     n_data->start = g_list_prepend(n_data->start, entry);
                     break;
                 case pcmk_action_stop:
                     n_data->stop = g_list_prepend(n_data->stop, entry);
                     break;
                 case pcmk_action_promote:
                     n_data->promote = g_list_prepend(n_data->promote, entry);
                     break;
                 case pcmk_action_demote:
                     n_data->demote = g_list_prepend(n_data->demote, entry);
                     break;
                 default:
                     free(entry);
                     break;
             }
         }
     }
 }
 
 // For (char *) value
 #define add_notify_env(n_data, key, value) do {                         \
          n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value);  \
     } while (0)
 
 // For (GString *) value
 #define add_notify_env_gs(n_data, key, value) do {                      \
          n_data->keys = pcmk_prepend_nvpair(n_data->keys, key,          \
                                             (const char *) value->str); \
     } while (0)
 
 // For (GString *) value
 #define add_notify_env_free_gs(n_data, key, value) do {                 \
          n_data->keys = pcmk_prepend_nvpair(n_data->keys, key,          \
                                             (const char *) value->str); \
          g_string_free(value, TRUE); value = NULL;                      \
     } while (0)
 
 /*!
  * \internal
  * \brief Create notification name/value pairs from structured data
  *
  * \param[in]     rsc       Resource that notification is for
  * \param[in,out] n_data    Notification data
  */
 static void
 add_notif_keys(const pcmk_resource_t *rsc, notify_data_t *n_data)
 {
     bool required = false; // Whether to make notify actions required
     GString *rsc_list = NULL;
     GString *node_list = NULL;
     GString *metal_list = NULL;
     const char *source = NULL;
     GList *nodes = NULL;
 
     n_data->stop = notify_entries_to_strings(n_data->stop,
                                              &rsc_list, &node_list);
     if ((strcmp(" ", (const char *) rsc_list->str) != 0)
         && pcmk__str_eq(n_data->action, PCMK_ACTION_STOP, pcmk__str_none)) {
         required = true;
     }
     add_notify_env_free_gs(n_data, "notify_stop_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_stop_uname", node_list);
 
     if ((n_data->start != NULL)
         && pcmk__str_eq(n_data->action, PCMK_ACTION_START, pcmk__str_none)) {
         required = true;
     }
     n_data->start = notify_entries_to_strings(n_data->start,
                                               &rsc_list, &node_list);
     add_notify_env_free_gs(n_data, "notify_start_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_start_uname", node_list);
 
     if ((n_data->demote != NULL)
         && pcmk__str_eq(n_data->action, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
         required = true;
     }
     n_data->demote = notify_entries_to_strings(n_data->demote,
                                                &rsc_list, &node_list);
     add_notify_env_free_gs(n_data, "notify_demote_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_demote_uname", node_list);
 
     if ((n_data->promote != NULL)
         && pcmk__str_eq(n_data->action, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
         required = true;
     }
     n_data->promote = notify_entries_to_strings(n_data->promote,
                                                 &rsc_list, &node_list);
     add_notify_env_free_gs(n_data, "notify_promote_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_promote_uname", node_list);
 
     n_data->active = notify_entries_to_strings(n_data->active,
                                                &rsc_list, &node_list);
     add_notify_env_free_gs(n_data, "notify_active_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_active_uname", node_list);
 
     n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted,
                                                    &rsc_list, &node_list);
     add_notify_env_gs(n_data, "notify_unpromoted_resource", rsc_list);
     add_notify_env_gs(n_data, "notify_unpromoted_uname", node_list);
 
     // Deprecated: kept for backward compatibility with older resource agents
     add_notify_env_free_gs(n_data, "notify_slave_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_slave_uname", node_list);
 
     n_data->promoted = notify_entries_to_strings(n_data->promoted,
                                                  &rsc_list, &node_list);
     add_notify_env_gs(n_data, "notify_promoted_resource", rsc_list);
     add_notify_env_gs(n_data, "notify_promoted_uname", node_list);
 
     // Deprecated: kept for backward compatibility with older resource agents
     add_notify_env_free_gs(n_data, "notify_master_resource", rsc_list);
     add_notify_env_free_gs(n_data, "notify_master_uname", node_list);
 
     n_data->inactive = notify_entries_to_strings(n_data->inactive,
                                                  &rsc_list, NULL);
     add_notify_env_free_gs(n_data, "notify_inactive_resource", rsc_list);
 
     nodes = g_hash_table_get_values(n_data->allowed_nodes);
     if (!pcmk__is_daemon) {
         /* For display purposes, sort the node list, for consistent
          * regression test output (while avoiding the performance hit
          * for the live cluster).
          */
         nodes = g_list_sort(nodes, pe__cmp_node_name);
     }
     get_node_names(nodes, &node_list, NULL);
     add_notify_env_free_gs(n_data, "notify_available_uname", node_list);
     g_list_free(nodes);
 
     source = g_hash_table_lookup(rsc->meta,
                                  PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
     if (pcmk__str_eq(PCMK_VALUE_HOST, source, pcmk__str_none)) {
         get_node_names(rsc->cluster->nodes, &node_list, &metal_list);
         add_notify_env_free_gs(n_data, "notify_all_hosts", metal_list);
     } else {
         get_node_names(rsc->cluster->nodes, &node_list, NULL);
     }
     add_notify_env_free_gs(n_data, "notify_all_uname", node_list);
 
     if (required && (n_data->pre != NULL)) {
         pcmk__clear_action_flags(n_data->pre, pcmk_action_optional);
         pcmk__clear_action_flags(n_data->pre_done, pcmk_action_optional);
     }
 
     if (required && (n_data->post != NULL)) {
         pcmk__clear_action_flags(n_data->post, pcmk_action_optional);
         pcmk__clear_action_flags(n_data->post_done, pcmk_action_optional);
     }
 }
 
 /*
  * \internal
  * \brief Find any remote connection start relevant to an action
  *
  * \param[in] action  Action to check
  *
  * \return If action is behind a remote connection, connection's start
  */
 static pcmk_action_t *
 find_remote_start(pcmk_action_t *action)
 {
     if ((action != NULL) && (action->node != NULL)) {
         pcmk_resource_t *remote_rsc = action->node->details->remote_rsc;
 
         if (remote_rsc != NULL) {
             return find_first_action(remote_rsc->actions, NULL,
                                      PCMK_ACTION_START,
                                      NULL);
         }
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Create notify actions, and add notify data to original actions
  *
  * \param[in,out] rsc     Clone or clone instance that notification is for
  * \param[in,out] n_data  Clone notification data for some action
  */
 static void
 create_notify_actions(pcmk_resource_t *rsc, notify_data_t *n_data)
 {
     GList *iter = NULL;
     pcmk_action_t *stop = NULL;
     pcmk_action_t *start = NULL;
     enum action_tasks task = pcmk_parse_action(n_data->action);
 
     // If this is a clone, call recursively for each instance
     if (rsc->children != NULL) {
         g_list_foreach(rsc->children, (GFunc) create_notify_actions, n_data);
         return;
     }
 
     // Add notification meta-attributes to original actions
     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
         pcmk_action_t *op = (pcmk_action_t *) iter->data;
 
         if (!pcmk_is_set(op->flags, pcmk_action_optional)
             && (op->node != NULL)) {
             switch (pcmk_parse_action(op->task)) {
                 case pcmk_action_start:
                 case pcmk_action_stop:
                 case pcmk_action_promote:
                 case pcmk_action_demote:
                     add_notify_data_to_action_meta(n_data, op);
                     break;
                 default:
                     break;
             }
         }
     }
 
     // Skip notify action itself if original action was not needed
     switch (task) {
         case pcmk_action_start:
             if (n_data->start == NULL) {
                 pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
                                 rsc->id, n_data->action);
                 return;
             }
             break;
 
         case pcmk_action_promote:
             if (n_data->promote == NULL) {
                 pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
                                 rsc->id, n_data->action);
                 return;
             }
             break;
 
         case pcmk_action_demote:
             if (n_data->demote == NULL) {
                 pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
                                 rsc->id, n_data->action);
                 return;
             }
             break;
 
         default:
             // We cannot do same for stop because it might be implied by fencing
             break;
     }
 
     pcmk__rsc_trace(rsc, "Creating notify actions for %s %s",
                     rsc->id, n_data->action);
 
     // Create notify actions for stop or demote
     if ((rsc->role != pcmk_role_stopped)
         && ((task == pcmk_action_stop) || (task == pcmk_action_demote))) {
 
         stop = find_first_action(rsc->actions, NULL, PCMK_ACTION_STOP, NULL);
 
         for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
             pcmk_node_t *current_node = (pcmk_node_t *) iter->data;
 
             /* If a stop is a pseudo-action implied by fencing, don't try to
              * notify the node getting fenced.
              */
             if ((stop != NULL)
                 && pcmk_is_set(stop->flags, pcmk_action_pseudo)
                 && (current_node->details->unclean
                     || current_node->details->remote_requires_reset)) {
                 continue;
             }
 
             new_notify_action(rsc, current_node, n_data->pre,
                               n_data->pre_done, n_data);
 
             if ((task == pcmk_action_demote) || (stop == NULL)
                 || pcmk_is_set(stop->flags, pcmk_action_optional)) {
                 new_post_notify_action(rsc, current_node, n_data);
             }
         }
     }
 
     // Create notify actions for start or promote
     if ((rsc->next_role != pcmk_role_stopped)
         && ((task == pcmk_action_start) || (task == pcmk_action_promote))) {
 
         start = find_first_action(rsc->actions, NULL, PCMK_ACTION_START, NULL);
         if (start != NULL) {
             pcmk_action_t *remote_start = find_remote_start(start);
 
             if ((remote_start != NULL)
                 && !pcmk_is_set(remote_start->flags, pcmk_action_runnable)) {
                 /* Start and promote actions for a clone instance behind
                  * a Pacemaker Remote connection happen after the
                  * connection starts. If the connection start is blocked, do
                  * not schedule notifications for these actions.
                  */
                 return;
             }
         }
         if (rsc->allocated_to == NULL) {
             pcmk__sched_err("Next role '%s' but %s is not allocated",
                             pcmk_role_text(rsc->next_role), rsc->id);
             return;
         }
         if ((task != pcmk_action_start) || (start == NULL)
             || pcmk_is_set(start->flags, pcmk_action_optional)) {
 
             new_notify_action(rsc, rsc->allocated_to, n_data->pre,
                               n_data->pre_done, n_data);
         }
         new_post_notify_action(rsc, rsc->allocated_to, n_data);
     }
 }
 
 /*!
  * \internal
  * \brief Create notification data and actions for one clone action
  *
  * \param[in,out] rsc     Clone resource that notification is for
  * \param[in,out] n_data  Clone notification data for some action
  */
 void
 pe__create_action_notifications(pcmk_resource_t *rsc, notify_data_t *n_data)
 {
     if ((rsc == NULL) || (n_data == NULL)) {
         return;
     }
     collect_resource_data(rsc, true, n_data);
     add_notif_keys(rsc, n_data);
     create_notify_actions(rsc, n_data);
 }
 
 /*!
  * \internal
  * \brief Free notification data for one action
  *
  * \param[in,out] n_data  Notification data to free
  */
 void
 pe__free_action_notification_data(notify_data_t *n_data)
 {
     if (n_data == NULL) {
         return;
     }
     g_list_free_full(n_data->stop, free);
     g_list_free_full(n_data->start, free);
     g_list_free_full(n_data->demote, free);
     g_list_free_full(n_data->promote, free);
     g_list_free_full(n_data->promoted, free);
     g_list_free_full(n_data->unpromoted, free);
     g_list_free_full(n_data->active, free);
     g_list_free_full(n_data->inactive, free);
     pcmk_free_nvpairs(n_data->keys);
     free(n_data);
 }
 
 /*!
  * \internal
  * \brief Order clone "notifications complete" pseudo-action after fencing
  *
  * If a stop action is implied by fencing, the usual notification pseudo-actions
  * will not be sufficient to order things properly, or even create all needed
  * notifications if the clone is also stopping on another node, and another
  * clone is ordered after it. This function creates new notification
  * pseudo-actions relative to the fencing to ensure everything works properly.
  *
  * \param[in]     stop        Stop action implied by fencing
  * \param[in,out] rsc         Clone resource that notification is for
  * \param[in,out] stonith_op  Fencing action that implies \p stop
  */
 void
 pe__order_notifs_after_fencing(const pcmk_action_t *stop, pcmk_resource_t *rsc,
                                pcmk_action_t *stonith_op)
 {
     notify_data_t *n_data;
 
     crm_info("Ordering notifications for implied %s after fencing", stop->uuid);
     n_data = pe__action_notif_pseudo_ops(rsc, PCMK_ACTION_STOP, NULL,
                                          stonith_op);
 
     if (n_data != NULL) {
         collect_resource_data(rsc, false, n_data);
         add_notify_env(n_data, "notify_stop_resource", rsc->id);
         add_notify_env(n_data, "notify_stop_uname", stop->node->details->uname);
         create_notify_actions(uber_parent(rsc), n_data);
         pe__free_action_notification_data(n_data);
     }
 }
diff --git a/lib/pengine/unpack.c b/lib/pengine/unpack.c
index 8bc1519d77..7fceea8916 100644
--- a/lib/pengine/unpack.c
+++ b/lib/pengine/unpack.c
@@ -1,5158 +1,5158 @@
 /*
  * 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 <crm_internal.h>
 
 #include <stdio.h>
 #include <string.h>
 #include <glib.h>
 #include <time.h>
 
 #include <crm/crm.h>
 #include <crm/services.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #include <crm/common/util.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 #include <pe_status_private.h>
 
 CRM_TRACE_INIT_DATA(pe_status);
 
 // A (parsed) resource action history entry
 struct action_history {
     pcmk_resource_t *rsc;       // Resource that history is for
     pcmk_node_t *node;        // Node that history is for
     xmlNode *xml;             // History entry XML
 
     // Parsed from entry XML
     const char *id;           // XML ID of history entry
     const char *key;          // Operation key of action
     const char *task;         // Action name
     const char *exit_reason;  // Exit reason given for result
     guint interval_ms;        // Action interval
     int call_id;              // Call ID of action
     int expected_exit_status; // Expected exit status of action
     int exit_status;          // Actual exit status of action
     int execution_status;     // Execution status of action
 };
 
 /* This uses pcmk__set_flags_as()/pcmk__clear_flags_as() directly rather than
  * use pcmk__set_scheduler_flags()/pcmk__clear_scheduler_flags() so that the
  * flag is stringified more readably in log messages.
  */
 #define set_config_flag(scheduler, option, flag) do {                         \
         GHashTable *config_hash = (scheduler)->config_hash;                   \
         const char *scf_value = pcmk__cluster_option(config_hash, (option));  \
                                                                               \
         if (scf_value != NULL) {                                              \
             if (crm_is_true(scf_value)) {                                     \
                 (scheduler)->flags = pcmk__set_flags_as(__func__, __LINE__,   \
                                     LOG_TRACE, "Scheduler",                   \
                                     crm_system_name, (scheduler)->flags,      \
                                     (flag), #flag);                           \
             } else {                                                          \
                 (scheduler)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
                                     LOG_TRACE, "Scheduler",                   \
                                     crm_system_name, (scheduler)->flags,      \
                                     (flag), #flag);                           \
             }                                                                 \
         }                                                                     \
     } while(0)
 
 static void unpack_rsc_op(pcmk_resource_t *rsc, pcmk_node_t *node,
                           xmlNode *xml_op, xmlNode **last_failure,
                           enum action_fail_response *failed);
 static void determine_remote_online_status(pcmk_scheduler_t *scheduler,
                                            pcmk_node_t *this_node);
 static void add_node_attrs(const xmlNode *xml_obj, pcmk_node_t *node,
                            bool overwrite, pcmk_scheduler_t *scheduler);
 static void determine_online_status(const xmlNode *node_state,
                                     pcmk_node_t *this_node,
                                     pcmk_scheduler_t *scheduler);
 
 static void unpack_node_lrm(pcmk_node_t *node, const xmlNode *xml,
                             pcmk_scheduler_t *scheduler);
 
 
 static gboolean
 is_dangling_guest_node(pcmk_node_t *node)
 {
     /* we are looking for a remote-node that was supposed to be mapped to a
      * container resource, but all traces of that container have disappeared 
      * from both the config and the status section. */
     if (pcmk__is_pacemaker_remote_node(node)
         && (node->details->remote_rsc != NULL)
         && (node->details->remote_rsc->container == NULL)
         && pcmk_is_set(node->details->remote_rsc->flags,
                        pcmk_rsc_removed_filler)) {
         return TRUE;
     }
 
     return FALSE;
 }
 
 /*!
  * \brief Schedule a fence action for a node
  *
  * \param[in,out] scheduler       Scheduler data
  * \param[in,out] node            Node to fence
  * \param[in]     reason          Text description of why fencing is needed
  * \param[in]     priority_delay  Whether to consider
  *                                \c PCMK_OPT_PRIORITY_FENCING_DELAY
  */
 void
 pe_fence_node(pcmk_scheduler_t *scheduler, pcmk_node_t *node,
               const char *reason, bool priority_delay)
 {
     CRM_CHECK(node, return);
 
     /* A guest node is fenced by marking its container as failed */
     if (pcmk__is_guest_or_bundle_node(node)) {
         pcmk_resource_t *rsc = node->details->remote_rsc->container;
 
         if (!pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
             if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
                 crm_notice("Not fencing guest node %s "
                            "(otherwise would because %s): "
                            "its guest resource %s is unmanaged",
                            pcmk__node_name(node), reason, rsc->id);
             } else {
                 pcmk__sched_warn("Guest node %s will be fenced "
                                  "(by recovering its guest resource %s): %s",
                                  pcmk__node_name(node), rsc->id, reason);
 
                 /* We don't mark the node as unclean because that would prevent the
                  * node from running resources. We want to allow it to run resources
                  * in this transition if the recovery succeeds.
                  */
                 node->details->remote_requires_reset = TRUE;
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             }
         }
 
     } else if (is_dangling_guest_node(node)) {
         crm_info("Cleaning up dangling connection for guest node %s: "
                  "fencing was already done because %s, "
                  "and guest resource no longer exists",
                  pcmk__node_name(node), reason);
         pcmk__set_rsc_flags(node->details->remote_rsc,
                             pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
 
     } else if (pcmk__is_remote_node(node)) {
         pcmk_resource_t *rsc = node->details->remote_rsc;
 
         if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
             crm_notice("Not fencing remote node %s "
                        "(otherwise would because %s): connection is unmanaged",
                        pcmk__node_name(node), reason);
         } else if(node->details->remote_requires_reset == FALSE) {
             node->details->remote_requires_reset = TRUE;
             pcmk__sched_warn("Remote node %s %s: %s",
                              pcmk__node_name(node),
                              pe_can_fence(scheduler, node)? "will be fenced" : "is unclean",
                              reason);
         }
         node->details->unclean = TRUE;
         // No need to apply PCMK_OPT_PRIORITY_FENCING_DELAY for remote nodes
         pe_fence_op(node, NULL, TRUE, reason, FALSE, scheduler);
 
     } else if (node->details->unclean) {
         crm_trace("Cluster node %s %s because %s",
                   pcmk__node_name(node),
                   pe_can_fence(scheduler, node)? "would also be fenced" : "also is unclean",
                   reason);
 
     } else {
         pcmk__sched_warn("Cluster node %s %s: %s",
                          pcmk__node_name(node),
                          pe_can_fence(scheduler, node)? "will be fenced" : "is unclean",
                          reason);
         node->details->unclean = TRUE;
         pe_fence_op(node, NULL, TRUE, reason, priority_delay, scheduler);
     }
 }
 
 // @TODO xpaths can't handle templates, rules, or id-refs
 
 // nvpair with provides or requires set to unfencing
 #define XPATH_UNFENCING_NVPAIR PCMK_XE_NVPAIR           \
     "[(@" PCMK_XA_NAME "='" PCMK_STONITH_PROVIDES "'"   \
     "or @" PCMK_XA_NAME "='" PCMK_META_REQUIRES "') "   \
     "and @" PCMK_XA_VALUE "='" PCMK_VALUE_UNFENCING "']"
 
 // unfencing in rsc_defaults or any resource
 #define XPATH_ENABLE_UNFENCING \
     "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RESOURCES     \
     "//" PCMK_XE_META_ATTRIBUTES "/" XPATH_UNFENCING_NVPAIR             \
     "|/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION "/" PCMK_XE_RSC_DEFAULTS \
     "/" PCMK_XE_META_ATTRIBUTES "/" XPATH_UNFENCING_NVPAIR
 
 static void
 set_if_xpath(uint64_t flag, const char *xpath, pcmk_scheduler_t *scheduler)
 {
     xmlXPathObjectPtr result = NULL;
 
     if (!pcmk_is_set(scheduler->flags, flag)) {
         result = xpath_search(scheduler->input, xpath);
         if (result && (numXpathResults(result) > 0)) {
             pcmk__set_scheduler_flags(scheduler, flag);
         }
         freeXpathObject(result);
     }
 }
 
 gboolean
 unpack_config(xmlNode *config, pcmk_scheduler_t *scheduler)
 {
     const char *value = NULL;
     guint interval_ms = 0U;
     GHashTable *config_hash = pcmk__strkey_table(free, free);
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     scheduler->config_hash = config_hash;
 
     pe__unpack_dataset_nvpairs(config, PCMK_XE_CLUSTER_PROPERTY_SET, &rule_data,
                                config_hash, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS,
                                FALSE, scheduler);
 
     pcmk__validate_cluster_options(config_hash);
 
     set_config_flag(scheduler, PCMK_OPT_ENABLE_STARTUP_PROBES,
                     pcmk_sched_probe_resources);
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_probe_resources)) {
         crm_info("Startup probes: disabled (dangerous)");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_HAVE_WATCHDOG);
     if (value && crm_is_true(value)) {
         crm_info("Watchdog-based self-fencing will be performed via SBD if "
                  "fencing is required and " PCMK_OPT_STONITH_WATCHDOG_TIMEOUT
                  " is nonzero");
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_fencing);
     }
 
     /* Set certain flags via xpath here, so they can be used before the relevant
      * configuration sections are unpacked.
      */
     set_if_xpath(pcmk_sched_enable_unfencing, XPATH_ENABLE_UNFENCING,
                  scheduler);
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_STONITH_TIMEOUT);
     pcmk_parse_interval_spec(value, &interval_ms);
 
     if (interval_ms >= INT_MAX) {
         scheduler->stonith_timeout = INT_MAX;
     } else {
         scheduler->stonith_timeout = (int) interval_ms;
     }
     crm_debug("STONITH timeout: %d", scheduler->stonith_timeout);
 
     set_config_flag(scheduler, PCMK_OPT_STONITH_ENABLED,
                     pcmk_sched_fencing_enabled);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         crm_debug("STONITH of failed nodes is enabled");
     } else {
         crm_debug("STONITH of failed nodes is disabled");
     }
 
     scheduler->stonith_action = pcmk__cluster_option(config_hash,
                                                      PCMK_OPT_STONITH_ACTION);
     if (!strcmp(scheduler->stonith_action, PCMK__ACTION_POWEROFF)) {
         pcmk__warn_once(pcmk__wo_poweroff,
                         "Support for " PCMK_OPT_STONITH_ACTION " of "
                         "'" PCMK__ACTION_POWEROFF "' is deprecated and will be "
                         "removed in a future release "
                         "(use '" PCMK_ACTION_OFF "' instead)");
         scheduler->stonith_action = PCMK_ACTION_OFF;
     }
     crm_trace("STONITH will %s nodes", scheduler->stonith_action);
 
     set_config_flag(scheduler, PCMK_OPT_CONCURRENT_FENCING,
                     pcmk_sched_concurrent_fencing);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_concurrent_fencing)) {
         crm_debug("Concurrent fencing is enabled");
     } else {
         crm_debug("Concurrent fencing is disabled");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_PRIORITY_FENCING_DELAY);
     if (value) {
         pcmk_parse_interval_spec(value, &interval_ms);
         scheduler->priority_fencing_delay = (int) (interval_ms / 1000);
         crm_trace("Priority fencing delay is %ds",
                   scheduler->priority_fencing_delay);
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ALL_RESOURCES,
                     pcmk_sched_stop_all);
     crm_debug("Stop all active resources: %s",
               pcmk__flag_text(scheduler->flags, pcmk_sched_stop_all));
 
     set_config_flag(scheduler, PCMK_OPT_SYMMETRIC_CLUSTER,
                     pcmk_sched_symmetric_cluster);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) {
         crm_debug("Cluster is symmetric" " - resources can run anywhere by default");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_NO_QUORUM_POLICY);
 
     if (pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_ignore;
 
     } else if (pcmk__str_eq(value, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_freeze;
 
     } else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
         scheduler->no_quorum_policy = pcmk_no_quorum_demote;
 
     } else if (pcmk__str_eq(value, PCMK_VALUE_FENCE_LEGACY, pcmk__str_casei)) {
         if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
             int do_panic = 0;
 
             crm_element_value_int(scheduler->input, PCMK_XA_NO_QUORUM_PANIC,
                                   &do_panic);
             if (do_panic || pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
                 scheduler->no_quorum_policy = pcmk_no_quorum_fence;
             } else {
                 crm_notice("Resetting " PCMK_OPT_NO_QUORUM_POLICY
                            " to 'stop': cluster has never had quorum");
                 scheduler->no_quorum_policy = pcmk_no_quorum_stop;
             }
         } else {
             pcmk__config_err("Resetting " PCMK_OPT_NO_QUORUM_POLICY
                              " to 'stop' because fencing is disabled");
             scheduler->no_quorum_policy = pcmk_no_quorum_stop;
         }
 
     } else {
         scheduler->no_quorum_policy = pcmk_no_quorum_stop;
     }
 
     switch (scheduler->no_quorum_policy) {
         case pcmk_no_quorum_freeze:
             crm_debug("On loss of quorum: Freeze resources");
             break;
         case pcmk_no_quorum_stop:
             crm_debug("On loss of quorum: Stop ALL resources");
             break;
         case pcmk_no_quorum_demote:
             crm_debug("On loss of quorum: "
                       "Demote promotable resources and stop other resources");
             break;
         case pcmk_no_quorum_fence:
             crm_notice("On loss of quorum: Fence all remaining nodes");
             break;
         case pcmk_no_quorum_ignore:
             crm_notice("On loss of quorum: Ignore");
             break;
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ORPHAN_RESOURCES,
                     pcmk_sched_stop_removed_resources);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_stop_removed_resources)) {
         crm_trace("Orphan resources are stopped");
     } else {
         crm_trace("Orphan resources are ignored");
     }
 
     set_config_flag(scheduler, PCMK_OPT_STOP_ORPHAN_ACTIONS,
                     pcmk_sched_cancel_removed_actions);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_cancel_removed_actions)) {
         crm_trace("Orphan resource actions are stopped");
     } else {
         crm_trace("Orphan resource actions are ignored");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK__OPT_REMOVE_AFTER_STOP);
     if (value != NULL) {
         if (crm_is_true(value)) {
             pcmk__set_scheduler_flags(scheduler, pcmk_sched_remove_after_stop);
 #ifndef PCMK__COMPAT_2_0
             pcmk__warn_once(pcmk__wo_remove_after,
                             "Support for the " PCMK__OPT_REMOVE_AFTER_STOP
                             " cluster property is deprecated and will be "
                             "removed in a future release");
 #endif
         } else {
             pcmk__clear_scheduler_flags(scheduler,
                                         pcmk_sched_remove_after_stop);
         }
     }
 
     set_config_flag(scheduler, PCMK_OPT_MAINTENANCE_MODE,
                     pcmk_sched_in_maintenance);
     crm_trace("Maintenance mode: %s",
               pcmk__flag_text(scheduler->flags, pcmk_sched_in_maintenance));
 
     set_config_flag(scheduler, PCMK_OPT_START_FAILURE_IS_FATAL,
                     pcmk_sched_start_failure_fatal);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_start_failure_fatal)) {
         crm_trace("Start failures are always fatal");
     } else {
         crm_trace("Start failures are handled by failcount");
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         set_config_flag(scheduler, PCMK_OPT_STARTUP_FENCING,
                         pcmk_sched_startup_fencing);
     }
     if (pcmk_is_set(scheduler->flags, pcmk_sched_startup_fencing)) {
         crm_trace("Unseen nodes will be fenced");
     } else {
         pcmk__warn_once(pcmk__wo_blind,
                         "Blind faith: not fencing unseen nodes");
     }
 
     pe__unpack_node_health_scores(scheduler);
 
     scheduler->placement_strategy =
         pcmk__cluster_option(config_hash, PCMK_OPT_PLACEMENT_STRATEGY);
     crm_trace("Placement strategy: %s", scheduler->placement_strategy);
 
     set_config_flag(scheduler, PCMK_OPT_SHUTDOWN_LOCK,
                     pcmk_sched_shutdown_lock);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         value = pcmk__cluster_option(config_hash, PCMK_OPT_SHUTDOWN_LOCK_LIMIT);
         pcmk_parse_interval_spec(value, &(scheduler->shutdown_lock));
         scheduler->shutdown_lock /= 1000;
         crm_trace("Resources will be locked to nodes that were cleanly "
                   "shut down (locks expire after %s)",
                   pcmk__readable_interval(scheduler->shutdown_lock));
     } else {
         crm_trace("Resources will not be locked to nodes that were cleanly "
                   "shut down");
     }
 
     value = pcmk__cluster_option(config_hash, PCMK_OPT_NODE_PENDING_TIMEOUT);
     pcmk_parse_interval_spec(value, &(scheduler->node_pending_timeout));
     scheduler->node_pending_timeout /= 1000;
     if (scheduler->node_pending_timeout == 0) {
         crm_trace("Do not fence pending nodes");
     } else {
         crm_trace("Fence pending nodes after %s",
                   pcmk__readable_interval(scheduler->node_pending_timeout
                                           * 1000));
     }
 
     return TRUE;
 }
 
 pcmk_node_t *
 pe_create_node(const char *id, const char *uname, const char *type,
                const char *score, pcmk_scheduler_t *scheduler)
 {
     pcmk_node_t *new_node = NULL;
 
     if (pe_find_node(scheduler->nodes, uname) != NULL) {
         pcmk__config_warn("More than one node entry has name '%s'", uname);
     }
 
     new_node = calloc(1, sizeof(pcmk_node_t));
     if (new_node == NULL) {
         pcmk__sched_err("Could not allocate memory for node %s", uname);
         return NULL;
     }
 
     new_node->weight = char2score(score);
     new_node->details = calloc(1, sizeof(struct pe_node_shared_s));
 
     if (new_node->details == NULL) {
         free(new_node);
         pcmk__sched_err("Could not allocate memory for node %s", uname);
         return NULL;
     }
 
     crm_trace("Creating node for entry %s/%s", uname, id);
     new_node->details->id = id;
     new_node->details->uname = uname;
     new_node->details->online = FALSE;
     new_node->details->shutdown = FALSE;
     new_node->details->rsc_discovery_enabled = TRUE;
     new_node->details->running_rsc = NULL;
     new_node->details->data_set = scheduler;
 
     if (pcmk__str_eq(type, PCMK_VALUE_MEMBER,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         new_node->details->type = pcmk_node_variant_cluster;
 
     } else if (pcmk__str_eq(type, PCMK_VALUE_REMOTE, pcmk__str_casei)) {
         new_node->details->type = pcmk_node_variant_remote;
         pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_remote_nodes);
 
     } else {
         /* @COMPAT 'ping' is the default for backward compatibility, but it
          * should be changed to 'member' at a compatibility break
          */
         if (!pcmk__str_eq(type, PCMK__VALUE_PING, pcmk__str_casei)) {
             pcmk__config_warn("Node %s has unrecognized type '%s', "
                               "assuming '" PCMK__VALUE_PING "'",
                               pcmk__s(uname, "without name"), type);
         }
         pcmk__warn_once(pcmk__wo_ping_node,
                         "Support for nodes of type '" PCMK__VALUE_PING "' "
                         "(such as %s) is deprecated and will be removed in a "
                         "future release",
                         pcmk__s(uname, "unnamed node"));
         new_node->details->type = node_ping;
     }
 
     new_node->details->attrs = pcmk__strkey_table(free, free);
 
     if (pcmk__is_pacemaker_remote_node(new_node)) {
         pcmk__insert_dup(new_node->details->attrs, CRM_ATTR_KIND, "remote");
     } else {
         pcmk__insert_dup(new_node->details->attrs, CRM_ATTR_KIND, "cluster");
     }
 
     new_node->details->utilization = pcmk__strkey_table(free, free);
     new_node->details->digest_cache = pcmk__strkey_table(free,
                                                           pe__free_digests);
 
     scheduler->nodes = g_list_insert_sorted(scheduler->nodes, new_node,
                                             pe__cmp_node_name);
     return new_node;
 }
 
 static const char *
 expand_remote_rsc_meta(xmlNode *xml_obj, xmlNode *parent, pcmk_scheduler_t *data)
 {
     xmlNode *attr_set = NULL;
     xmlNode *attr = NULL;
 
     const char *container_id = pcmk__xe_id(xml_obj);
     const char *remote_name = NULL;
     const char *remote_server = NULL;
     const char *remote_port = NULL;
     const char *connect_timeout = "60s";
     const char *remote_allow_migrate=NULL;
     const char *is_managed = NULL;
 
     for (attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
          attr_set = pcmk__xe_next(attr_set)) {
 
         if (!pcmk__xe_is(attr_set, PCMK_XE_META_ATTRIBUTES)) {
             continue;
         }
 
         for (attr = pcmk__xe_first_child(attr_set); attr != NULL;
              attr = pcmk__xe_next(attr)) {
             const char *value = crm_element_value(attr, PCMK_XA_VALUE);
             const char *name = crm_element_value(attr, PCMK_XA_NAME);
 
             if (name == NULL) { // Sanity
                 continue;
             }
 
             if (strcmp(name, PCMK_META_REMOTE_NODE) == 0) {
                 remote_name = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_ADDR) == 0) {
                 remote_server = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_PORT) == 0) {
                 remote_port = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_CONNECT_TIMEOUT) == 0) {
                 connect_timeout = value;
 
             } else if (strcmp(name, PCMK_META_REMOTE_ALLOW_MIGRATE) == 0) {
                 remote_allow_migrate = value;
 
             } else if (strcmp(name, PCMK_META_IS_MANAGED) == 0) {
                 is_managed = value;
             }
         }
     }
 
     if (remote_name == NULL) {
         return NULL;
     }
 
     if (pe_find_resource(data->resources, remote_name) != NULL) {
         return NULL;
     }
 
     pe_create_remote_xml(parent, remote_name, container_id,
                          remote_allow_migrate, is_managed,
                          connect_timeout, remote_server, remote_port);
     return remote_name;
 }
 
 static void
 handle_startup_fencing(pcmk_scheduler_t *scheduler, pcmk_node_t *new_node)
 {
     if ((new_node->details->type == pcmk_node_variant_remote)
         && (new_node->details->remote_rsc == NULL)) {
         /* Ignore fencing for remote nodes that don't have a connection resource
          * associated with them. This happens when remote node entries get left
          * in the nodes section after the connection resource is removed.
          */
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_startup_fencing)) {
         // All nodes are unclean until we've seen their status entry
         new_node->details->unclean = TRUE;
 
     } else {
         // Blind faith ...
         new_node->details->unclean = FALSE;
     }
 
     /* We need to be able to determine if a node's status section
      * exists or not separate from whether the node is unclean. */
     new_node->details->unseen = TRUE;
 }
 
 gboolean
 unpack_nodes(xmlNode *xml_nodes, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
     pcmk_node_t *new_node = NULL;
     const char *id = NULL;
     const char *uname = NULL;
     const char *type = NULL;
     const char *score = NULL;
 
     for (xml_obj = pcmk__xe_first_child(xml_nodes); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (pcmk__xe_is(xml_obj, PCMK_XE_NODE)) {
             new_node = NULL;
 
             id = crm_element_value(xml_obj, PCMK_XA_ID);
             uname = crm_element_value(xml_obj, PCMK_XA_UNAME);
             type = crm_element_value(xml_obj, PCMK_XA_TYPE);
             score = crm_element_value(xml_obj, PCMK_XA_SCORE);
             crm_trace("Processing node %s/%s", uname, id);
 
             if (id == NULL) {
                 pcmk__config_err("Ignoring <" PCMK_XE_NODE
                                  "> entry in configuration without id");
                 continue;
             }
             new_node = pe_create_node(id, uname, type, score, scheduler);
 
             if (new_node == NULL) {
                 return FALSE;
             }
 
             handle_startup_fencing(scheduler, new_node);
 
             add_node_attrs(xml_obj, new_node, FALSE, scheduler);
 
             crm_trace("Done with node %s",
                       crm_element_value(xml_obj, PCMK_XA_UNAME));
         }
     }
 
     if (scheduler->localhost
         && (pe_find_node(scheduler->nodes, scheduler->localhost) == NULL)) {
         crm_info("Creating a fake local node");
         pe_create_node(scheduler->localhost, scheduler->localhost, NULL, 0,
                        scheduler);
     }
 
     return TRUE;
 }
 
 static void
 setup_container(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
 {
     const char *container_id = NULL;
 
     if (rsc->children) {
         g_list_foreach(rsc->children, (GFunc) setup_container, scheduler);
         return;
     }
 
     container_id = g_hash_table_lookup(rsc->meta, PCMK__META_CONTAINER);
     if (container_id && !pcmk__str_eq(container_id, rsc->id, pcmk__str_casei)) {
         pcmk_resource_t *container = pe_find_resource(scheduler->resources,
                                                       container_id);
 
         if (container) {
             rsc->container = container;
             pcmk__set_rsc_flags(container, pcmk_rsc_has_filler);
             container->fillers = g_list_append(container->fillers, rsc);
             pcmk__rsc_trace(rsc, "Resource %s's container is %s",
                             rsc->id, container_id);
         } else {
             pcmk__config_err("Resource %s: Unknown resource container (%s)",
                              rsc->id, container_id);
         }
     }
 }
 
 gboolean
 unpack_remote_nodes(xmlNode *xml_resources, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
 
     /* Create remote nodes and guest nodes from the resource configuration
      * before unpacking resources.
      */
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         const char *new_node_id = NULL;
 
         /* Check for remote nodes, which are defined by ocf:pacemaker:remote
          * primitives.
          */
         if (xml_contains_remote_node(xml_obj)) {
             new_node_id = pcmk__xe_id(xml_obj);
             /* The "pe_find_node" check is here to make sure we don't iterate over
              * an expanded node that has already been added to the node list. */
             if (new_node_id
                 && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                 crm_trace("Found remote node %s defined by resource %s",
                           new_node_id, pcmk__xe_id(xml_obj));
                 pe_create_node(new_node_id, new_node_id, PCMK_VALUE_REMOTE,
                                NULL, scheduler);
             }
             continue;
         }
 
         /* Check for guest nodes, which are defined by special meta-attributes
          * of a primitive of any type (for example, VirtualDomain or Xen).
          */
         if (pcmk__xe_is(xml_obj, PCMK_XE_PRIMITIVE)) {
             /* This will add an ocf:pacemaker:remote primitive to the
              * configuration for the guest node's connection, to be unpacked
              * later.
              */
             new_node_id = expand_remote_rsc_meta(xml_obj, xml_resources,
                                                  scheduler);
             if (new_node_id
                 && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                 crm_trace("Found guest node %s in resource %s",
                           new_node_id, pcmk__xe_id(xml_obj));
                 pe_create_node(new_node_id, new_node_id, PCMK_VALUE_REMOTE,
                                NULL, scheduler);
             }
             continue;
         }
 
         /* Check for guest nodes inside a group. Clones are currently not
          * supported as guest nodes.
          */
         if (pcmk__xe_is(xml_obj, PCMK_XE_GROUP)) {
             xmlNode *xml_obj2 = NULL;
             for (xml_obj2 = pcmk__xe_first_child(xml_obj); xml_obj2 != NULL;
                  xml_obj2 = pcmk__xe_next(xml_obj2)) {
 
                 new_node_id = expand_remote_rsc_meta(xml_obj2, xml_resources,
                                                      scheduler);
 
                 if (new_node_id
                     && (pe_find_node(scheduler->nodes, new_node_id) == NULL)) {
                     crm_trace("Found guest node %s in resource %s inside group %s",
                               new_node_id, pcmk__xe_id(xml_obj2),
                               pcmk__xe_id(xml_obj));
                     pe_create_node(new_node_id, new_node_id, PCMK_VALUE_REMOTE,
                                    NULL, scheduler);
                 }
             }
         }
     }
     return TRUE;
 }
 
 /* Call this after all the nodes and resources have been
  * unpacked, but before the status section is read.
  *
  * A remote node's online status is reflected by the state
  * of the remote node's connection resource. We need to link
  * the remote node to this connection resource so we can have
  * easy access to the connection resource during the scheduler calculations.
  */
 static void
 link_rsc2remotenode(pcmk_scheduler_t *scheduler, pcmk_resource_t *new_rsc)
 {
     pcmk_node_t *remote_node = NULL;
 
     if (new_rsc->is_remote_node == FALSE) {
         return;
     }
 
     if (pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         /* remote_nodes and remote_resources are not linked in quick location calculations */
         return;
     }
 
     remote_node = pe_find_node(scheduler->nodes, new_rsc->id);
     CRM_CHECK(remote_node != NULL, return);
 
     pcmk__rsc_trace(new_rsc, "Linking remote connection resource %s to %s",
                     new_rsc->id, pcmk__node_name(remote_node));
     remote_node->details->remote_rsc = new_rsc;
 
     if (new_rsc->container == NULL) {
         /* Handle start-up fencing for remote nodes (as opposed to guest nodes)
          * the same as is done for cluster nodes.
          */
         handle_startup_fencing(scheduler, remote_node);
 
     } else {
         /* pe_create_node() marks the new node as "remote" or "cluster"; now
          * that we know the node is a guest node, update it correctly.
          */
         pcmk__insert_dup(remote_node->details->attrs,
                          CRM_ATTR_KIND, "container");
     }
 }
 
 static void
 destroy_tag(gpointer data)
 {
     pcmk_tag_t *tag = data;
 
     if (tag) {
         free(tag->id);
         g_list_free_full(tag->refs, free);
         free(tag);
     }
 }
 
 /*!
  * \internal
  * \brief Parse configuration XML for resource information
  *
  * \param[in]     xml_resources  Top of resource configuration XML
  * \param[in,out] scheduler      Scheduler data
  *
  * \return TRUE
  *
  * \note unpack_remote_nodes() MUST be called before this, so that the nodes can
  *       be used when pe__unpack_resource() calls resource_location()
  */
 gboolean
 unpack_resources(const xmlNode *xml_resources, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
     GList *gIter = NULL;
 
     scheduler->template_rsc_sets = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_obj = pcmk__xe_first_child(xml_resources); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         pcmk_resource_t *new_rsc = NULL;
         const char *id = pcmk__xe_id(xml_obj);
 
         if (pcmk__str_empty(id)) {
             pcmk__config_err("Ignoring <%s> resource without ID",
                              xml_obj->name);
             continue;
         }
 
         if (pcmk__xe_is(xml_obj, PCMK_XE_TEMPLATE)) {
             if (g_hash_table_lookup_extended(scheduler->template_rsc_sets, id,
                                              NULL, NULL) == FALSE) {
                 /* Record the template's ID for the knowledge of its existence anyway. */
                 pcmk__insert_dup(scheduler->template_rsc_sets, id, NULL);
             }
             continue;
         }
 
         crm_trace("Unpacking <%s " PCMK_XA_ID "='%s'>", xml_obj->name, id);
         if (pe__unpack_resource(xml_obj, &new_rsc, NULL,
                                 scheduler) == pcmk_rc_ok) {
             scheduler->resources = g_list_append(scheduler->resources, new_rsc);
             pcmk__rsc_trace(new_rsc, "Added resource %s", new_rsc->id);
 
         } else {
             pcmk__config_err("Ignoring <%s> resource '%s' "
                              "because configuration is invalid",
                              xml_obj->name, id);
         }
     }
 
     for (gIter = scheduler->resources; gIter != NULL; gIter = gIter->next) {
         pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data;
 
         setup_container(rsc, scheduler);
         link_rsc2remotenode(scheduler, rsc);
     }
 
     scheduler->resources = g_list_sort(scheduler->resources,
                                       pe__cmp_rsc_priority);
     if (pcmk_is_set(scheduler->flags, pcmk_sched_location_only)) {
         /* Ignore */
 
     } else if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)
                && !pcmk_is_set(scheduler->flags, pcmk_sched_have_fencing)) {
 
         pcmk__config_err("Resource start-up disabled since no STONITH resources have been defined");
         pcmk__config_err("Either configure some or disable STONITH with the "
                          PCMK_OPT_STONITH_ENABLED " option");
         pcmk__config_err("NOTE: Clusters with shared data need STONITH to ensure data integrity");
     }
 
     return TRUE;
 }
 
 gboolean
 unpack_tags(xmlNode *xml_tags, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_tag = NULL;
 
     scheduler->tags = pcmk__strkey_table(free, destroy_tag);
 
     for (xml_tag = pcmk__xe_first_child(xml_tags); xml_tag != NULL;
          xml_tag = pcmk__xe_next(xml_tag)) {
 
         xmlNode *xml_obj_ref = NULL;
         const char *tag_id = pcmk__xe_id(xml_tag);
 
         if (!pcmk__xe_is(xml_tag, PCMK_XE_TAG)) {
             continue;
         }
 
         if (tag_id == NULL) {
             pcmk__config_err("Ignoring <%s> without " PCMK_XA_ID,
                              (const char *) xml_tag->name);
             continue;
         }
 
         for (xml_obj_ref = pcmk__xe_first_child(xml_tag); xml_obj_ref != NULL;
              xml_obj_ref = pcmk__xe_next(xml_obj_ref)) {
 
             const char *obj_ref = pcmk__xe_id(xml_obj_ref);
 
             if (!pcmk__xe_is(xml_obj_ref, PCMK_XE_OBJ_REF)) {
                 continue;
             }
 
             if (obj_ref == NULL) {
                 pcmk__config_err("Ignoring <%s> for tag '%s' without " PCMK_XA_ID,
                                  xml_obj_ref->name, tag_id);
                 continue;
             }
 
             if (add_tag_ref(scheduler->tags, tag_id, obj_ref) == FALSE) {
                 return FALSE;
             }
         }
     }
 
     return TRUE;
 }
 
 /* The ticket state section:
  * "/cib/status/tickets/ticket_state" */
 static gboolean
 unpack_ticket_state(xmlNode *xml_ticket, pcmk_scheduler_t *scheduler)
 {
     const char *ticket_id = NULL;
     const char *granted = NULL;
     const char *last_granted = NULL;
     const char *standby = NULL;
     xmlAttrPtr xIter = NULL;
 
     pcmk_ticket_t *ticket = NULL;
 
     ticket_id = pcmk__xe_id(xml_ticket);
     if (pcmk__str_empty(ticket_id)) {
         return FALSE;
     }
 
     crm_trace("Processing ticket state for %s", ticket_id);
 
     ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
     if (ticket == NULL) {
         ticket = ticket_new(ticket_id, scheduler);
         if (ticket == NULL) {
             return FALSE;
         }
     }
 
     for (xIter = xml_ticket->properties; xIter; xIter = xIter->next) {
         const char *prop_name = (const char *)xIter->name;
         const char *prop_value = pcmk__xml_attr_value(xIter);
 
         if (pcmk__str_eq(prop_name, PCMK_XA_ID, pcmk__str_none)) {
             continue;
         }
         pcmk__insert_dup(ticket->state, prop_name, prop_value);
     }
 
     granted = g_hash_table_lookup(ticket->state, PCMK__XA_GRANTED);
     if (granted && crm_is_true(granted)) {
         ticket->granted = TRUE;
         crm_info("We have ticket '%s'", ticket->id);
     } else {
         ticket->granted = FALSE;
         crm_info("We do not have ticket '%s'", ticket->id);
     }
 
     last_granted = g_hash_table_lookup(ticket->state, PCMK_XA_LAST_GRANTED);
     if (last_granted) {
         long long last_granted_ll;
 
         pcmk__scan_ll(last_granted, &last_granted_ll, 0LL);
         ticket->last_granted = (time_t) last_granted_ll;
     }
 
     standby = g_hash_table_lookup(ticket->state, PCMK_XA_STANDBY);
     if (standby && crm_is_true(standby)) {
         ticket->standby = TRUE;
         if (ticket->granted) {
             crm_info("Granted ticket '%s' is in standby-mode", ticket->id);
         }
     } else {
         ticket->standby = FALSE;
     }
 
     crm_trace("Done with ticket state for %s", ticket_id);
 
     return TRUE;
 }
 
 static gboolean
 unpack_tickets_state(xmlNode *xml_tickets, pcmk_scheduler_t *scheduler)
 {
     xmlNode *xml_obj = NULL;
 
     for (xml_obj = pcmk__xe_first_child(xml_tickets); xml_obj != NULL;
          xml_obj = pcmk__xe_next(xml_obj)) {
 
         if (!pcmk__xe_is(xml_obj, PCMK__XE_TICKET_STATE)) {
             continue;
         }
         unpack_ticket_state(xml_obj, scheduler);
     }
 
     return TRUE;
 }
 
 static void
 unpack_handle_remote_attrs(pcmk_node_t *this_node, const xmlNode *state,
                            pcmk_scheduler_t *scheduler)
 {
     const char *discovery = NULL;
     const xmlNode *attrs = NULL;
     pcmk_resource_t *rsc = NULL;
 
     if (!pcmk__xe_is(state, PCMK__XE_NODE_STATE)) {
         return;
     }
 
     if ((this_node == NULL) || !pcmk__is_pacemaker_remote_node(this_node)) {
         return;
     }
     crm_trace("Processing Pacemaker Remote node %s",
               pcmk__node_name(this_node));
 
     pcmk__scan_min_int(crm_element_value(state, PCMK__XA_NODE_IN_MAINTENANCE),
                        &(this_node->details->remote_maintenance), 0);
 
     rsc = this_node->details->remote_rsc;
     if (this_node->details->remote_requires_reset == FALSE) {
         this_node->details->unclean = FALSE;
         this_node->details->unseen = FALSE;
     }
     attrs = find_xml_node(state, PCMK__XE_TRANSIENT_ATTRIBUTES, FALSE);
     add_node_attrs(attrs, this_node, TRUE, scheduler);
 
     if (pe__shutdown_requested(this_node)) {
         crm_info("%s is shutting down", pcmk__node_name(this_node));
         this_node->details->shutdown = TRUE;
     }
 
     if (crm_is_true(pcmk__node_attr(this_node, PCMK_NODE_ATTR_STANDBY, NULL,
                                     pcmk__rsc_node_current))) {
         crm_info("%s is in standby mode", pcmk__node_name(this_node));
         this_node->details->standby = TRUE;
     }
 
     if (crm_is_true(pcmk__node_attr(this_node, PCMK_NODE_ATTR_MAINTENANCE, NULL,
                                     pcmk__rsc_node_current))
         || ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_managed))) {
         crm_info("%s is in maintenance mode", pcmk__node_name(this_node));
         this_node->details->maintenance = TRUE;
     }
 
     discovery = pcmk__node_attr(this_node,
                                 PCMK__NODE_ATTR_RESOURCE_DISCOVERY_ENABLED,
                                 NULL, pcmk__rsc_node_current);
     if ((discovery != NULL) && !crm_is_true(discovery)) {
         pcmk__warn_once(pcmk__wo_rdisc_enabled,
                         "Support for the "
                         PCMK__NODE_ATTR_RESOURCE_DISCOVERY_ENABLED
                         " node attribute is deprecated and will be removed"
                         " (and behave as 'true') in a future release.");
 
         if (pcmk__is_remote_node(this_node)
             && !pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
             pcmk__config_warn("Ignoring "
                               PCMK__NODE_ATTR_RESOURCE_DISCOVERY_ENABLED
                               " attribute on Pacemaker Remote node %s"
                               " because fencing is disabled",
                               pcmk__node_name(this_node));
         } else {
             /* This is either a remote node with fencing enabled, or a guest
              * node. We don't care whether fencing is enabled when fencing guest
              * nodes, because they are "fenced" by recovering their containing
              * resource.
              */
             crm_info("%s has resource discovery disabled",
                      pcmk__node_name(this_node));
             this_node->details->rsc_discovery_enabled = FALSE;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a cluster node's transient attributes
  *
  * \param[in]     state      CIB node state XML
  * \param[in,out] node       Cluster node whose attributes are being unpacked
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_transient_attributes(const xmlNode *state, pcmk_node_t *node,
                             pcmk_scheduler_t *scheduler)
 {
     const char *discovery = NULL;
     const xmlNode *attrs = find_xml_node(state, PCMK__XE_TRANSIENT_ATTRIBUTES,
                                          FALSE);
 
     add_node_attrs(attrs, node, TRUE, scheduler);
 
     if (crm_is_true(pcmk__node_attr(node, PCMK_NODE_ATTR_STANDBY, NULL,
                                     pcmk__rsc_node_current))) {
         crm_info("%s is in standby mode", pcmk__node_name(node));
         node->details->standby = TRUE;
     }
 
     if (crm_is_true(pcmk__node_attr(node, PCMK_NODE_ATTR_MAINTENANCE, NULL,
                                     pcmk__rsc_node_current))) {
         crm_info("%s is in maintenance mode", pcmk__node_name(node));
         node->details->maintenance = TRUE;
     }
 
     discovery = pcmk__node_attr(node,
                                 PCMK__NODE_ATTR_RESOURCE_DISCOVERY_ENABLED,
                                 NULL, pcmk__rsc_node_current);
     if ((discovery != NULL) && !crm_is_true(discovery)) {
         pcmk__config_warn("Ignoring "
                           PCMK__NODE_ATTR_RESOURCE_DISCOVERY_ENABLED
                           " attribute for %s because disabling resource"
                           " discovery is not allowed for cluster nodes",
                           pcmk__node_name(node));
     }
 }
 
 /*!
  * \internal
  * \brief Unpack a node state entry (first pass)
  *
  * Unpack one node state entry from status. This unpacks information from the
  * \C PCMK__XE_NODE_STATE element itself and node attributes inside it, but not
  * the resource history inside it. Multiple passes through the status are needed
  * to fully unpack everything.
  *
  * \param[in]     state      CIB node state XML
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_node_state(const xmlNode *state, pcmk_scheduler_t *scheduler)
 {
     const char *id = NULL;
     const char *uname = NULL;
     pcmk_node_t *this_node = NULL;
 
     id = crm_element_value(state, PCMK_XA_ID);
     if (id == NULL) {
         pcmk__config_err("Ignoring invalid " PCMK__XE_NODE_STATE " entry without "
                          PCMK_XA_ID);
         crm_log_xml_info(state, "missing-id");
         return;
     }
 
     uname = crm_element_value(state, PCMK_XA_UNAME);
     if (uname == NULL) {
         /* If a joining peer makes the cluster acquire the quorum from corosync
          * meanwhile it has not joined CPG membership of pacemaker-controld yet,
          * it's possible that the created PCMK__XE_NODE_STATE entry doesn't have
          * a PCMK_XA_UNAME yet. We should recognize the node as `pending` and
          * wait for it to join CPG.
          */
         crm_trace("Handling " PCMK__XE_NODE_STATE " entry with id=\"%s\" "
                   "without " PCMK_XA_UNAME,
                   id);
     }
 
     this_node = pe_find_node_any(scheduler->nodes, id, uname);
     if (this_node == NULL) {
         pcmk__config_warn("Ignoring recorded node state for id=\"%s\" (%s) "
                           "because it is no longer in the configuration",
                           id, pcmk__s(uname, "uname unknown"));
         return;
     }
 
     if (pcmk__is_pacemaker_remote_node(this_node)) {
         /* We can't determine the online status of Pacemaker Remote nodes until
          * after all resource history has been unpacked. In this first pass, we
          * do need to mark whether the node has been fenced, as this plays a
          * role during unpacking cluster node resource state.
          */
         pcmk__scan_min_int(crm_element_value(state, PCMK__XA_NODE_FENCED),
                            &(this_node->details->remote_was_fenced), 0);
         return;
     }
 
     unpack_transient_attributes(state, this_node, scheduler);
 
     /* Provisionally mark this cluster node as clean. We have at least seen it
      * in the current cluster's lifetime.
      */
     this_node->details->unclean = FALSE;
     this_node->details->unseen = FALSE;
 
     crm_trace("Determining online status of cluster node %s (id %s)",
               pcmk__node_name(this_node), id);
     determine_online_status(state, this_node, scheduler);
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_quorate)
         && this_node->details->online
         && (scheduler->no_quorum_policy == pcmk_no_quorum_fence)) {
         /* Everything else should flow from this automatically
          * (at least until the scheduler becomes able to migrate off
          * healthy resources)
          */
         pe_fence_node(scheduler, this_node, "cluster does not have quorum",
                       FALSE);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack nodes' resource history as much as possible
  *
  * Unpack as many nodes' resource history as possible in one pass through the
  * status. We need to process Pacemaker Remote nodes' connections/containers
  * before unpacking their history; the connection/container history will be
  * in another node's history, so it might take multiple passes to unpack
  * everything.
  *
  * \param[in]     status     CIB XML status section
  * \param[in]     fence      If true, treat any not-yet-unpacked nodes as unseen
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Standard Pacemaker return code (specifically pcmk_rc_ok if done,
  *         or EAGAIN if more unpacking remains to be done)
  */
 static int
 unpack_node_history(const xmlNode *status, bool fence,
                     pcmk_scheduler_t *scheduler)
 {
     int rc = pcmk_rc_ok;
 
     // Loop through all PCMK__XE_NODE_STATE entries in CIB status
     for (const xmlNode *state = first_named_child(status, PCMK__XE_NODE_STATE);
          state != NULL; state = crm_next_same_xml(state)) {
 
         const char *id = pcmk__xe_id(state);
         const char *uname = crm_element_value(state, PCMK_XA_UNAME);
         pcmk_node_t *this_node = NULL;
 
         if ((id == NULL) || (uname == NULL)) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history from malformed "
                       PCMK__XE_NODE_STATE " without id and/or uname");
             continue;
         }
 
         this_node = pe_find_node_any(scheduler->nodes, id, uname);
         if (this_node == NULL) {
             // Warning already logged in first pass through status section
             crm_trace("Not unpacking resource history for node %s because "
                       "no longer in configuration", id);
             continue;
         }
 
         if (this_node->details->unpacked) {
             crm_trace("Not unpacking resource history for node %s because "
                       "already unpacked", id);
             continue;
         }
 
         if (fence) {
             // We're processing all remaining nodes
 
         } else if (pcmk__is_guest_or_bundle_node(this_node)) {
             /* We can unpack a guest node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection and containing resource are both up.
              */
             pcmk_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL) || (rsc->role != pcmk_role_started)
                 || (rsc->container->role != pcmk_role_started)) {
                 crm_trace("Not unpacking resource history for guest node %s "
                           "because container and connection are not known to "
                           "be up", id);
                 continue;
             }
 
         } else if (pcmk__is_remote_node(this_node)) {
             /* We can unpack a remote node's history only after we've unpacked
              * other resource history to the point that we know that the node's
              * connection is up, with the exception of when shutdown locks are
              * in use.
              */
             pcmk_resource_t *rsc = this_node->details->remote_rsc;
 
             if ((rsc == NULL)
                 || (!pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)
                     && (rsc->role != pcmk_role_started))) {
                 crm_trace("Not unpacking resource history for remote node %s "
                           "because connection is not known to be up", id);
                 continue;
             }
 
         /* If fencing and shutdown locks are disabled and we're not processing
          * unseen nodes, then we don't want to unpack offline nodes until online
          * nodes have been unpacked. This allows us to number active clone
          * instances first.
          */
         } else if (!pcmk_any_flags_set(scheduler->flags,
                                        pcmk_sched_fencing_enabled
                                        |pcmk_sched_shutdown_lock)
                    && !this_node->details->online) {
             crm_trace("Not unpacking resource history for offline "
                       "cluster node %s", id);
             continue;
         }
 
         if (pcmk__is_pacemaker_remote_node(this_node)) {
             determine_remote_online_status(scheduler, this_node);
             unpack_handle_remote_attrs(this_node, state, scheduler);
         }
 
         crm_trace("Unpacking resource history for %snode %s",
                   (fence? "unseen " : ""), id);
 
         this_node->details->unpacked = TRUE;
         unpack_node_lrm(this_node, state, scheduler);
 
         rc = EAGAIN; // Other node histories might depend on this one
     }
     return rc;
 }
 
 /* remove nodes that are down, stopping */
 /* create positive rsc_to_node constraints between resources and the nodes they are running on */
 /* anything else? */
 gboolean
 unpack_status(xmlNode *status, pcmk_scheduler_t *scheduler)
 {
     xmlNode *state = NULL;
 
     crm_trace("Beginning unpack");
 
     if (scheduler->tickets == NULL) {
         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     for (state = pcmk__xe_first_child(status); state != NULL;
          state = pcmk__xe_next(state)) {
 
         if (pcmk__xe_is(state, PCMK_XE_TICKETS)) {
             unpack_tickets_state((xmlNode *) state, scheduler);
 
         } else if (pcmk__xe_is(state, PCMK__XE_NODE_STATE)) {
             unpack_node_state(state, scheduler);
         }
     }
 
     while (unpack_node_history(status, FALSE, scheduler) == EAGAIN) {
         crm_trace("Another pass through node resource histories is needed");
     }
 
     // Now catch any nodes we didn't see
     unpack_node_history(status,
                         pcmk_is_set(scheduler->flags,
                                     pcmk_sched_fencing_enabled),
                         scheduler);
 
     /* Now that we know where resources are, we can schedule stops of containers
      * with failed bundle connections
      */
     if (scheduler->stop_needed != NULL) {
         for (GList *item = scheduler->stop_needed; item; item = item->next) {
             pcmk_resource_t *container = item->data;
             pcmk_node_t *node = pcmk__current_node(container);
 
             if (node) {
                 stop_action(container, node, FALSE);
             }
         }
         g_list_free(scheduler->stop_needed);
         scheduler->stop_needed = NULL;
     }
 
     /* Now that we know status of all Pacemaker Remote connections and nodes,
      * we can stop connections for node shutdowns, and check the online status
      * of remote/guest nodes that didn't have any node history to unpack.
      */
     for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
         pcmk_node_t *this_node = gIter->data;
 
         if (!pcmk__is_pacemaker_remote_node(this_node)) {
             continue;
         }
         if (this_node->details->shutdown
             && (this_node->details->remote_rsc != NULL)) {
             pe__set_next_role(this_node->details->remote_rsc, pcmk_role_stopped,
                               "remote shutdown");
         }
         if (!this_node->details->unpacked) {
             determine_remote_online_status(scheduler, this_node);
         }
     }
 
     return TRUE;
 }
 
 /*!
  * \internal
  * \brief Unpack node's time when it became a member at the cluster layer
  *
  * \param[in]     node_state  Node's \c PCMK__XE_NODE_STATE entry
  * \param[in,out] scheduler   Scheduler data
  *
  * \return Epoch time when node became a cluster member
  *         (or scheduler effective time for legacy entries) if a member,
  *         0 if not a member, or -1 if no valid information available
  */
 static long long
 unpack_node_member(const xmlNode *node_state, pcmk_scheduler_t *scheduler)
 {
     const char *member_time = crm_element_value(node_state, PCMK__XA_IN_CCM);
     int member = 0;
 
     if (member_time == NULL) {
         return -1LL;
 
     } else if (crm_str_to_boolean(member_time, &member) == 1) {
         /* If in_ccm=0, we'll return 0 here. If in_ccm=1, either the entry was
          * recorded as a boolean for a DC < 2.1.7, or the node is pending
          * shutdown and has left the CPG, in which case it was set to 1 to avoid
          * fencing for PCMK_OPT_NODE_PENDING_TIMEOUT.
          *
          * We return the effective time for in_ccm=1 because what's important to
          * avoid fencing is that effective time minus this value is less than
          * the pending node timeout.
          */
         return member? (long long) get_effective_time(scheduler) : 0LL;
 
     } else {
         long long when_member = 0LL;
 
         if ((pcmk__scan_ll(member_time, &when_member,
                            0LL) != pcmk_rc_ok) || (when_member < 0LL)) {
             crm_warn("Unrecognized value '%s' for " PCMK__XA_IN_CCM
                      " in " PCMK__XE_NODE_STATE " entry", member_time);
             return -1LL;
         }
         return when_member;
     }
 }
 
 /*!
  * \internal
  * \brief Unpack node's time when it became online in process group
  *
  * \param[in] node_state  Node's \c PCMK__XE_NODE_STATE entry
  *
  * \return Epoch time when node became online in process group (or 0 if not
  *         online, or 1 for legacy online entries)
  */
 static long long
 unpack_node_online(const xmlNode *node_state)
 {
     const char *peer_time = crm_element_value(node_state, PCMK_XA_CRMD);
 
     // @COMPAT Entries recorded for DCs < 2.1.7 have "online" or "offline"
     if (pcmk__str_eq(peer_time, PCMK_VALUE_OFFLINE,
                      pcmk__str_casei|pcmk__str_null_matches)) {
         return 0LL;
 
     } else if (pcmk__str_eq(peer_time, PCMK_VALUE_ONLINE, pcmk__str_casei)) {
         return 1LL;
 
     } else {
         long long when_online = 0LL;
 
         if ((pcmk__scan_ll(peer_time, &when_online, 0LL) != pcmk_rc_ok)
             || (when_online < 0)) {
             crm_warn("Unrecognized value '%s' for " PCMK_XA_CRMD " in "
                      PCMK__XE_NODE_STATE " entry, assuming offline", peer_time);
             return 0LL;
         }
         return when_online;
     }
 }
 
 /*!
  * \internal
  * \brief Unpack node attribute for user-requested fencing
  *
  * \param[in] node        Node to check
  * \param[in] node_state  Node's \c PCMK__XE_NODE_STATE entry in CIB status
  *
  * \return \c true if fencing has been requested for \p node, otherwise \c false
  */
 static bool
 unpack_node_terminate(const pcmk_node_t *node, const xmlNode *node_state)
 {
     long long value = 0LL;
     int value_i = 0;
     const char *value_s = pcmk__node_attr(node, PCMK_NODE_ATTR_TERMINATE,
                                           NULL, pcmk__rsc_node_current);
 
     // Value may be boolean or an epoch time
     if (crm_str_to_boolean(value_s, &value_i) == 1) {
         return (value_i != 0);
     }
     if (pcmk__scan_ll(value_s, &value, 0LL) == pcmk_rc_ok) {
         return (value > 0);
     }
     crm_warn("Ignoring unrecognized value '%s' for " PCMK_NODE_ATTR_TERMINATE
              "node attribute for %s", value_s, pcmk__node_name(node));
     return false;
 }
 
 static gboolean
 determine_online_status_no_fencing(pcmk_scheduler_t *scheduler,
                                    const xmlNode *node_state,
                                    pcmk_node_t *this_node)
 {
     gboolean online = FALSE;
     const char *join = crm_element_value(node_state, PCMK__XA_JOIN);
     const char *exp_state = crm_element_value(node_state, PCMK_XA_EXPECTED);
     long long when_member = unpack_node_member(node_state, scheduler);
     long long when_online = unpack_node_online(node_state);
 
     if (when_member <= 0) {
         crm_trace("Node %s is %sdown", pcmk__node_name(this_node),
                   ((when_member < 0)? "presumed " : ""));
 
     } else if (when_online > 0) {
         if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
             online = TRUE;
         } else {
             crm_debug("Node %s is not ready to run resources: %s",
                       pcmk__node_name(this_node), join);
         }
 
     } else if (this_node->details->expected_up == FALSE) {
         crm_trace("Node %s controller is down: "
                   "member@%lld online@%lld join=%s expected=%s",
                   pcmk__node_name(this_node), when_member, when_online,
                   pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
 
     } else {
         /* mark it unclean */
         pe_fence_node(scheduler, this_node, "peer is unexpectedly down", FALSE);
         crm_info("Node %s member@%lld online@%lld join=%s expected=%s",
                  pcmk__node_name(this_node), when_member, when_online,
                  pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"));
     }
     return online;
 }
 
 /*!
  * \internal
  * \brief Check whether a node has taken too long to join controller group
  *
  * \param[in,out] scheduler    Scheduler data
  * \param[in]     node         Node to check
  * \param[in]     when_member  Epoch time when node became a cluster member
  * \param[in]     when_online  Epoch time when node joined controller group
  *
  * \return true if node has been pending (on the way up) longer than
  *         \c PCMK_OPT_NODE_PENDING_TIMEOUT, otherwise false
  * \note This will also update the cluster's recheck time if appropriate.
  */
 static inline bool
 pending_too_long(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                  long long when_member, long long when_online)
 {
     if ((scheduler->node_pending_timeout > 0)
         && (when_member > 0) && (when_online <= 0)) {
         // There is a timeout on pending nodes, and node is pending
 
         time_t timeout = when_member + scheduler->node_pending_timeout;
 
         if (get_effective_time(node->details->data_set) >= timeout) {
             return true; // Node has timed out
         }
 
         // Node is pending, but still has time
         pe__update_recheck_time(timeout, scheduler, "pending node timeout");
     }
     return false;
 }
 
 static bool
 determine_online_status_fencing(pcmk_scheduler_t *scheduler,
                                 const xmlNode *node_state,
                                 pcmk_node_t *this_node)
 {
     bool termination_requested = unpack_node_terminate(this_node, node_state);
     const char *join = crm_element_value(node_state, PCMK__XA_JOIN);
     const char *exp_state = crm_element_value(node_state, PCMK_XA_EXPECTED);
     long long when_member = unpack_node_member(node_state, scheduler);
     long long when_online = unpack_node_online(node_state);
 
 /*
   - PCMK__XA_JOIN          ::= member|down|pending|banned
   - PCMK_XA_EXPECTED       ::= member|down
 
   @COMPAT with entries recorded for DCs < 2.1.7
   - PCMK__XA_IN_CCM        ::= true|false
   - PCMK_XA_CRMD           ::= online|offline
 
   Since crm_feature_set 3.18.0 (pacemaker-2.1.7):
   - PCMK__XA_IN_CCM        ::= <timestamp>|0
   Since when node has been a cluster member. A value 0 of means the node is not
   a cluster member.
 
   - PCMK_XA_CRMD           ::= <timestamp>|0
   Since when peer has been online in CPG. A value 0 means the peer is offline
   in CPG.
 */
 
     crm_trace("Node %s member@%lld online@%lld join=%s expected=%s%s",
               pcmk__node_name(this_node), when_member, when_online,
               pcmk__s(join, "<null>"), pcmk__s(exp_state, "<null>"),
               (termination_requested? " (termination requested)" : ""));
 
     if (this_node->details->shutdown) {
         crm_debug("%s is shutting down", pcmk__node_name(this_node));
 
         /* Slightly different criteria since we can't shut down a dead peer */
         return (when_online > 0);
     }
 
     if (when_member < 0) {
         pe_fence_node(scheduler, this_node,
                       "peer has not been seen by the cluster", FALSE);
         return false;
     }
 
     if (pcmk__str_eq(join, CRMD_JOINSTATE_NACK, pcmk__str_none)) {
         pe_fence_node(scheduler, this_node,
                       "peer failed Pacemaker membership criteria", FALSE);
 
     } else if (termination_requested) {
         if ((when_member <= 0) && (when_online <= 0)
             && pcmk__str_eq(join, CRMD_JOINSTATE_DOWN, pcmk__str_none)) {
             crm_info("%s was fenced as requested", pcmk__node_name(this_node));
             return false;
         }
         pe_fence_node(scheduler, this_node, "fencing was requested", false);
 
     } else if (pcmk__str_eq(exp_state, CRMD_JOINSTATE_DOWN,
                             pcmk__str_null_matches)) {
 
         if (pending_too_long(scheduler, this_node, when_member, when_online)) {
             pe_fence_node(scheduler, this_node,
                           "peer pending timed out on joining the process group",
                           FALSE);
 
         } else if ((when_member > 0) || (when_online > 0)) {
             crm_info("- %s is not ready to run resources",
                      pcmk__node_name(this_node));
             this_node->details->standby = TRUE;
             this_node->details->pending = TRUE;
 
         } else {
             crm_trace("%s is down or still coming up",
                       pcmk__node_name(this_node));
         }
 
     } else if (when_member <= 0) {
         // Consider PCMK_OPT_PRIORITY_FENCING_DELAY for lost nodes
         pe_fence_node(scheduler, this_node,
                       "peer is no longer part of the cluster", TRUE);
 
     } else if (when_online <= 0) {
         pe_fence_node(scheduler, this_node,
                       "peer process is no longer available", FALSE);
 
         /* Everything is running at this point, now check join state */
 
     } else if (pcmk__str_eq(join, CRMD_JOINSTATE_MEMBER, pcmk__str_none)) {
         crm_info("%s is active", pcmk__node_name(this_node));
 
     } else if (pcmk__str_any_of(join, CRMD_JOINSTATE_PENDING,
                                 CRMD_JOINSTATE_DOWN, NULL)) {
         crm_info("%s is not ready to run resources",
                  pcmk__node_name(this_node));
         this_node->details->standby = TRUE;
         this_node->details->pending = TRUE;
 
     } else {
         pe_fence_node(scheduler, this_node, "peer was in an unknown state",
                       FALSE);
     }
 
     return (when_member > 0);
 }
 
 static void
 determine_remote_online_status(pcmk_scheduler_t *scheduler,
                                pcmk_node_t *this_node)
 {
     pcmk_resource_t *rsc = this_node->details->remote_rsc;
     pcmk_resource_t *container = NULL;
     pcmk_node_t *host = NULL;
 
     /* If there is a node state entry for a (former) Pacemaker Remote node
      * but no resource creating that node, the node's connection resource will
      * be NULL. Consider it an offline remote node in that case.
      */
     if (rsc == NULL) {
         this_node->details->online = FALSE;
         goto remote_online_done;
     }
 
     container = rsc->container;
 
     if (container && pcmk__list_of_1(rsc->running_on)) {
         host = rsc->running_on->data;
     }
 
     /* If the resource is currently started, mark it online. */
     if (rsc->role == pcmk_role_started) {
         crm_trace("%s node %s presumed ONLINE because connection resource is started",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = TRUE;
     }
 
     /* consider this node shutting down if transitioning start->stop */
     if ((rsc->role == pcmk_role_started)
         && (rsc->next_role == pcmk_role_stopped)) {
 
         crm_trace("%s node %s shutting down because connection resource is stopping",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->shutdown = TRUE;
     }
 
     /* Now check all the failure conditions. */
     if(container && pcmk_is_set(container->flags, pcmk_rsc_failed)) {
         crm_trace("Guest node %s UNCLEAN because guest resource failed",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
 
     } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) {
         crm_trace("%s node %s OFFLINE because connection resource failed",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
 
     } else if ((rsc->role == pcmk_role_stopped)
                || ((container != NULL)
                    && (container->role == pcmk_role_stopped))) {
 
         crm_trace("%s node %s OFFLINE because its resource is stopped",
                   (container? "Guest" : "Remote"), this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = FALSE;
 
     } else if (host && (host->details->online == FALSE)
                && host->details->unclean) {
         crm_trace("Guest node %s UNCLEAN because host is unclean",
                   this_node->details->id);
         this_node->details->online = FALSE;
         this_node->details->remote_requires_reset = TRUE;
     }
 
 remote_online_done:
     crm_trace("Remote node %s online=%s",
         this_node->details->id, this_node->details->online ? "TRUE" : "FALSE");
 }
 
 static void
 determine_online_status(const xmlNode *node_state, pcmk_node_t *this_node,
                         pcmk_scheduler_t *scheduler)
 {
     gboolean online = FALSE;
     const char *exp_state = crm_element_value(node_state, PCMK_XA_EXPECTED);
 
     CRM_CHECK(this_node != NULL, return);
 
     this_node->details->shutdown = FALSE;
     this_node->details->expected_up = FALSE;
 
     if (pe__shutdown_requested(this_node)) {
         this_node->details->shutdown = TRUE;
 
     } else if (pcmk__str_eq(exp_state, CRMD_JOINSTATE_MEMBER, pcmk__str_casei)) {
         this_node->details->expected_up = TRUE;
     }
 
     if (this_node->details->type == node_ping) {
         this_node->details->unclean = FALSE;
         online = FALSE;         /* As far as resource management is concerned,
                                  * the node is safely offline.
                                  * Anyone caught abusing this logic will be shot
                                  */
 
     } else if (!pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
         online = determine_online_status_no_fencing(scheduler, node_state,
                                                     this_node);
 
     } else {
         online = determine_online_status_fencing(scheduler, node_state,
                                                  this_node);
     }
 
     if (online) {
         this_node->details->online = TRUE;
 
     } else {
         /* remove node from contention */
         this_node->fixed = TRUE; // @COMPAT deprecated and unused
-        this_node->weight = -INFINITY;
+        this_node->weight = -PCMK_SCORE_INFINITY;
     }
 
     if (online && this_node->details->shutdown) {
         /* don't run resources here */
         this_node->fixed = TRUE; // @COMPAT deprecated and unused
-        this_node->weight = -INFINITY;
+        this_node->weight = -PCMK_SCORE_INFINITY;
     }
 
     if (this_node->details->type == node_ping) {
         crm_info("%s is not a Pacemaker node", pcmk__node_name(this_node));
 
     } else if (this_node->details->unclean) {
         pcmk__sched_warn("%s is unclean", pcmk__node_name(this_node));
 
     } else if (this_node->details->online) {
         crm_info("%s is %s", pcmk__node_name(this_node),
                  this_node->details->shutdown ? "shutting down" :
                  this_node->details->pending ? "pending" :
                  this_node->details->standby ? "standby" :
                  this_node->details->maintenance ? "maintenance" : "online");
 
     } else {
         crm_trace("%s is offline", pcmk__node_name(this_node));
     }
 }
 
 /*!
  * \internal
  * \brief Find the end of a resource's name, excluding any clone suffix
  *
  * \param[in] id  Resource ID to check
  *
  * \return Pointer to last character of resource's base name
  */
 const char *
 pe_base_name_end(const char *id)
 {
     if (!pcmk__str_empty(id)) {
         const char *end = id + strlen(id) - 1;
 
         for (const char *s = end; s > id; --s) {
             switch (*s) {
                 case '0':
                 case '1':
                 case '2':
                 case '3':
                 case '4':
                 case '5':
                 case '6':
                 case '7':
                 case '8':
                 case '9':
                     break;
                 case ':':
                     return (s == end)? s : (s - 1);
                 default:
                     return end;
             }
         }
         return end;
     }
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get a resource name excluding any clone suffix
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_strip(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     char *basename = NULL;
 
     CRM_ASSERT(end);
     basename = strndup(last_rsc_id, end - last_rsc_id + 1);
     CRM_ASSERT(basename);
     return basename;
 }
 
 /*!
  * \internal
  * \brief Get the name of the first instance of a cloned resource
  *
  * \param[in] last_rsc_id  Resource ID to check
  *
  * \return Pointer to newly allocated string with resource's base name plus :0
  * \note It is the caller's responsibility to free() the result.
  *       This asserts on error, so callers can assume result is not NULL.
  */
 char *
 clone_zero(const char *last_rsc_id)
 {
     const char *end = pe_base_name_end(last_rsc_id);
     size_t base_name_len = end - last_rsc_id + 1;
     char *zero = NULL;
 
     CRM_ASSERT(end);
     zero = calloc(base_name_len + 3, sizeof(char));
     CRM_ASSERT(zero);
     memcpy(zero, last_rsc_id, base_name_len);
     zero[base_name_len] = ':';
     zero[base_name_len + 1] = '0';
     return zero;
 }
 
 static pcmk_resource_t *
 create_fake_resource(const char *rsc_id, const xmlNode *rsc_entry,
                      pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *rsc = NULL;
     xmlNode *xml_rsc = create_xml_node(NULL, PCMK_XE_PRIMITIVE);
 
     copy_in_properties(xml_rsc, rsc_entry);
     crm_xml_add(xml_rsc, PCMK_XA_ID, rsc_id);
     crm_log_xml_debug(xml_rsc, "Orphan resource");
 
     if (pe__unpack_resource(xml_rsc, &rsc, NULL, scheduler) != pcmk_rc_ok) {
         return NULL;
     }
 
     if (xml_contains_remote_node(xml_rsc)) {
         pcmk_node_t *node;
 
         crm_debug("Detected orphaned remote node %s", rsc_id);
         node = pe_find_node(scheduler->nodes, rsc_id);
         if (node == NULL) {
             node = pe_create_node(rsc_id, rsc_id, PCMK_VALUE_REMOTE, NULL,
                                   scheduler);
         }
         link_rsc2remotenode(scheduler, rsc);
 
         if (node) {
             crm_trace("Setting node %s as shutting down due to orphaned connection resource", rsc_id);
             node->details->shutdown = TRUE;
         }
     }
 
     if (crm_element_value(rsc_entry, PCMK__META_CONTAINER)) {
         /* This orphaned rsc needs to be mapped to a container. */
         crm_trace("Detected orphaned container filler %s", rsc_id);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_removed_filler);
     }
     pcmk__set_rsc_flags(rsc, pcmk_rsc_removed);
     scheduler->resources = g_list_append(scheduler->resources, rsc);
     return rsc;
 }
 
 /*!
  * \internal
  * \brief Create orphan instance for anonymous clone resource history
  *
  * \param[in,out] parent     Clone resource that orphan will be added to
  * \param[in]     rsc_id     Orphan's resource ID
  * \param[in]     node       Where orphan is active (for logging only)
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Newly added orphaned instance of \p parent
  */
 static pcmk_resource_t *
 create_anonymous_orphan(pcmk_resource_t *parent, const char *rsc_id,
                         const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *top = pe__create_clone_child(parent, scheduler);
 
     // find_rsc() because we might be a cloned group
     pcmk_resource_t *orphan = top->fns->find_rsc(top, rsc_id, NULL,
                                                pcmk_rsc_match_clone_only);
 
     pcmk__rsc_debug(parent, "Created orphan %s for %s: %s on %s",
                     top->id, parent->id, rsc_id, pcmk__node_name(node));
     return orphan;
 }
 
 /*!
  * \internal
  * \brief Check a node for an instance of an anonymous clone
  *
  * Return a child instance of the specified anonymous clone, in order of
  * preference: (1) the instance running on the specified node, if any;
  * (2) an inactive instance (i.e. within the total of \c PCMK_META_CLONE_MAX
  * instances); (3) a newly created orphan (that is, \c PCMK_META_CLONE_MAX
  * instances are already active).
  *
  * \param[in,out] scheduler  Scheduler data
  * \param[in]     node       Node on which to check for instance
  * \param[in,out] parent     Clone to check
  * \param[in]     rsc_id     Name of cloned resource in history (no instance)
  */
 static pcmk_resource_t *
 find_anonymous_clone(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                      pcmk_resource_t *parent, const char *rsc_id)
 {
     GList *rIter = NULL;
     pcmk_resource_t *rsc = NULL;
     pcmk_resource_t *inactive_instance = NULL;
     gboolean skip_inactive = FALSE;
 
     CRM_ASSERT(parent != NULL);
     CRM_ASSERT(pcmk__is_clone(parent));
     CRM_ASSERT(!pcmk_is_set(parent->flags, pcmk_rsc_unique));
 
     // Check for active (or partially active, for cloned groups) instance
     pcmk__rsc_trace(parent, "Looking for %s on %s in %s",
                     rsc_id, pcmk__node_name(node), parent->id);
     for (rIter = parent->children; rsc == NULL && rIter; rIter = rIter->next) {
         GList *locations = NULL;
         pcmk_resource_t *child = rIter->data;
 
         /* Check whether this instance is already known to be active or pending
          * anywhere, at this stage of unpacking. Because this function is called
          * for a resource before the resource's individual operation history
          * entries are unpacked, locations will generally not contain the
          * desired node.
          *
          * However, there are three exceptions:
          * (1) when child is a cloned group and we have already unpacked the
          *     history of another member of the group on the same node;
          * (2) when we've already unpacked the history of another numbered
          *     instance on the same node (which can happen if
          *     PCMK_META_GLOBALLY_UNIQUE was flipped from true to false); and
          * (3) when we re-run calculations on the same scheduler data as part of
          *     a simulation.
          */
         child->fns->location(child, &locations, 2);
         if (locations) {
             /* We should never associate the same numbered anonymous clone
              * instance with multiple nodes, and clone instances can't migrate,
              * so there must be only one location, regardless of history.
              */
             CRM_LOG_ASSERT(locations->next == NULL);
 
             if (pcmk__same_node((pcmk_node_t *) locations->data, node)) {
                 /* This child instance is active on the requested node, so check
                  * for a corresponding configured resource. We use find_rsc()
                  * instead of child because child may be a cloned group, and we
                  * need the particular member corresponding to rsc_id.
                  *
                  * If the history entry is orphaned, rsc will be NULL.
                  */
                 rsc = parent->fns->find_rsc(child, rsc_id, NULL,
                                             pcmk_rsc_match_clone_only);
                 if (rsc) {
                     /* If there are multiple instance history entries for an
                      * anonymous clone in a single node's history (which can
                      * happen if PCMK_META_GLOBALLY_UNIQUE is switched from true
                      * to false), we want to consider the instances beyond the
                      * first as orphans, even if there are inactive instance
                      * numbers available.
                      */
                     if (rsc->running_on) {
                         crm_notice("Active (now-)anonymous clone %s has "
                                    "multiple (orphan) instance histories on %s",
                                    parent->id, pcmk__node_name(node));
                         skip_inactive = TRUE;
                         rsc = NULL;
                     } else {
                         pcmk__rsc_trace(parent, "Resource %s, active", rsc->id);
                     }
                 }
             }
             g_list_free(locations);
 
         } else {
             pcmk__rsc_trace(parent, "Resource %s, skip inactive", child->id);
             if (!skip_inactive && !inactive_instance
                 && !pcmk_is_set(child->flags, pcmk_rsc_blocked)) {
                 // Remember one inactive instance in case we don't find active
                 inactive_instance = parent->fns->find_rsc(child, rsc_id, NULL,
                                                           pcmk_rsc_match_clone_only);
 
                 /* ... but don't use it if it was already associated with a
                  * pending action on another node
                  */
                 if ((inactive_instance != NULL) &&
                     (inactive_instance->pending_node != NULL) &&
                     !pcmk__same_node(inactive_instance->pending_node, node)) {
                     inactive_instance = NULL;
                 }
             }
         }
     }
 
     if ((rsc == NULL) && !skip_inactive && (inactive_instance != NULL)) {
         pcmk__rsc_trace(parent, "Resource %s, empty slot",
                         inactive_instance->id);
         rsc = inactive_instance;
     }
 
     /* If the resource has PCMK_META_REQUIRES set to PCMK_VALUE_QUORUM or
      * PCMK_VALUE_NOTHING, and we don't have a clone instance for every node, we
      * don't want to consume a valid instance number for unclean nodes. Such
      * instances may appear to be active according to the history, but should be
      * considered inactive, so we can start an instance elsewhere. Treat such
      * instances as orphans.
      *
      * An exception is instances running on guest nodes -- since guest node
      * "fencing" is actually just a resource stop, requires shouldn't apply.
      *
      * @TODO Ideally, we'd use an inactive instance number if it is not needed
      * for any clean instances. However, we don't know that at this point.
      */
     if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)
         && (!node->details->online || node->details->unclean)
         && !pcmk__is_guest_or_bundle_node(node)
         && !pe__is_universal_clone(parent, scheduler)) {
 
         rsc = NULL;
     }
 
     if (rsc == NULL) {
         rsc = create_anonymous_orphan(parent, rsc_id, node, scheduler);
         pcmk__rsc_trace(parent, "Resource %s, orphan", rsc->id);
     }
     return rsc;
 }
 
 static pcmk_resource_t *
 unpack_find_resource(pcmk_scheduler_t *scheduler, const pcmk_node_t *node,
                      const char *rsc_id)
 {
     pcmk_resource_t *rsc = NULL;
     pcmk_resource_t *parent = NULL;
 
     crm_trace("looking for %s", rsc_id);
     rsc = pe_find_resource(scheduler->resources, rsc_id);
 
     if (rsc == NULL) {
         /* If we didn't find the resource by its name in the operation history,
          * check it again as a clone instance. Even when PCMK_META_CLONE_MAX=0,
          * we create a single :0 orphan to match against here.
          */
         char *clone0_id = clone_zero(rsc_id);
         pcmk_resource_t *clone0 = pe_find_resource(scheduler->resources,
                                                    clone0_id);
 
         if (clone0 && !pcmk_is_set(clone0->flags, pcmk_rsc_unique)) {
             rsc = clone0;
             parent = uber_parent(clone0);
             crm_trace("%s found as %s (%s)", rsc_id, clone0_id, parent->id);
         } else {
             crm_trace("%s is not known as %s either (orphan)",
                       rsc_id, clone0_id);
         }
         free(clone0_id);
 
     } else if (rsc->variant > pcmk_rsc_variant_primitive) {
         crm_trace("Resource history for %s is orphaned because it is no longer primitive",
                   rsc_id);
         return NULL;
 
     } else {
         parent = uber_parent(rsc);
     }
 
     if (pcmk__is_anonymous_clone(parent)) {
 
         if (pcmk__is_bundled(parent)) {
             rsc = pe__find_bundle_replica(parent->parent, node);
         } else {
             char *base = clone_strip(rsc_id);
 
             rsc = find_anonymous_clone(scheduler, node, parent, base);
             free(base);
             CRM_ASSERT(rsc != NULL);
         }
     }
 
     if (rsc && !pcmk__str_eq(rsc_id, rsc->id, pcmk__str_casei)
         && !pcmk__str_eq(rsc_id, rsc->clone_name, pcmk__str_casei)) {
 
         pcmk__str_update(&rsc->clone_name, rsc_id);
         pcmk__rsc_debug(rsc, "Internally renamed %s on %s to %s%s",
                         rsc_id, pcmk__node_name(node), rsc->id,
                         pcmk_is_set(rsc->flags, pcmk_rsc_removed)? " (ORPHAN)" : "");
     }
     return rsc;
 }
 
 static pcmk_resource_t *
 process_orphan_resource(const xmlNode *rsc_entry, const pcmk_node_t *node,
                         pcmk_scheduler_t *scheduler)
 {
     pcmk_resource_t *rsc = NULL;
     const char *rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
 
     crm_debug("Detected orphan resource %s on %s",
               rsc_id, pcmk__node_name(node));
     rsc = create_fake_resource(rsc_id, rsc_entry, scheduler);
     if (rsc == NULL) {
         return NULL;
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_stop_removed_resources)) {
         pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
 
     } else {
         CRM_CHECK(rsc != NULL, return NULL);
         pcmk__rsc_trace(rsc, "Added orphan %s", rsc->id);
-        resource_location(rsc, NULL, -INFINITY, "__orphan_do_not_run__",
-                          scheduler);
+        resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
+                          "__orphan_do_not_run__", scheduler);
     }
     return rsc;
 }
 
 static void
 process_rsc_state(pcmk_resource_t *rsc, pcmk_node_t *node,
                   enum action_fail_response on_fail)
 {
     pcmk_node_t *tmpnode = NULL;
     char *reason = NULL;
     enum action_fail_response save_on_fail = pcmk_on_fail_ignore;
 
     CRM_ASSERT(rsc);
     pcmk__rsc_trace(rsc, "Resource %s is %s on %s: on_fail=%s",
                     rsc->id, pcmk_role_text(rsc->role), pcmk__node_name(node),
                     pcmk_on_fail_text(on_fail));
 
     /* process current state */
     if (rsc->role != pcmk_role_unknown) {
         pcmk_resource_t *iter = rsc;
 
         while (iter) {
             if (g_hash_table_lookup(iter->known_on, node->details->id) == NULL) {
                 pcmk_node_t *n = pe__copy_node(node);
 
                 pcmk__rsc_trace(rsc, "%s%s%s known on %s",
                                 rsc->id,
                                 ((rsc->clone_name == NULL)? "" : " also known as "),
                                 ((rsc->clone_name == NULL)? "" : rsc->clone_name),
                                 pcmk__node_name(n));
                 g_hash_table_insert(iter->known_on, (gpointer) n->details->id, n);
             }
             if (pcmk_is_set(iter->flags, pcmk_rsc_unique)) {
                 break;
             }
             iter = iter->parent;
         }
     }
 
     /* If a managed resource is believed to be running, but node is down ... */
     if ((rsc->role > pcmk_role_stopped)
         && node->details->online == FALSE
         && node->details->maintenance == FALSE
         && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
 
         gboolean should_fence = FALSE;
 
         /* If this is a guest node, fence it (regardless of whether fencing is
          * enabled, because guest node fencing is done by recovery of the
          * container resource rather than by the fencer). Mark the resource
          * we're processing as failed. When the guest comes back up, its
          * operation history in the CIB will be cleared, freeing the affected
          * resource to run again once we are sure we know its state.
          */
         if (pcmk__is_guest_or_bundle_node(node)) {
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             should_fence = TRUE;
 
         } else if (pcmk_is_set(rsc->cluster->flags,
                                pcmk_sched_fencing_enabled)) {
             if (pcmk__is_remote_node(node)
                 && (node->details->remote_rsc != NULL)
                 && !pcmk_is_set(node->details->remote_rsc->flags,
                                 pcmk_rsc_failed)) {
 
                 /* Setting unseen means that fencing of the remote node will
                  * occur only if the connection resource is not going to start
                  * somewhere. This allows connection resources on a failed
                  * cluster node to move to another node without requiring the
                  * remote nodes to be fenced as well.
                  */
                 node->details->unseen = TRUE;
                 reason = crm_strdup_printf("%s is active there (fencing will be"
                                            " revoked if remote connection can "
                                            "be re-established elsewhere)",
                                            rsc->id);
             }
             should_fence = TRUE;
         }
 
         if (should_fence) {
             if (reason == NULL) {
                reason = crm_strdup_printf("%s is thought to be active there", rsc->id);
             }
             pe_fence_node(rsc->cluster, node, reason, FALSE);
         }
         free(reason);
     }
 
     /* In order to calculate priority_fencing_delay correctly, save the failure information and pass it to native_add_running(). */
     save_on_fail = on_fail;
 
     if (node->details->unclean) {
         /* No extra processing needed
          * Also allows resources to be started again after a node is shot
          */
         on_fail = pcmk_on_fail_ignore;
     }
 
     switch (on_fail) {
         case pcmk_on_fail_ignore:
             /* nothing to do */
             break;
 
         case pcmk_on_fail_demote:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed);
             demote_action(rsc, node, FALSE);
             break;
 
         case pcmk_on_fail_fence_node:
             /* treat it as if it is still running
              * but also mark the node as unclean
              */
             reason = crm_strdup_printf("%s failed there", rsc->id);
             pe_fence_node(rsc->cluster, node, reason, FALSE);
             free(reason);
             break;
 
         case pcmk_on_fail_standby_node:
             node->details->standby = TRUE;
             node->details->standby_onfail = TRUE;
             break;
 
         case pcmk_on_fail_block:
             /* is_managed == FALSE will prevent any
              * actions being sent for the resource
              */
             pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
             pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
             break;
 
         case pcmk_on_fail_ban:
             /* make sure it comes up somewhere else
              * or not at all
              */
-            resource_location(rsc, node, -INFINITY, "__action_migration_auto__",
-                              rsc->cluster);
+            resource_location(rsc, node, -PCMK_SCORE_INFINITY,
+                              "__action_migration_auto__", rsc->cluster);
             break;
 
         case pcmk_on_fail_stop:
             pe__set_next_role(rsc, pcmk_role_stopped,
                               PCMK_META_ON_FAIL "=" PCMK_VALUE_STOP);
             break;
 
         case pcmk_on_fail_restart:
             if ((rsc->role != pcmk_role_stopped)
                 && (rsc->role != pcmk_role_unknown)) {
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case pcmk_on_fail_restart_container:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             if ((rsc->container != NULL) && pcmk__is_bundled(rsc)) {
                 /* A bundle's remote connection can run on a different node than
                  * the bundle's container. We don't necessarily know where the
                  * container is running yet, so remember it and add a stop
                  * action for it later.
                  */
                 rsc->cluster->stop_needed =
                     g_list_prepend(rsc->cluster->stop_needed, rsc->container);
             } else if (rsc->container) {
                 stop_action(rsc->container, node, FALSE);
             } else if ((rsc->role != pcmk_role_stopped)
                        && (rsc->role != pcmk_role_unknown)) {
                 stop_action(rsc, node, FALSE);
             }
             break;
 
         case pcmk_on_fail_reset_remote:
             pcmk__set_rsc_flags(rsc, pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
                 tmpnode = NULL;
                 if (rsc->is_remote_node) {
                     tmpnode = pe_find_node(rsc->cluster->nodes, rsc->id);
                 }
                 if (pcmk__is_remote_node(tmpnode)
                     && !(tmpnode->details->remote_was_fenced)) {
                     /* The remote connection resource failed in a way that
                      * should result in fencing the remote node.
                      */
                     pe_fence_node(rsc->cluster, tmpnode,
                                   "remote connection is unrecoverable", FALSE);
                 }
             }
 
             /* require the stop action regardless if fencing is occurring or not. */
             if (rsc->role > pcmk_role_stopped) {
                 stop_action(rsc, node, FALSE);
             }
 
             /* if reconnect delay is in use, prevent the connection from exiting the
              * "STOPPED" role until the failure is cleared by the delay timeout. */
             if (rsc->remote_reconnect_ms) {
                 pe__set_next_role(rsc, pcmk_role_stopped, "remote reset");
             }
             break;
     }
 
     /* ensure a remote-node connection failure forces an unclean remote-node
      * to be fenced. By setting unseen = FALSE, the remote-node failure will
      * result in a fencing operation regardless if we're going to attempt to 
      * reconnect to the remote-node in this transition or not. */
     if (pcmk_is_set(rsc->flags, pcmk_rsc_failed) && rsc->is_remote_node) {
         tmpnode = pe_find_node(rsc->cluster->nodes, rsc->id);
         if (tmpnode && tmpnode->details->unclean) {
             tmpnode->details->unseen = FALSE;
         }
     }
 
     if ((rsc->role != pcmk_role_stopped)
         && (rsc->role != pcmk_role_unknown)) {
         if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) {
             if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) {
                 pcmk__config_warn("Detected active orphan %s running on %s",
                                   rsc->id, pcmk__node_name(node));
             } else {
                 pcmk__config_warn("Resource '%s' must be stopped manually on "
                                   "%s because cluster is configured not to "
                                   "stop active orphans",
                                   rsc->id, pcmk__node_name(node));
             }
         }
 
         native_add_running(rsc, node, rsc->cluster,
                            (save_on_fail != pcmk_on_fail_ignore));
         switch (on_fail) {
             case pcmk_on_fail_ignore:
                 break;
             case pcmk_on_fail_demote:
             case pcmk_on_fail_block:
                 pcmk__set_rsc_flags(rsc, pcmk_rsc_failed);
                 break;
             default:
                 pcmk__set_rsc_flags(rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
                 break;
         }
 
     } else if (rsc->clone_name && strchr(rsc->clone_name, ':') != NULL) {
         /* Only do this for older status sections that included instance numbers
          * Otherwise stopped instances will appear as orphans
          */
         pcmk__rsc_trace(rsc, "Resetting clone_name %s for %s (stopped)",
                         rsc->clone_name, rsc->id);
         free(rsc->clone_name);
         rsc->clone_name = NULL;
 
     } else {
         GList *possible_matches = pe__resource_actions(rsc, node,
                                                        PCMK_ACTION_STOP, FALSE);
         GList *gIter = possible_matches;
 
         for (; gIter != NULL; gIter = gIter->next) {
             pcmk_action_t *stop = (pcmk_action_t *) gIter->data;
 
             pcmk__set_action_flags(stop, pcmk_action_optional);
         }
 
         g_list_free(possible_matches);
     }
 
     /* A successful stop after migrate_to on the migration source doesn't make
      * the partially migrated resource stopped on the migration target.
      */
     if ((rsc->role == pcmk_role_stopped)
         && rsc->partial_migration_source
         && rsc->partial_migration_source->details == node->details
         && rsc->partial_migration_target
         && rsc->running_on) {
 
         rsc->role = pcmk_role_started;
     }
 }
 
 /* create active recurring operations as optional */
 static void
 process_recurring(pcmk_node_t *node, pcmk_resource_t *rsc,
                   int start_index, int stop_index,
                   GList *sorted_op_list, pcmk_scheduler_t *scheduler)
 {
     int counter = -1;
     const char *task = NULL;
     const char *status = NULL;
     GList *gIter = sorted_op_list;
 
     CRM_ASSERT(rsc);
     pcmk__rsc_trace(rsc, "%s: Start index %d, stop index = %d",
                     rsc->id, start_index, stop_index);
 
     for (; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         guint interval_ms = 0;
         char *key = NULL;
         const char *id = pcmk__xe_id(rsc_op);
 
         counter++;
 
         if (node->details->online == FALSE) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: node is offline",
                             rsc->id, pcmk__node_name(node));
             break;
 
             /* Need to check if there's a monitor for role="Stopped" */
         } else if (start_index < stop_index && counter <= stop_index) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: resource is not active",
                             id, pcmk__node_name(node));
             continue;
 
         } else if (counter < start_index) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: old %d",
                             id, pcmk__node_name(node), counter);
             continue;
         }
 
         crm_element_value_ms(rsc_op, PCMK_META_INTERVAL, &interval_ms);
         if (interval_ms == 0) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: non-recurring",
                             id, pcmk__node_name(node));
             continue;
         }
 
         status = crm_element_value(rsc_op, PCMK__XA_OP_STATUS);
         if (pcmk__str_eq(status, "-1", pcmk__str_casei)) {
             pcmk__rsc_trace(rsc, "Skipping %s on %s: status",
                             id, pcmk__node_name(node));
             continue;
         }
         task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
         /* create the action */
         key = pcmk__op_key(rsc->id, task, interval_ms);
         pcmk__rsc_trace(rsc, "Creating %s on %s", key, pcmk__node_name(node));
         custom_action(rsc, key, task, node, TRUE, scheduler);
     }
 }
 
 void
 calculate_active_ops(const GList *sorted_op_list, int *start_index,
                      int *stop_index)
 {
     int counter = -1;
     int implied_monitor_start = -1;
     int implied_clone_start = -1;
     const char *task = NULL;
     const char *status = NULL;
 
     *stop_index = -1;
     *start_index = -1;
 
     for (const GList *iter = sorted_op_list; iter != NULL; iter = iter->next) {
         const xmlNode *rsc_op = (const xmlNode *) iter->data;
 
         counter++;
 
         task = crm_element_value(rsc_op, PCMK_XA_OPERATION);
         status = crm_element_value(rsc_op, PCMK__XA_OP_STATUS);
 
         if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_casei)
             && pcmk__str_eq(status, "0", pcmk__str_casei)) {
             *stop_index = counter;
 
         } else if (pcmk__strcase_any_of(task, PCMK_ACTION_START,
                                         PCMK_ACTION_MIGRATE_FROM, NULL)) {
             *start_index = counter;
 
         } else if ((implied_monitor_start <= *stop_index)
                    && pcmk__str_eq(task, PCMK_ACTION_MONITOR,
                                    pcmk__str_casei)) {
             const char *rc = crm_element_value(rsc_op, PCMK__XA_RC_CODE);
 
             if (pcmk__strcase_any_of(rc, "0", "8", NULL)) {
                 implied_monitor_start = counter;
             }
         } else if (pcmk__strcase_any_of(task, PCMK_ACTION_PROMOTE,
                                         PCMK_ACTION_DEMOTE, NULL)) {
             implied_clone_start = counter;
         }
     }
 
     if (*start_index == -1) {
         if (implied_clone_start != -1) {
             *start_index = implied_clone_start;
         } else if (implied_monitor_start != -1) {
             *start_index = implied_monitor_start;
         }
     }
 }
 
 // If resource history entry has shutdown lock, remember lock node and time
 static void
 unpack_shutdown_lock(const xmlNode *rsc_entry, pcmk_resource_t *rsc,
                      const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
 {
     time_t lock_time = 0;   // When lock started (i.e. node shutdown time)
 
     if ((crm_element_value_epoch(rsc_entry, PCMK_OPT_SHUTDOWN_LOCK,
                                  &lock_time) == pcmk_ok) && (lock_time != 0)) {
 
         if ((scheduler->shutdown_lock > 0)
             && (get_effective_time(scheduler)
                 > (lock_time + scheduler->shutdown_lock))) {
             pcmk__rsc_info(rsc, "Shutdown lock for %s on %s expired",
                            rsc->id, pcmk__node_name(node));
             pe__clear_resource_history(rsc, node);
         } else {
             /* @COMPAT I don't like breaking const signatures, but
              * rsc->lock_node should really be const -- we just can't change it
              * until the next API compatibility break.
              */
             rsc->lock_node = (pcmk_node_t *) node;
             rsc->lock_time = lock_time;
         }
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one \c PCMK__XE_LRM_RESOURCE entry from a node's CIB status
  *
  * \param[in,out] node       Node whose status is being unpacked
  * \param[in]     rsc_entry  \c PCMK__XE_LRM_RESOURCE XML being unpacked
  * \param[in,out] scheduler  Scheduler data
  *
  * \return Resource corresponding to the entry, or NULL if no operation history
  */
 static pcmk_resource_t *
 unpack_lrm_resource(pcmk_node_t *node, const xmlNode *lrm_resource,
                     pcmk_scheduler_t *scheduler)
 {
     GList *gIter = NULL;
     int stop_index = -1;
     int start_index = -1;
     enum rsc_role_e req_role = pcmk_role_unknown;
 
     const char *rsc_id = pcmk__xe_id(lrm_resource);
 
     pcmk_resource_t *rsc = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     xmlNode *rsc_op = NULL;
     xmlNode *last_failure = NULL;
 
     enum action_fail_response on_fail = pcmk_on_fail_ignore;
     enum rsc_role_e saved_role = pcmk_role_unknown;
 
     if (rsc_id == NULL) {
         pcmk__config_err("Ignoring invalid " PCMK__XE_LRM_RESOURCE
                          " entry: No " PCMK_XA_ID);
         crm_log_xml_info(lrm_resource, "missing-id");
         return NULL;
     }
     crm_trace("Unpacking " PCMK__XE_LRM_RESOURCE " for %s on %s",
               rsc_id, pcmk__node_name(node));
 
     /* Build a list of individual PCMK__XE_LRM_RSC_OP entries, so we can sort
      * them
      */
     for (rsc_op = first_named_child(lrm_resource, PCMK__XE_LRM_RSC_OP);
          rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) {
 
         op_list = g_list_prepend(op_list, rsc_op);
     }
 
     if (!pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         }
     }
 
     /* find the resource */
     rsc = unpack_find_resource(scheduler, node, rsc_id);
     if (rsc == NULL) {
         if (op_list == NULL) {
             // If there are no operations, there is nothing to do
             return NULL;
         } else {
             rsc = process_orphan_resource(lrm_resource, node, scheduler);
         }
     }
     CRM_ASSERT(rsc != NULL);
 
     // Check whether the resource is "shutdown-locked" to this node
     if (pcmk_is_set(scheduler->flags, pcmk_sched_shutdown_lock)) {
         unpack_shutdown_lock(lrm_resource, rsc, node, scheduler);
     }
 
     /* process operations */
     saved_role = rsc->role;
     rsc->role = pcmk_role_unknown;
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         unpack_rsc_op(rsc, node, rsc_op, &last_failure, &on_fail);
     }
 
     /* create active recurring operations as optional */
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
     process_recurring(node, rsc, start_index, stop_index, sorted_op_list,
                       scheduler);
 
     /* no need to free the contents */
     g_list_free(sorted_op_list);
 
     process_rsc_state(rsc, node, on_fail);
 
     if (get_target_role(rsc, &req_role)) {
         if ((rsc->next_role == pcmk_role_unknown)
             || (req_role < rsc->next_role)) {
 
             pe__set_next_role(rsc, req_role, PCMK_META_TARGET_ROLE);
 
         } else if (req_role > rsc->next_role) {
             pcmk__rsc_info(rsc,
                            "%s: Not overwriting calculated next role %s"
                            " with requested next role %s",
                            rsc->id, pcmk_role_text(rsc->next_role),
                            pcmk_role_text(req_role));
         }
     }
 
     if (saved_role > rsc->role) {
         rsc->role = saved_role;
     }
 
     return rsc;
 }
 
 static void
 handle_orphaned_container_fillers(const xmlNode *lrm_rsc_list,
                                   pcmk_scheduler_t *scheduler)
 {
     for (const xmlNode *rsc_entry = pcmk__xe_first_child(lrm_rsc_list);
          rsc_entry != NULL; rsc_entry = pcmk__xe_next(rsc_entry)) {
 
         pcmk_resource_t *rsc;
         pcmk_resource_t *container;
         const char *rsc_id;
         const char *container_id;
 
         if (!pcmk__xe_is(rsc_entry, PCMK__XE_LRM_RESOURCE)) {
             continue;
         }
 
         container_id = crm_element_value(rsc_entry, PCMK__META_CONTAINER);
         rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID);
         if (container_id == NULL || rsc_id == NULL) {
             continue;
         }
 
         container = pe_find_resource(scheduler->resources, container_id);
         if (container == NULL) {
             continue;
         }
 
         rsc = pe_find_resource(scheduler->resources, rsc_id);
         if ((rsc == NULL) || (rsc->container != NULL)
             || !pcmk_is_set(rsc->flags, pcmk_rsc_removed_filler)) {
             continue;
         }
 
         pcmk__rsc_trace(rsc, "Mapped container of orphaned resource %s to %s",
                         rsc->id, container_id);
         rsc->container = container;
         container->fillers = g_list_append(container->fillers, rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack one node's lrm status section
  *
  * \param[in,out] node       Node whose status is being unpacked
  * \param[in]     xml        CIB node state XML
  * \param[in,out] scheduler  Scheduler data
  */
 static void
 unpack_node_lrm(pcmk_node_t *node, const xmlNode *xml,
                 pcmk_scheduler_t *scheduler)
 {
     bool found_orphaned_container_filler = false;
 
     // Drill down to PCMK__XE_LRM_RESOURCES section
     xml = find_xml_node(xml, PCMK__XE_LRM, FALSE);
     if (xml == NULL) {
         return;
     }
     xml = find_xml_node(xml, PCMK__XE_LRM_RESOURCES, FALSE);
     if (xml == NULL) {
         return;
     }
 
     // Unpack each PCMK__XE_LRM_RESOURCE entry
     for (const xmlNode *rsc_entry = first_named_child(xml,
                                                       PCMK__XE_LRM_RESOURCE);
          rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
 
         pcmk_resource_t *rsc = unpack_lrm_resource(node, rsc_entry, scheduler);
 
         if ((rsc != NULL)
             && pcmk_is_set(rsc->flags, pcmk_rsc_removed_filler)) {
             found_orphaned_container_filler = true;
         }
     }
 
     /* Now that all resource state has been unpacked for this node, map any
      * orphaned container fillers to their container resource.
      */
     if (found_orphaned_container_filler) {
         handle_orphaned_container_fillers(xml, scheduler);
     }
 }
 
 static void
 set_active(pcmk_resource_t *rsc)
 {
     const pcmk_resource_t *top = pe__const_top_resource(rsc, false);
 
     if (top && pcmk_is_set(top->flags, pcmk_rsc_promotable)) {
         rsc->role = pcmk_role_unpromoted;
     } else {
         rsc->role = pcmk_role_started;
     }
 }
 
 static void
 set_node_score(gpointer key, gpointer value, gpointer user_data)
 {
     pcmk_node_t *node = value;
     int *score = user_data;
 
     node->weight = *score;
 }
 
 #define XPATH_NODE_STATE "/" PCMK_XE_CIB "/" PCMK_XE_STATUS \
                          "/" PCMK__XE_NODE_STATE
 #define SUB_XPATH_LRM_RESOURCE "/" PCMK__XE_LRM             \
                                "/" PCMK__XE_LRM_RESOURCES   \
                                "/" PCMK__XE_LRM_RESOURCE
 #define SUB_XPATH_LRM_RSC_OP "/" PCMK__XE_LRM_RSC_OP
 
 static xmlNode *
 find_lrm_op(const char *resource, const char *op, const char *node, const char *source,
             int target_rc, pcmk_scheduler_t *scheduler)
 {
     GString *xpath = NULL;
     xmlNode *xml = NULL;
 
     CRM_CHECK((resource != NULL) && (op != NULL) && (node != NULL),
               return NULL);
 
     xpath = g_string_sized_new(256);
     pcmk__g_strcat(xpath,
                    XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='", node, "']"
                    SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='", resource, "']"
                    SUB_XPATH_LRM_RSC_OP "[@" PCMK_XA_OPERATION "='", op, "'",
                    NULL);
 
     /* Need to check against transition_magic too? */
     if ((source != NULL) && (strcmp(op, PCMK_ACTION_MIGRATE_TO) == 0)) {
         pcmk__g_strcat(xpath,
                        " and @" PCMK__META_MIGRATE_TARGET "='", source, "']",
                        NULL);
 
     } else if ((source != NULL)
                && (strcmp(op, PCMK_ACTION_MIGRATE_FROM) == 0)) {
         pcmk__g_strcat(xpath,
                        " and @" PCMK__META_MIGRATE_SOURCE "='", source, "']",
                        NULL);
     } else {
         g_string_append_c(xpath, ']');
     }
 
     xml = get_xpath_object((const char *) xpath->str, scheduler->input,
                            LOG_DEBUG);
     g_string_free(xpath, TRUE);
 
     if (xml && target_rc >= 0) {
         int rc = PCMK_OCF_UNKNOWN_ERROR;
         int status = PCMK_EXEC_ERROR;
 
         crm_element_value_int(xml, PCMK__XA_RC_CODE, &rc);
         crm_element_value_int(xml, PCMK__XA_OP_STATUS, &status);
         if ((rc != target_rc) || (status != PCMK_EXEC_DONE)) {
             return NULL;
         }
     }
     return xml;
 }
 
 static xmlNode *
 find_lrm_resource(const char *rsc_id, const char *node_name,
                   pcmk_scheduler_t *scheduler)
 {
     GString *xpath = NULL;
     xmlNode *xml = NULL;
 
     CRM_CHECK((rsc_id != NULL) && (node_name != NULL), return NULL);
 
     xpath = g_string_sized_new(256);
     pcmk__g_strcat(xpath,
                    XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='", node_name, "']"
                    SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='", rsc_id, "']",
                    NULL);
 
     xml = get_xpath_object((const char *) xpath->str, scheduler->input,
                            LOG_DEBUG);
 
     g_string_free(xpath, TRUE);
     return xml;
 }
 
 /*!
  * \internal
  * \brief Check whether a resource has no completed action history on a node
  *
  * \param[in,out] rsc        Resource to check
  * \param[in]     node_name  Node to check
  *
  * \return true if \p rsc_id is unknown on \p node_name, otherwise false
  */
 static bool
 unknown_on_node(pcmk_resource_t *rsc, const char *node_name)
 {
     bool result = false;
     xmlXPathObjectPtr search;
     char *xpath = NULL;
 
     xpath = crm_strdup_printf(XPATH_NODE_STATE "[@" PCMK_XA_UNAME "='%s']"
                               SUB_XPATH_LRM_RESOURCE "[@" PCMK_XA_ID "='%s']"
                               SUB_XPATH_LRM_RSC_OP
                               "[@" PCMK__XA_RC_CODE "!='%d']",
                               node_name, rsc->id, PCMK_OCF_UNKNOWN);
 
     search = xpath_search(rsc->cluster->input, xpath);
     result = (numXpathResults(search) == 0);
     freeXpathObject(search);
     free(xpath);
     return result;
 }
 
 /*!
  * \brief Check whether a probe/monitor indicating the resource was not running
  * on a node happened after some event
  *
  * \param[in]     rsc_id     Resource being checked
  * \param[in]     node_name  Node being checked
  * \param[in]     xml_op     Event that monitor is being compared to
  * \param[in]     same_node  Whether the operations are on the same node
  * \param[in,out] scheduler  Scheduler data
  *
  * \return true if such a monitor happened after event, false otherwise
  */
 static bool
 monitor_not_running_after(const char *rsc_id, const char *node_name,
                           const xmlNode *xml_op, bool same_node,
                           pcmk_scheduler_t *scheduler)
 {
     /* Any probe/monitor operation on the node indicating it was not running
      * there
      */
     xmlNode *monitor = find_lrm_op(rsc_id, PCMK_ACTION_MONITOR, node_name,
                                    NULL, PCMK_OCF_NOT_RUNNING, scheduler);
 
     return (monitor && pe__is_newer_op(monitor, xml_op, same_node) > 0);
 }
 
 /*!
  * \brief Check whether any non-monitor operation on a node happened after some
  * event
  *
  * \param[in]     rsc_id     Resource being checked
  * \param[in]     node_name  Node being checked
  * \param[in]     xml_op     Event that non-monitor is being compared to
  * \param[in]     same_node  Whether the operations are on the same node
  * \param[in,out] scheduler  Scheduler data
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 non_monitor_after(const char *rsc_id, const char *node_name,
                   const xmlNode *xml_op, bool same_node,
                   pcmk_scheduler_t *scheduler)
 {
     xmlNode *lrm_resource = NULL;
 
     lrm_resource = find_lrm_resource(rsc_id, node_name, scheduler);
     if (lrm_resource == NULL) {
         return false;
     }
 
     for (xmlNode *op = first_named_child(lrm_resource, PCMK__XE_LRM_RSC_OP);
          op != NULL; op = crm_next_same_xml(op)) {
         const char * task = NULL;
 
         if (op == xml_op) {
             continue;
         }
 
         task = crm_element_value(op, PCMK_XA_OPERATION);
 
         if (pcmk__str_any_of(task, PCMK_ACTION_START, PCMK_ACTION_STOP,
                              PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
                              NULL)
             && pe__is_newer_op(op, xml_op, same_node) > 0) {
             return true;
         }
     }
 
     return false;
 }
 
 /*!
  * \brief Check whether the resource has newer state on a node after a migration
  * attempt
  *
  * \param[in]     rsc_id        Resource being checked
  * \param[in]     node_name     Node being checked
  * \param[in]     migrate_to    Any migrate_to event that is being compared to
  * \param[in]     migrate_from  Any migrate_from event that is being compared to
  * \param[in,out] scheduler     Scheduler data
  *
  * \return true if such a operation happened after event, false otherwise
  */
 static bool
 newer_state_after_migrate(const char *rsc_id, const char *node_name,
                           const xmlNode *migrate_to,
                           const xmlNode *migrate_from,
                           pcmk_scheduler_t *scheduler)
 {
     const xmlNode *xml_op = migrate_to;
     const char *source = NULL;
     const char *target = NULL;
     bool same_node = false;
 
     if (migrate_from) {
         xml_op = migrate_from;
     }
 
     source = crm_element_value(xml_op, PCMK__META_MIGRATE_SOURCE);
     target = crm_element_value(xml_op, PCMK__META_MIGRATE_TARGET);
 
     /* It's preferred to compare to the migrate event on the same node if
      * existing, since call ids are more reliable.
      */
     if (pcmk__str_eq(node_name, target, pcmk__str_casei)) {
         if (migrate_from) {
            xml_op = migrate_from;
            same_node = true;
 
         } else {
            xml_op = migrate_to;
         }
 
     } else if (pcmk__str_eq(node_name, source, pcmk__str_casei)) {
         if (migrate_to) {
            xml_op = migrate_to;
            same_node = true;
 
         } else {
            xml_op = migrate_from;
         }
     }
 
     /* If there's any newer non-monitor operation on the node, or any newer
      * probe/monitor operation on the node indicating it was not running there,
      * the migration events potentially no longer matter for the node.
      */
     return non_monitor_after(rsc_id, node_name, xml_op, same_node, scheduler)
            || monitor_not_running_after(rsc_id, node_name, xml_op, same_node,
                                         scheduler);
 }
 
 /*!
  * \internal
  * \brief Parse migration source and target node names from history entry
  *
  * \param[in]  entry        Resource history entry for a migration action
  * \param[in]  source_node  If not NULL, source must match this node
  * \param[in]  target_node  If not NULL, target must match this node
  * \param[out] source_name  Where to store migration source node name
  * \param[out] target_name  Where to store migration target node name
  *
  * \return Standard Pacemaker return code
  */
 static int
 get_migration_node_names(const xmlNode *entry, const pcmk_node_t *source_node,
                          const pcmk_node_t *target_node,
                          const char **source_name, const char **target_name)
 {
     *source_name = crm_element_value(entry, PCMK__META_MIGRATE_SOURCE);
     *target_name = crm_element_value(entry, PCMK__META_MIGRATE_TARGET);
     if ((*source_name == NULL) || (*target_name == NULL)) {
         pcmk__config_err("Ignoring resource history entry %s without "
                          PCMK__META_MIGRATE_SOURCE " and "
                          PCMK__META_MIGRATE_TARGET, pcmk__xe_id(entry));
         return pcmk_rc_unpack_error;
     }
 
     if ((source_node != NULL)
         && !pcmk__str_eq(*source_name, source_node->details->uname,
                          pcmk__str_casei|pcmk__str_null_matches)) {
         pcmk__config_err("Ignoring resource history entry %s because "
                          PCMK__META_MIGRATE_SOURCE "='%s' does not match %s",
                          pcmk__xe_id(entry), *source_name,
                          pcmk__node_name(source_node));
         return pcmk_rc_unpack_error;
     }
 
     if ((target_node != NULL)
         && !pcmk__str_eq(*target_name, target_node->details->uname,
                          pcmk__str_casei|pcmk__str_null_matches)) {
         pcmk__config_err("Ignoring resource history entry %s because "
                          PCMK__META_MIGRATE_TARGET "='%s' does not match %s",
                          pcmk__xe_id(entry), *target_name,
                          pcmk__node_name(target_node));
         return pcmk_rc_unpack_error;
     }
 
     return pcmk_rc_ok;
 }
 
 /*
  * \internal
  * \brief Add a migration source to a resource's list of dangling migrations
  *
  * If the migrate_to and migrate_from actions in a live migration both
  * succeeded, but there is no stop on the source, the migration is considered
  * "dangling." Add the source to the resource's dangling migration list, which
  * will be used to schedule a stop on the source without affecting the target.
  *
  * \param[in,out] rsc   Resource involved in migration
  * \param[in]     node  Migration source
  */
 static void
 add_dangling_migration(pcmk_resource_t *rsc, const pcmk_node_t *node)
 {
     pcmk__rsc_trace(rsc, "Dangling migration of %s requires stop on %s",
                     rsc->id, pcmk__node_name(node));
     rsc->role = pcmk_role_stopped;
     rsc->dangling_migrations = g_list_prepend(rsc->dangling_migrations,
                                               (gpointer) node);
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a successful migrate_to action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_to_success(struct action_history *history)
 {
     /* A complete migration sequence is:
      * 1. migrate_to on source node (which succeeded if we get to this function)
      * 2. migrate_from on target node
      * 3. stop on source node
      *
      * If no migrate_from has happened, the migration is considered to be
      * "partial". If the migrate_from succeeded but no stop has happened, the
      * migration is considered to be "dangling".
      *
      * If a successful migrate_to and stop have happened on the source node, we
      * still need to check for a partial migration, due to scenarios (easier to
      * produce with batch-limit=1) like:
      *
      * - A resource is migrating from node1 to node2, and a migrate_to is
      *   initiated for it on node1.
      *
      * - node2 goes into standby mode while the migrate_to is pending, which
      *   aborts the transition.
      *
      * - Upon completion of the migrate_to, a new transition schedules a stop
      *   on both nodes and a start on node1.
      *
      * - If the new transition is aborted for any reason while the resource is
      *   stopping on node1, the transition after that stop completes will see
      *   the migrate_to and stop on the source, but it's still a partial
      *   migration, and the resource must be stopped on node2 because it is
      *   potentially active there due to the migrate_to.
      *
      *   We also need to take into account that either node's history may be
      *   cleared at any point in the migration process.
      */
     int from_rc = PCMK_OCF_OK;
     int from_status = PCMK_EXEC_PENDING;
     pcmk_node_t *target_node = NULL;
     xmlNode *migrate_from = NULL;
     const char *source = NULL;
     const char *target = NULL;
     bool source_newer_op = false;
     bool target_newer_state = false;
     bool active_on_target = false;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, history->node, NULL, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     // Check for newer state on the source
     source_newer_op = non_monitor_after(history->rsc->id, source, history->xml,
                                         true, history->rsc->cluster);
 
     // Check for a migrate_from action from this source on the target
     migrate_from = find_lrm_op(history->rsc->id, PCMK_ACTION_MIGRATE_FROM,
                                target, source, -1, history->rsc->cluster);
     if (migrate_from != NULL) {
         if (source_newer_op) {
             /* There's a newer non-monitor operation on the source and a
              * migrate_from on the target, so this migrate_to is irrelevant to
              * the resource's state.
              */
             return;
         }
         crm_element_value_int(migrate_from, PCMK__XA_RC_CODE, &from_rc);
         crm_element_value_int(migrate_from, PCMK__XA_OP_STATUS, &from_status);
     }
 
     /* If the resource has newer state on both the source and target after the
      * migration events, this migrate_to is irrelevant to the resource's state.
      */
     target_newer_state = newer_state_after_migrate(history->rsc->id, target,
                                                    history->xml, migrate_from,
                                                    history->rsc->cluster);
     if (source_newer_op && target_newer_state) {
         return;
     }
 
     /* Check for dangling migration (migrate_from succeeded but stop not done).
      * We know there's no stop because we already returned if the target has a
      * migrate_from and the source has any newer non-monitor operation.
      */
     if ((from_rc == PCMK_OCF_OK) && (from_status == PCMK_EXEC_DONE)) {
         add_dangling_migration(history->rsc, history->node);
         return;
     }
 
     /* Without newer state, this migrate_to implies the resource is active.
      * (Clones are not allowed to migrate, so role can't be promoted.)
      */
     history->rsc->role = pcmk_role_started;
 
     target_node = pe_find_node(history->rsc->cluster->nodes, target);
     active_on_target = !target_newer_state && (target_node != NULL)
                        && target_node->details->online;
 
     if (from_status != PCMK_EXEC_PENDING) { // migrate_from failed on target
         if (active_on_target) {
             native_add_running(history->rsc, target_node, history->rsc->cluster,
                                TRUE);
         } else {
             // Mark resource as failed, require recovery, and prevent migration
             pcmk__set_rsc_flags(history->rsc,
                                 pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_migratable);
         }
         return;
     }
 
     // The migrate_from is pending, complete but erased, or to be scheduled
 
     /* If there is no history at all for the resource on an online target, then
      * it was likely cleaned. Just return, and we'll schedule a probe. Once we
      * have the probe result, it will be reflected in target_newer_state.
      */
     if ((target_node != NULL) && target_node->details->online
         && unknown_on_node(history->rsc, target)) {
         return;
     }
 
     if (active_on_target) {
         pcmk_node_t *source_node = pe_find_node(history->rsc->cluster->nodes,
                                                 source);
 
         native_add_running(history->rsc, target_node, history->rsc->cluster,
                            FALSE);
         if ((source_node != NULL) && source_node->details->online) {
             /* This is a partial migration: the migrate_to completed
              * successfully on the source, but the migrate_from has not
              * completed. Remember the source and target; if the newly
              * chosen target remains the same when we schedule actions
              * later, we may continue with the migration.
              */
             history->rsc->partial_migration_target = target_node;
             history->rsc->partial_migration_source = source_node;
         }
 
     } else if (!source_newer_op) {
         // Mark resource as failed, require recovery, and prevent migration
         pcmk__set_rsc_flags(history->rsc,
                             pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
         pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_migratable);
     }
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a failed migrate_to action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_to_failure(struct action_history *history)
 {
     xmlNode *target_migrate_from = NULL;
     const char *source = NULL;
     const char *target = NULL;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, history->node, NULL, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     history->rsc->role = pcmk_role_started;
 
     // Check for migrate_from on the target
     target_migrate_from = find_lrm_op(history->rsc->id,
                                       PCMK_ACTION_MIGRATE_FROM, target, source,
                                       PCMK_OCF_OK, history->rsc->cluster);
 
     if (/* If the resource state is unknown on the target, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(history->rsc, target)
         /* If the resource has newer state on the target after the migration
          * events, this migrate_to no longer matters for the target.
          */
         && !newer_state_after_migrate(history->rsc->id, target, history->xml,
                                       target_migrate_from,
                                       history->rsc->cluster)) {
         /* The resource has no newer state on the target, so assume it's still
          * active there.
          * (if it is up).
          */
         pcmk_node_t *target_node = pe_find_node(history->rsc->cluster->nodes,
                                                 target);
 
         if (target_node && target_node->details->online) {
             native_add_running(history->rsc, target_node, history->rsc->cluster,
                                FALSE);
         }
 
     } else if (!non_monitor_after(history->rsc->id, source, history->xml, true,
                                   history->rsc->cluster)) {
         /* We know the resource has newer state on the target, but this
          * migrate_to still matters for the source as long as there's no newer
          * non-monitor operation there.
          */
 
         // Mark node as having dangling migration so we can force a stop later
         history->rsc->dangling_migrations =
             g_list_prepend(history->rsc->dangling_migrations,
                            (gpointer) history->node);
     }
 }
 
 /*!
  * \internal
  * \brief Update resource role etc. after a failed migrate_from action
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 unpack_migrate_from_failure(struct action_history *history)
 {
     xmlNode *source_migrate_to = NULL;
     const char *source = NULL;
     const char *target = NULL;
 
     // Get source and target node names from XML
     if (get_migration_node_names(history->xml, NULL, history->node, &source,
                                  &target) != pcmk_rc_ok) {
         return;
     }
 
     /* If a migration failed, we have to assume the resource is active. Clones
      * are not allowed to migrate, so role can't be promoted.
      */
     history->rsc->role = pcmk_role_started;
 
     // Check for a migrate_to on the source
     source_migrate_to = find_lrm_op(history->rsc->id, PCMK_ACTION_MIGRATE_TO,
                                     source, target, PCMK_OCF_OK,
                                     history->rsc->cluster);
 
     if (/* If the resource state is unknown on the source, it will likely be
          * probed there.
          * Don't just consider it running there. We will get back here anyway in
          * case the probe detects it's running there.
          */
         !unknown_on_node(history->rsc, source)
         /* If the resource has newer state on the source after the migration
          * events, this migrate_from no longer matters for the source.
          */
         && !newer_state_after_migrate(history->rsc->id, source,
                                       source_migrate_to, history->xml,
                                       history->rsc->cluster)) {
         /* The resource has no newer state on the source, so assume it's still
          * active there (if it is up).
          */
         pcmk_node_t *source_node = pe_find_node(history->rsc->cluster->nodes,
                                                 source);
 
         if (source_node && source_node->details->online) {
             native_add_running(history->rsc, source_node, history->rsc->cluster,
                                TRUE);
         }
     }
 }
 
 /*!
  * \internal
  * \brief Add an action to cluster's list of failed actions
  *
  * \param[in,out] history  Parsed action result history
  */
 static void
 record_failed_op(struct action_history *history)
 {
     if (!(history->node->details->online)) {
         return;
     }
 
     for (const xmlNode *xIter = history->rsc->cluster->failed->children;
          xIter != NULL; xIter = xIter->next) {
 
         const char *key = pcmk__xe_history_key(xIter);
         const char *uname = crm_element_value(xIter, PCMK_XA_UNAME);
 
         if (pcmk__str_eq(history->key, key, pcmk__str_none)
             && pcmk__str_eq(uname, history->node->details->uname,
                             pcmk__str_casei)) {
             crm_trace("Skipping duplicate entry %s on %s",
                       history->key, pcmk__node_name(history->node));
             return;
         }
     }
 
     crm_trace("Adding entry for %s on %s to failed action list",
               history->key, pcmk__node_name(history->node));
     crm_xml_add(history->xml, PCMK_XA_UNAME, history->node->details->uname);
     crm_xml_add(history->xml, PCMK__XA_RSC_ID, history->rsc->id);
     add_node_copy(history->rsc->cluster->failed, history->xml);
 }
 
 static char *
 last_change_str(const xmlNode *xml_op)
 {
     time_t when;
     char *result = NULL;
 
     if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE,
                                 &when) == pcmk_ok) {
         char *when_s = pcmk__epoch2str(&when, 0);
         const char *p = strchr(when_s, ' ');
 
         // Skip day of week to make message shorter
         if ((p != NULL) && (*(++p) != '\0')) {
             result = strdup(p);
             CRM_ASSERT(result != NULL);
         }
         free(when_s);
     }
 
     if (result == NULL) {
         result = strdup("unknown time");
         CRM_ASSERT(result != NULL);
     }
 
     return result;
 }
 
 /*!
  * \internal
  * \brief Compare two on-fail values
  *
  * \param[in] first   One on-fail value to compare
  * \param[in] second  The other on-fail value to compare
  *
  * \return A negative number if second is more severe than first, zero if they
  *         are equal, or a positive number if first is more severe than second.
  * \note This is only needed until the action_fail_response values can be
  *       renumbered at the next API compatibility break.
  */
 static int
 cmp_on_fail(enum action_fail_response first, enum action_fail_response second)
 {
     switch (first) {
         case pcmk_on_fail_demote:
             switch (second) {
                 case pcmk_on_fail_ignore:
                     return 1;
                 case pcmk_on_fail_demote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case pcmk_on_fail_reset_remote:
             switch (second) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                     return 1;
                 case pcmk_on_fail_reset_remote:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         case pcmk_on_fail_restart_container:
             switch (second) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                 case pcmk_on_fail_reset_remote:
                     return 1;
                 case pcmk_on_fail_restart_container:
                     return 0;
                 default:
                     return -1;
             }
             break;
 
         default:
             break;
     }
     switch (second) {
         case pcmk_on_fail_demote:
             return (first == pcmk_on_fail_ignore)? -1 : 1;
 
         case pcmk_on_fail_reset_remote:
             switch (first) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         case pcmk_on_fail_restart_container:
             switch (first) {
                 case pcmk_on_fail_ignore:
                 case pcmk_on_fail_demote:
                 case pcmk_on_fail_restart:
                 case pcmk_on_fail_reset_remote:
                     return -1;
                 default:
                     return 1;
             }
             break;
 
         default:
             break;
     }
     return first - second;
 }
 
 /*!
  * \internal
  * \brief Ban a resource (or its clone if an anonymous instance) from all nodes
  *
  * \param[in,out] rsc  Resource to ban
  */
 static void
 ban_from_all_nodes(pcmk_resource_t *rsc)
 {
-    int score = -INFINITY;
+    int score = -PCMK_SCORE_INFINITY;
     pcmk_resource_t *fail_rsc = rsc;
 
     if (fail_rsc->parent != NULL) {
         pcmk_resource_t *parent = uber_parent(fail_rsc);
 
         if (pcmk__is_anonymous_clone(parent)) {
             /* For anonymous clones, if an operation with
              * PCMK_META_ON_FAIL=PCMK_VALUE_STOP fails for any instance, the
              * entire clone must stop.
              */
             fail_rsc = parent;
         }
     }
 
     // Ban the resource from all nodes
     crm_notice("%s will not be started under current conditions", fail_rsc->id);
     if (fail_rsc->allowed_nodes != NULL) {
         g_hash_table_destroy(fail_rsc->allowed_nodes);
     }
     fail_rsc->allowed_nodes = pe__node_list2table(rsc->cluster->nodes);
     g_hash_table_foreach(fail_rsc->allowed_nodes, set_node_score, &score);
 }
 
 /*!
  * \internal
  * \brief Get configured failure handling and role after failure for an action
  *
  * \param[in,out] history    Unpacked action history entry
  * \param[out]    on_fail    Where to set configured failure handling
  * \param[out]    fail_role  Where to set to role after failure
  */
 static void
 unpack_failure_handling(struct action_history *history,
                         enum action_fail_response *on_fail,
                         enum rsc_role_e *fail_role)
 {
     xmlNode *config = pcmk__find_action_config(history->rsc, history->task,
                                                history->interval_ms, true);
 
     GHashTable *meta = pcmk__unpack_action_meta(history->rsc, history->node,
                                                 history->task,
                                                 history->interval_ms, config);
 
     const char *on_fail_str = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
 
     *on_fail = pcmk__parse_on_fail(history->rsc, history->task,
                                    history->interval_ms, on_fail_str);
     *fail_role = pcmk__role_after_failure(history->rsc, history->task, *on_fail,
                                           meta);
     g_hash_table_destroy(meta);
 }
 
 /*!
  * \internal
  * \brief Update resource role, failure handling, etc., after a failed action
  *
  * \param[in,out] history         Parsed action result history
  * \param[in]     config_on_fail  Action failure handling from configuration
  * \param[in]     fail_role       Resource's role after failure of this action
  * \param[out]    last_failure    This will be set to the history XML
  * \param[in,out] on_fail         Actual handling of action result
  */
 static void
 unpack_rsc_op_failure(struct action_history *history,
                       enum action_fail_response config_on_fail,
                       enum rsc_role_e fail_role, xmlNode **last_failure,
                       enum action_fail_response *on_fail)
 {
     bool is_probe = false;
     char *last_change_s = NULL;
 
     *last_failure = history->xml;
 
     is_probe = pcmk_xe_is_probe(history->xml);
     last_change_s = last_change_str(history->xml);
 
     if (!pcmk_is_set(history->rsc->cluster->flags, pcmk_sched_symmetric_cluster)
         && (history->exit_status == PCMK_OCF_NOT_INSTALLED)) {
         crm_trace("Unexpected result (%s%s%s) was recorded for "
                   "%s of %s on %s at %s " CRM_XS " exit-status=%d id=%s",
                   services_ocf_exitcode_str(history->exit_status),
                   (pcmk__str_empty(history->exit_reason)? "" : ": "),
                   pcmk__s(history->exit_reason, ""),
                   (is_probe? "probe" : history->task), history->rsc->id,
                   pcmk__node_name(history->node), last_change_s,
                   history->exit_status, history->id);
     } else {
         pcmk__sched_warn("Unexpected result (%s%s%s) was recorded for %s of "
                          "%s on %s at %s " CRM_XS " exit-status=%d id=%s",
                          services_ocf_exitcode_str(history->exit_status),
                          (pcmk__str_empty(history->exit_reason)? "" : ": "),
                          pcmk__s(history->exit_reason, ""),
                          (is_probe? "probe" : history->task), history->rsc->id,
                          pcmk__node_name(history->node), last_change_s,
                          history->exit_status, history->id);
 
         if (is_probe && (history->exit_status != PCMK_OCF_OK)
             && (history->exit_status != PCMK_OCF_NOT_RUNNING)
             && (history->exit_status != PCMK_OCF_RUNNING_PROMOTED)) {
 
             /* A failed (not just unexpected) probe result could mean the user
              * didn't know resources will be probed even where they can't run.
              */
             crm_notice("If it is not possible for %s to run on %s, see "
                        "the " PCMK_XA_RESOURCE_DISCOVERY " option for location "
                        "constraints",
                        history->rsc->id, pcmk__node_name(history->node));
         }
 
         record_failed_op(history);
     }
 
     free(last_change_s);
 
     if (cmp_on_fail(*on_fail, config_on_fail) < 0) {
         pcmk__rsc_trace(history->rsc, "on-fail %s -> %s for %s",
                         pcmk_on_fail_text(*on_fail),
                         pcmk_on_fail_text(config_on_fail), history->key);
         *on_fail = config_on_fail;
     }
 
     if (strcmp(history->task, PCMK_ACTION_STOP) == 0) {
-        resource_location(history->rsc, history->node, -INFINITY,
+        resource_location(history->rsc, history->node, -PCMK_SCORE_INFINITY,
                           "__stop_fail__", history->rsc->cluster);
 
     } else if (strcmp(history->task, PCMK_ACTION_MIGRATE_TO) == 0) {
         unpack_migrate_to_failure(history);
 
     } else if (strcmp(history->task, PCMK_ACTION_MIGRATE_FROM) == 0) {
         unpack_migrate_from_failure(history);
 
     } else if (strcmp(history->task, PCMK_ACTION_PROMOTE) == 0) {
         history->rsc->role = pcmk_role_promoted;
 
     } else if (strcmp(history->task, PCMK_ACTION_DEMOTE) == 0) {
         if (config_on_fail == pcmk_on_fail_block) {
             history->rsc->role = pcmk_role_promoted;
             pe__set_next_role(history->rsc, pcmk_role_stopped,
                               "demote with on-fail=block");
 
         } else if (history->exit_status == PCMK_OCF_NOT_RUNNING) {
             history->rsc->role = pcmk_role_stopped;
 
         } else {
             /* Staying in the promoted role would put the scheduler and
              * controller into a loop. Setting the role to unpromoted is not
              * dangerous because the resource will be stopped as part of
              * recovery, and any promotion will be ordered after that stop.
              */
             history->rsc->role = pcmk_role_unpromoted;
         }
     }
 
     if (is_probe && (history->exit_status == PCMK_OCF_NOT_INSTALLED)) {
         /* leave stopped */
         pcmk__rsc_trace(history->rsc, "Leaving %s stopped", history->rsc->id);
         history->rsc->role = pcmk_role_stopped;
 
     } else if (history->rsc->role < pcmk_role_started) {
         pcmk__rsc_trace(history->rsc, "Setting %s active", history->rsc->id);
         set_active(history->rsc);
     }
 
     pcmk__rsc_trace(history->rsc,
                     "Resource %s: role=%s unclean=%s on_fail=%s fail_role=%s",
                     history->rsc->id, pcmk_role_text(history->rsc->role),
                     pcmk__btoa(history->node->details->unclean),
                     pcmk_on_fail_text(config_on_fail),
                     pcmk_role_text(fail_role));
 
     if ((fail_role != pcmk_role_started)
         && (history->rsc->next_role < fail_role)) {
         pe__set_next_role(history->rsc, fail_role, "failure");
     }
 
     if (fail_role == pcmk_role_stopped) {
         ban_from_all_nodes(history->rsc);
     }
 }
 
 /*!
  * \internal
  * \brief Block a resource with a failed action if it cannot be recovered
  *
  * If resource action is a failed stop and fencing is not possible, mark the
  * resource as unmanaged and blocked, since recovery cannot be done.
  *
  * \param[in,out] history  Parsed action history entry
  */
 static void
 block_if_unrecoverable(struct action_history *history)
 {
     char *last_change_s = NULL;
 
     if (strcmp(history->task, PCMK_ACTION_STOP) != 0) {
         return; // All actions besides stop are always recoverable
     }
     if (pe_can_fence(history->node->details->data_set, history->node)) {
         return; // Failed stops are recoverable via fencing
     }
 
     last_change_s = last_change_str(history->xml);
     pcmk__sched_err("No further recovery can be attempted for %s "
                     "because %s on %s failed (%s%s%s) at %s "
                     CRM_XS " rc=%d id=%s",
                     history->rsc->id, history->task,
                     pcmk__node_name(history->node),
                     services_ocf_exitcode_str(history->exit_status),
                     (pcmk__str_empty(history->exit_reason)? "" : ": "),
                     pcmk__s(history->exit_reason, ""),
                     last_change_s, history->exit_status, history->id);
 
     free(last_change_s);
 
     pcmk__clear_rsc_flags(history->rsc, pcmk_rsc_managed);
     pcmk__set_rsc_flags(history->rsc, pcmk_rsc_blocked);
 }
 
 /*!
  * \internal
  * \brief Update action history's execution status and why
  *
  * \param[in,out] history  Parsed action history entry
  * \param[out]    why      Where to store reason for update
  * \param[in]     value    New value
  * \param[in]     reason   Description of why value was changed
  */
 static inline void
 remap_because(struct action_history *history, const char **why, int value,
               const char *reason)
 {
     if (history->execution_status != value) {
         history->execution_status = value;
         *why = reason;
     }
 }
 
 /*!
  * \internal
  * \brief Remap informational monitor results and operation status
  *
  * For the monitor results, certain OCF codes are for providing extended information
  * to the user about services that aren't yet failed but not entirely healthy either.
  * These must be treated as the "normal" result by Pacemaker.
  *
  * For operation status, the action result can be used to determine an appropriate
  * status for the purposes of responding to the action.  The status provided by the
  * executor is not directly usable since the executor does not know what was expected.
  *
  * \param[in,out] history  Parsed action history entry
  * \param[in,out] on_fail  What should be done about the result
  * \param[in]     expired  Whether result is expired
  *
  * \note If the result is remapped and the node is not shutting down or failed,
  *       the operation will be recorded in the scheduler data's list of failed
  *       operations to highlight it for the user.
  *
  * \note This may update the resource's current and next role.
  */
 static void
 remap_operation(struct action_history *history,
                 enum action_fail_response *on_fail, bool expired)
 {
     bool is_probe = false;
     int orig_exit_status = history->exit_status;
     int orig_exec_status = history->execution_status;
     const char *why = NULL;
     const char *task = history->task;
 
     // Remap degraded results to their successful counterparts
     history->exit_status = pcmk__effective_rc(history->exit_status);
     if (history->exit_status != orig_exit_status) {
         why = "degraded result";
         if (!expired && (!history->node->details->shutdown
                          || history->node->details->online)) {
             record_failed_op(history);
         }
     }
 
     if (!pcmk__is_bundled(history->rsc)
         && pcmk_xe_mask_probe_failure(history->xml)
         && ((history->execution_status != PCMK_EXEC_DONE)
             || (history->exit_status != PCMK_OCF_NOT_RUNNING))) {
         history->execution_status = PCMK_EXEC_DONE;
         history->exit_status = PCMK_OCF_NOT_RUNNING;
         why = "equivalent probe result";
     }
 
     /* If the executor reported an execution status of anything but done or
      * error, consider that final. But for done or error, we know better whether
      * it should be treated as a failure or not, because we know the expected
      * result.
      */
     switch (history->execution_status) {
         case PCMK_EXEC_DONE:
         case PCMK_EXEC_ERROR:
             break;
 
         // These should be treated as node-fatal
         case PCMK_EXEC_NO_FENCE_DEVICE:
         case PCMK_EXEC_NO_SECRETS:
             remap_because(history, &why, PCMK_EXEC_ERROR_HARD,
                           "node-fatal error");
             goto remap_done;
 
         default:
             goto remap_done;
     }
 
     is_probe = pcmk_xe_is_probe(history->xml);
     if (is_probe) {
         task = "probe";
     }
 
     if (history->expected_exit_status < 0) {
         /* Pre-1.0 Pacemaker versions, and Pacemaker 1.1.6 or earlier with
          * Heartbeat 2.0.7 or earlier as the cluster layer, did not include the
          * expected exit status in the transition key, which (along with the
          * similar case of a corrupted transition key in the CIB) will be
          * reported to this function as -1. Pacemaker 2.0+ does not support
          * rolling upgrades from those versions or processing of saved CIB files
          * from those versions, so we do not need to care much about this case.
          */
         remap_because(history, &why, PCMK_EXEC_ERROR,
                       "obsolete history format");
         pcmk__config_warn("Expected result not found for %s on %s "
                           "(corrupt or obsolete CIB?)",
                           history->key, pcmk__node_name(history->node));
 
     } else if (history->exit_status == history->expected_exit_status) {
         remap_because(history, &why, PCMK_EXEC_DONE, "expected result");
 
     } else {
         remap_because(history, &why, PCMK_EXEC_ERROR, "unexpected result");
         pcmk__rsc_debug(history->rsc,
                         "%s on %s: expected %d (%s), got %d (%s%s%s)",
                         history->key, pcmk__node_name(history->node),
                         history->expected_exit_status,
                         services_ocf_exitcode_str(history->expected_exit_status),
                         history->exit_status,
                         services_ocf_exitcode_str(history->exit_status),
                         (pcmk__str_empty(history->exit_reason)? "" : ": "),
                         pcmk__s(history->exit_reason, ""));
     }
 
     switch (history->exit_status) {
         case PCMK_OCF_OK:
             if (is_probe
                 && (history->expected_exit_status == PCMK_OCF_NOT_RUNNING)) {
                 char *last_change_s = last_change_str(history->xml);
 
                 remap_because(history, &why, PCMK_EXEC_DONE, "probe");
                 pcmk__rsc_info(history->rsc,
                                "Probe found %s active on %s at %s",
                                history->rsc->id, pcmk__node_name(history->node),
                                last_change_s);
                 free(last_change_s);
             }
             break;
 
         case PCMK_OCF_NOT_RUNNING:
             if (is_probe
                 || (history->expected_exit_status == history->exit_status)
                 || !pcmk_is_set(history->rsc->flags, pcmk_rsc_managed)) {
 
                 /* For probes, recurring monitors for the Stopped role, and
                  * unmanaged resources, "not running" is not considered a
                  * failure.
                  */
                 remap_because(history, &why, PCMK_EXEC_DONE, "exit status");
                 history->rsc->role = pcmk_role_stopped;
                 *on_fail = pcmk_on_fail_ignore;
                 pe__set_next_role(history->rsc, pcmk_role_unknown,
                                   "not running");
             }
             break;
 
         case PCMK_OCF_RUNNING_PROMOTED:
             if (is_probe
                 && (history->exit_status != history->expected_exit_status)) {
                 char *last_change_s = last_change_str(history->xml);
 
                 remap_because(history, &why, PCMK_EXEC_DONE, "probe");
                 pcmk__rsc_info(history->rsc,
                                "Probe found %s active and promoted on %s at %s",
                                 history->rsc->id,
                                 pcmk__node_name(history->node), last_change_s);
                 free(last_change_s);
             }
             if (!expired
                 || (history->exit_status == history->expected_exit_status)) {
                 history->rsc->role = pcmk_role_promoted;
             }
             break;
 
         case PCMK_OCF_FAILED_PROMOTED:
             if (!expired) {
                 history->rsc->role = pcmk_role_promoted;
             }
             remap_because(history, &why, PCMK_EXEC_ERROR, "exit status");
             break;
 
         case PCMK_OCF_NOT_CONFIGURED:
             remap_because(history, &why, PCMK_EXEC_ERROR_FATAL, "exit status");
             break;
 
         case PCMK_OCF_UNIMPLEMENT_FEATURE:
             {
                 guint interval_ms = 0;
                 crm_element_value_ms(history->xml, PCMK_META_INTERVAL,
                                      &interval_ms);
 
                 if (interval_ms == 0) {
                     if (!expired) {
                         block_if_unrecoverable(history);
                     }
                     remap_because(history, &why, PCMK_EXEC_ERROR_HARD,
                                   "exit status");
                 } else {
                     remap_because(history, &why, PCMK_EXEC_NOT_SUPPORTED,
                                   "exit status");
                 }
             }
             break;
 
         case PCMK_OCF_NOT_INSTALLED:
         case PCMK_OCF_INVALID_PARAM:
         case PCMK_OCF_INSUFFICIENT_PRIV:
             if (!expired) {
                 block_if_unrecoverable(history);
             }
             remap_because(history, &why, PCMK_EXEC_ERROR_HARD, "exit status");
             break;
 
         default:
             if (history->execution_status == PCMK_EXEC_DONE) {
                 char *last_change_s = last_change_str(history->xml);
 
                 crm_info("Treating unknown exit status %d from %s of %s "
                          "on %s at %s as failure",
                          history->exit_status, task, history->rsc->id,
                          pcmk__node_name(history->node), last_change_s);
                 remap_because(history, &why, PCMK_EXEC_ERROR,
                               "unknown exit status");
                 free(last_change_s);
             }
             break;
     }
 
 remap_done:
     if (why != NULL) {
         pcmk__rsc_trace(history->rsc,
                         "Remapped %s result from [%s: %s] to [%s: %s] "
                         "because of %s",
                         history->key, pcmk_exec_status_str(orig_exec_status),
                         crm_exit_str(orig_exit_status),
                         pcmk_exec_status_str(history->execution_status),
                         crm_exit_str(history->exit_status), why);
     }
 }
 
 // return TRUE if start or monitor last failure but parameters changed
 static bool
 should_clear_for_param_change(const xmlNode *xml_op, const char *task,
                               pcmk_resource_t *rsc, pcmk_node_t *node)
 {
     if (pcmk__str_any_of(task, PCMK_ACTION_START, PCMK_ACTION_MONITOR, NULL)) {
         if (pe__bundle_needs_remote_name(rsc)) {
             /* We haven't allocated resources yet, so we can't reliably
              * substitute addr parameters for the REMOTE_CONTAINER_HACK.
              * When that's needed, defer the check until later.
              */
             pe__add_param_check(xml_op, rsc, node, pcmk__check_last_failure,
                                 rsc->cluster);
 
         } else {
             pcmk__op_digest_t *digest_data = NULL;
 
             digest_data = rsc_action_digest_cmp(rsc, xml_op, node,
                                                 rsc->cluster);
             switch (digest_data->rc) {
                 case pcmk__digest_unknown:
                     crm_trace("Resource %s history entry %s on %s"
                               " has no digest to compare",
                               rsc->id, pcmk__xe_history_key(xml_op),
                               node->details->id);
                     break;
                 case pcmk__digest_match:
                     break;
                 default:
                     return TRUE;
             }
         }
     }
     return FALSE;
 }
 
 // Order action after fencing of remote node, given connection rsc
 static void
 order_after_remote_fencing(pcmk_action_t *action, pcmk_resource_t *remote_conn,
                            pcmk_scheduler_t *scheduler)
 {
     pcmk_node_t *remote_node = pe_find_node(scheduler->nodes, remote_conn->id);
 
     if (remote_node) {
         pcmk_action_t *fence = pe_fence_op(remote_node, NULL, TRUE, NULL,
                                            FALSE, scheduler);
 
         order_actions(fence, action, pcmk__ar_first_implies_then);
     }
 }
 
 static bool
 should_ignore_failure_timeout(const pcmk_resource_t *rsc, const char *task,
                               guint interval_ms, bool is_last_failure)
 {
     /* Clearing failures of recurring monitors has special concerns. The
      * executor reports only changes in the monitor result, so if the
      * monitor is still active and still getting the same failure result,
      * that will go undetected after the failure is cleared.
      *
      * Also, the operation history will have the time when the recurring
      * monitor result changed to the given code, not the time when the
      * result last happened.
      *
      * @TODO We probably should clear such failures only when the failure
      * timeout has passed since the last occurrence of the failed result.
      * However we don't record that information. We could maybe approximate
      * that by clearing only if there is a more recent successful monitor or
      * stop result, but we don't even have that information at this point
      * since we are still unpacking the resource's operation history.
      *
      * This is especially important for remote connection resources with a
      * reconnect interval, so in that case, we skip clearing failures
      * if the remote node hasn't been fenced.
      */
     if (rsc->remote_reconnect_ms
         && pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)
         && (interval_ms != 0)
         && pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
 
         pcmk_node_t *remote_node = pe_find_node(rsc->cluster->nodes, rsc->id);
 
         if (remote_node && !remote_node->details->remote_was_fenced) {
             if (is_last_failure) {
                 crm_info("Waiting to clear monitor failure for remote node %s"
                          " until fencing has occurred", rsc->id);
             }
             return TRUE;
         }
     }
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Check operation age and schedule failure clearing when appropriate
  *
  * This function has two distinct purposes. The first is to check whether an
  * operation history entry is expired (i.e. the resource has a failure timeout,
  * the entry is older than the timeout, and the resource either has no fail
  * count or its fail count is entirely older than the timeout). The second is to
  * schedule fail count clearing when appropriate (i.e. the operation is expired
  * and either the resource has an expired fail count or the operation is a
  * last_failure for a remote connection resource with a reconnect interval,
  * or the operation is a last_failure for a start or monitor operation and the
  * resource's parameters have changed since the operation).
  *
  * \param[in,out] history  Parsed action result history
  *
  * \return true if operation history entry is expired, otherwise false
  */
 static bool
 check_operation_expiry(struct action_history *history)
 {
     bool expired = false;
     bool is_last_failure = pcmk__ends_with(history->id, "_last_failure_0");
     time_t last_run = 0;
     int unexpired_fail_count = 0;
     const char *clear_reason = NULL;
 
     if (history->execution_status == PCMK_EXEC_NOT_INSTALLED) {
         pcmk__rsc_trace(history->rsc,
                         "Resource history entry %s on %s is not expired: "
                         "Not Installed does not expire",
                         history->id, pcmk__node_name(history->node));
         return false; // "Not installed" must always be cleared manually
     }
 
     if ((history->rsc->failure_timeout > 0)
         && (crm_element_value_epoch(history->xml, PCMK_XA_LAST_RC_CHANGE,
                                     &last_run) == 0)) {
 
         /* Resource has a PCMK_META_FAILURE_TIMEOUT and history entry has a
          * timestamp
          */
 
         time_t now = get_effective_time(history->rsc->cluster);
         time_t last_failure = 0;
 
         // Is this particular operation history older than the failure timeout?
         if ((now >= (last_run + history->rsc->failure_timeout))
             && !should_ignore_failure_timeout(history->rsc, history->task,
                                               history->interval_ms,
                                               is_last_failure)) {
             expired = true;
         }
 
         // Does the resource as a whole have an unexpired fail count?
         unexpired_fail_count = pe_get_failcount(history->node, history->rsc,
                                                 &last_failure,
                                                 pcmk__fc_effective,
                                                 history->xml);
 
         // Update scheduler recheck time according to *last* failure
         crm_trace("%s@%lld is %sexpired @%lld with unexpired_failures=%d timeout=%ds"
                   " last-failure@%lld",
                   history->id, (long long) last_run, (expired? "" : "not "),
                   (long long) now, unexpired_fail_count,
                   history->rsc->failure_timeout, (long long) last_failure);
         last_failure += history->rsc->failure_timeout + 1;
         if (unexpired_fail_count && (now < last_failure)) {
             pe__update_recheck_time(last_failure, history->rsc->cluster,
                                     "fail count expiration");
         }
     }
 
     if (expired) {
         if (pe_get_failcount(history->node, history->rsc, NULL,
                              pcmk__fc_default, history->xml)) {
             // There is a fail count ignoring timeout
 
             if (unexpired_fail_count == 0) {
                 // There is no fail count considering timeout
                 clear_reason = "it expired";
 
             } else {
                 /* This operation is old, but there is an unexpired fail count.
                  * In a properly functioning cluster, this should only be
                  * possible if this operation is not a failure (otherwise the
                  * fail count should be expired too), so this is really just a
                  * failsafe.
                  */
                 pcmk__rsc_trace(history->rsc,
                                 "Resource history entry %s on %s is not "
                                 "expired: Unexpired fail count",
                                 history->id, pcmk__node_name(history->node));
                 expired = false;
             }
 
         } else if (is_last_failure
                    && (history->rsc->remote_reconnect_ms != 0)) {
             /* Clear any expired last failure when reconnect interval is set,
              * even if there is no fail count.
              */
             clear_reason = "reconnect interval is set";
         }
     }
 
     if (!expired && is_last_failure
         && should_clear_for_param_change(history->xml, history->task,
                                          history->rsc, history->node)) {
         clear_reason = "resource parameters have changed";
     }
 
     if (clear_reason != NULL) {
         pcmk_action_t *clear_op = NULL;
 
         // Schedule clearing of the fail count
         clear_op = pe__clear_failcount(history->rsc, history->node,
                                        clear_reason, history->rsc->cluster);
 
         if (pcmk_is_set(history->rsc->cluster->flags,
                         pcmk_sched_fencing_enabled)
             && (history->rsc->remote_reconnect_ms != 0)) {
             /* If we're clearing a remote connection due to a reconnect
              * interval, we want to wait until any scheduled fencing
              * completes.
              *
              * We could limit this to remote_node->details->unclean, but at
              * this point, that's always true (it won't be reliable until
              * after unpack_node_history() is done).
              */
             crm_info("Clearing %s failure will wait until any scheduled "
                      "fencing of %s completes",
                      history->task, history->rsc->id);
             order_after_remote_fencing(clear_op, history->rsc,
                                        history->rsc->cluster);
         }
     }
 
     if (expired && (history->interval_ms == 0)
         && pcmk__str_eq(history->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
         switch (history->exit_status) {
             case PCMK_OCF_OK:
             case PCMK_OCF_NOT_RUNNING:
             case PCMK_OCF_RUNNING_PROMOTED:
             case PCMK_OCF_DEGRADED:
             case PCMK_OCF_DEGRADED_PROMOTED:
                 // Don't expire probes that return these values
                 pcmk__rsc_trace(history->rsc,
                                 "Resource history entry %s on %s is not "
                                 "expired: Probe result",
                              history->id, pcmk__node_name(history->node));
                 expired = false;
                 break;
         }
     }
 
     return expired;
 }
 
 int
 pe__target_rc_from_xml(const xmlNode *xml_op)
 {
     int target_rc = 0;
     const char *key = crm_element_value(xml_op, PCMK__XA_TRANSITION_KEY);
 
     if (key == NULL) {
         return -1;
     }
     decode_transition_key(key, NULL, NULL, NULL, &target_rc);
     return target_rc;
 }
 
 /*!
  * \internal
  * \brief Update a resource's state for an action result
  *
  * \param[in,out] history       Parsed action history entry
  * \param[in]     exit_status   Exit status to base new state on
  * \param[in]     last_failure  Resource's last_failure entry, if known
  * \param[in,out] on_fail       Resource's current failure handling
  */
 static void
 update_resource_state(struct action_history *history, int exit_status,
                       const xmlNode *last_failure,
                       enum action_fail_response *on_fail)
 {
     bool clear_past_failure = false;
 
     if ((exit_status == PCMK_OCF_NOT_INSTALLED)
         || (!pcmk__is_bundled(history->rsc)
             && pcmk_xe_mask_probe_failure(history->xml))) {
         history->rsc->role = pcmk_role_stopped;
 
     } else if (exit_status == PCMK_OCF_NOT_RUNNING) {
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MONITOR,
                             pcmk__str_none)) {
         if ((last_failure != NULL)
             && pcmk__str_eq(history->key, pcmk__xe_history_key(last_failure),
                             pcmk__str_none)) {
             clear_past_failure = true;
         }
         if (history->rsc->role < pcmk_role_started) {
             set_active(history->rsc);
         }
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_START, pcmk__str_none)) {
         history->rsc->role = pcmk_role_started;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_STOP, pcmk__str_none)) {
         history->rsc->role = pcmk_role_stopped;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_PROMOTE,
                             pcmk__str_none)) {
         history->rsc->role = pcmk_role_promoted;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_DEMOTE,
                             pcmk__str_none)) {
         if (*on_fail == pcmk_on_fail_demote) {
             /* Demote clears an error only if
              * PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE
              */
             clear_past_failure = true;
         }
         history->rsc->role = pcmk_role_unpromoted;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MIGRATE_FROM,
                             pcmk__str_none)) {
         history->rsc->role = pcmk_role_started;
         clear_past_failure = true;
 
     } else if (pcmk__str_eq(history->task, PCMK_ACTION_MIGRATE_TO,
                             pcmk__str_none)) {
         unpack_migrate_to_success(history);
 
     } else if (history->rsc->role < pcmk_role_started) {
         pcmk__rsc_trace(history->rsc, "%s active on %s",
                         history->rsc->id, pcmk__node_name(history->node));
         set_active(history->rsc);
     }
 
     if (!clear_past_failure) {
         return;
     }
 
     switch (*on_fail) {
         case pcmk_on_fail_stop:
         case pcmk_on_fail_ban:
         case pcmk_on_fail_standby_node:
         case pcmk_on_fail_fence_node:
             pcmk__rsc_trace(history->rsc,
                             "%s (%s) is not cleared by a completed %s",
                             history->rsc->id, pcmk_on_fail_text(*on_fail),
                             history->task);
             break;
 
         case pcmk_on_fail_block:
         case pcmk_on_fail_ignore:
         case pcmk_on_fail_demote:
         case pcmk_on_fail_restart:
         case pcmk_on_fail_restart_container:
             *on_fail = pcmk_on_fail_ignore;
             pe__set_next_role(history->rsc, pcmk_role_unknown,
                               "clear past failures");
             break;
 
         case pcmk_on_fail_reset_remote:
             if (history->rsc->remote_reconnect_ms == 0) {
                 /* With no reconnect interval, the connection is allowed to
                  * start again after the remote node is fenced and
                  * completely stopped. (With a reconnect interval, we wait
                  * for the failure to be cleared entirely before attempting
                  * to reconnect.)
                  */
                 *on_fail = pcmk_on_fail_ignore;
                 pe__set_next_role(history->rsc, pcmk_role_unknown,
                                   "clear past failures and reset remote");
             }
             break;
     }
 }
 
 /*!
  * \internal
  * \brief Check whether a given history entry matters for resource state
  *
  * \param[in] history  Parsed action history entry
  *
  * \return true if action can affect resource state, otherwise false
  */
 static inline bool
 can_affect_state(struct action_history *history)
 {
 #if 0
     /* @COMPAT It might be better to parse only actions we know we're interested
      * in, rather than exclude a couple we don't. However that would be a
      * behavioral change that should be done at a major or minor series release.
      * Currently, unknown operations can affect whether a resource is considered
      * active and/or failed.
      */
      return pcmk__str_any_of(history->task, PCMK_ACTION_MONITOR,
                              PCMK_ACTION_START, PCMK_ACTION_STOP,
                              PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
                              PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
                              "asyncmon", NULL);
 #else
      return !pcmk__str_any_of(history->task, PCMK_ACTION_NOTIFY,
                               PCMK_ACTION_META_DATA, NULL);
 #endif
 }
 
 /*!
  * \internal
  * \brief Unpack execution/exit status and exit reason from a history entry
  *
  * \param[in,out] history  Action history entry to unpack
  *
  * \return Standard Pacemaker return code
  */
 static int
 unpack_action_result(struct action_history *history)
 {
     if ((crm_element_value_int(history->xml, PCMK__XA_OP_STATUS,
                                &(history->execution_status)) < 0)
         || (history->execution_status < PCMK_EXEC_PENDING)
         || (history->execution_status > PCMK_EXEC_MAX)
         || (history->execution_status == PCMK_EXEC_CANCELLED)) {
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "with invalid " PCMK__XA_OP_STATUS " '%s'",
                          history->id, history->rsc->id,
                          pcmk__node_name(history->node),
                          pcmk__s(crm_element_value(history->xml,
                                                    PCMK__XA_OP_STATUS),
                                  ""));
         return pcmk_rc_unpack_error;
     }
     if ((crm_element_value_int(history->xml, PCMK__XA_RC_CODE,
                                &(history->exit_status)) < 0)
         || (history->exit_status < 0) || (history->exit_status > CRM_EX_MAX)) {
 #if 0
         /* @COMPAT We should ignore malformed entries, but since that would
          * change behavior, it should be done at a major or minor series
          * release.
          */
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "with invalid " PCMK__XA_RC_CODE " '%s'",
                          history->id, history->rsc->id,
                          pcmk__node_name(history->node),
                          pcmk__s(crm_element_value(history->xml,
                                                    PCMK__XA_RC_CODE),
                                  ""));
         return pcmk_rc_unpack_error;
 #else
         history->exit_status = CRM_EX_ERROR;
 #endif
     }
     history->exit_reason = crm_element_value(history->xml, PCMK_XA_EXIT_REASON);
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Process an action history entry whose result expired
  *
  * \param[in,out] history           Parsed action history entry
  * \param[in]     orig_exit_status  Action exit status before remapping
  *
  * \return Standard Pacemaker return code (in particular, pcmk_rc_ok means the
  *         entry needs no further processing)
  */
 static int
 process_expired_result(struct action_history *history, int orig_exit_status)
 {
     if (!pcmk__is_bundled(history->rsc)
         && pcmk_xe_mask_probe_failure(history->xml)
         && (orig_exit_status != history->expected_exit_status)) {
 
         if (history->rsc->role <= pcmk_role_stopped) {
             history->rsc->role = pcmk_role_unknown;
         }
         crm_trace("Ignoring resource history entry %s for probe of %s on %s: "
                   "Masked failure expired",
                   history->id, history->rsc->id,
                   pcmk__node_name(history->node));
         return pcmk_rc_ok;
     }
 
     if (history->exit_status == history->expected_exit_status) {
         return pcmk_rc_undetermined; // Only failures expire
     }
 
     if (history->interval_ms == 0) {
         crm_notice("Ignoring resource history entry %s for %s of %s on %s: "
                    "Expired failure",
                    history->id, history->task, history->rsc->id,
                    pcmk__node_name(history->node));
         return pcmk_rc_ok;
     }
 
     if (history->node->details->online && !history->node->details->unclean) {
         /* Reschedule the recurring action. schedule_cancel() won't work at
          * this stage, so as a hacky workaround, forcibly change the restart
          * digest so pcmk__check_action_config() does what we want later.
          *
          * @TODO We should skip this if there is a newer successful monitor.
          *       Also, this causes rescheduling only if the history entry
          *       has a PCMK__XA_OP_DIGEST (which the expire-non-blocked-failure
          *       scheduler regression test doesn't, but that may not be a
          *       realistic scenario in production).
          */
         crm_notice("Rescheduling %s-interval %s of %s on %s "
                    "after failure expired",
                    pcmk__readable_interval(history->interval_ms), history->task,
                    history->rsc->id, pcmk__node_name(history->node));
         crm_xml_add(history->xml, PCMK__XA_OP_RESTART_DIGEST,
                     "calculated-failure-timeout");
         return pcmk_rc_ok;
     }
 
     return pcmk_rc_undetermined;
 }
 
 /*!
  * \internal
  * \brief Process a masked probe failure
  *
  * \param[in,out] history           Parsed action history entry
  * \param[in]     orig_exit_status  Action exit status before remapping
  * \param[in]     last_failure      Resource's last_failure entry, if known
  * \param[in,out] on_fail           Resource's current failure handling
  */
 static void
 mask_probe_failure(struct action_history *history, int orig_exit_status,
                    const xmlNode *last_failure,
                    enum action_fail_response *on_fail)
 {
     pcmk_resource_t *ban_rsc = history->rsc;
 
     if (!pcmk_is_set(history->rsc->flags, pcmk_rsc_unique)) {
         ban_rsc = uber_parent(history->rsc);
     }
 
     crm_notice("Treating probe result '%s' for %s on %s as 'not running'",
                services_ocf_exitcode_str(orig_exit_status), history->rsc->id,
                pcmk__node_name(history->node));
     update_resource_state(history, history->expected_exit_status, last_failure,
                           on_fail);
     crm_xml_add(history->xml, PCMK_XA_UNAME, history->node->details->uname);
 
     record_failed_op(history);
-    resource_location(ban_rsc, history->node, -INFINITY, "masked-probe-failure",
-                      history->rsc->cluster);
+    resource_location(ban_rsc, history->node, -PCMK_SCORE_INFINITY,
+                      "masked-probe-failure", history->rsc->cluster);
 }
 
 /*!
  * \internal Check whether a given failure is for a given pending action
  *
  * \param[in] history       Parsed history entry for pending action
  * \param[in] last_failure  Resource's last_failure entry, if known
  *
  * \return true if \p last_failure is failure of pending action in \p history,
  *         otherwise false
  * \note Both \p history and \p last_failure must come from the same
  *       \c PCMK__XE_LRM_RESOURCE block, as node and resource are assumed to be
  *       the same.
  */
 static bool
 failure_is_newer(const struct action_history *history,
                  const xmlNode *last_failure)
 {
     guint failure_interval_ms = 0U;
     long long failure_change = 0LL;
     long long this_change = 0LL;
 
     if (last_failure == NULL) {
         return false; // Resource has no last_failure entry
     }
 
     if (!pcmk__str_eq(history->task,
                       crm_element_value(last_failure, PCMK_XA_OPERATION),
                       pcmk__str_none)) {
         return false; // last_failure is for different action
     }
 
     if ((crm_element_value_ms(last_failure, PCMK_META_INTERVAL,
                               &failure_interval_ms) != pcmk_ok)
         || (history->interval_ms != failure_interval_ms)) {
         return false; // last_failure is for action with different interval
     }
 
     if ((pcmk__scan_ll(crm_element_value(history->xml, PCMK_XA_LAST_RC_CHANGE),
                        &this_change, 0LL) != pcmk_rc_ok)
         || (pcmk__scan_ll(crm_element_value(last_failure,
                                             PCMK_XA_LAST_RC_CHANGE),
                           &failure_change, 0LL) != pcmk_rc_ok)
         || (failure_change < this_change)) {
         return false; // Failure is not known to be newer
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Update a resource's role etc. for a pending action
  *
  * \param[in,out] history       Parsed history entry for pending action
  * \param[in]     last_failure  Resource's last_failure entry, if known
  */
 static void
 process_pending_action(struct action_history *history,
                        const xmlNode *last_failure)
 {
     /* For recurring monitors, a failure is recorded only in RSC_last_failure_0,
      * and there might be a RSC_monitor_INTERVAL entry with the last successful
      * or pending result.
      *
      * If last_failure contains the failure of the pending recurring monitor
      * we're processing here, and is newer, the action is no longer pending.
      * (Pending results have call ID -1, which sorts last, so the last failure
      * if any should be known.)
      */
     if (failure_is_newer(history, last_failure)) {
         return;
     }
 
     if (strcmp(history->task, PCMK_ACTION_START) == 0) {
         pcmk__set_rsc_flags(history->rsc, pcmk_rsc_start_pending);
         set_active(history->rsc);
 
     } else if (strcmp(history->task, PCMK_ACTION_PROMOTE) == 0) {
         history->rsc->role = pcmk_role_promoted;
 
     } else if ((strcmp(history->task, PCMK_ACTION_MIGRATE_TO) == 0)
                && history->node->details->unclean) {
         /* A migrate_to action is pending on a unclean source, so force a stop
          * on the target.
          */
         const char *migrate_target = NULL;
         pcmk_node_t *target = NULL;
 
         migrate_target = crm_element_value(history->xml,
                                            PCMK__META_MIGRATE_TARGET);
         target = pe_find_node(history->rsc->cluster->nodes, migrate_target);
         if (target != NULL) {
             stop_action(history->rsc, target, FALSE);
         }
     }
 
     if (history->rsc->pending_task != NULL) {
         /* There should never be multiple pending actions, but as a failsafe,
          * just remember the first one processed for display purposes.
          */
         return;
     }
 
     if (pcmk_is_probe(history->task, history->interval_ms)) {
         /* Pending probes are currently never displayed, even if pending
          * operations are requested. If we ever want to change that,
          * enable the below and the corresponding part of
          * native.c:native_pending_task().
          */
 #if 0
         history->rsc->pending_task = strdup("probe");
         history->rsc->pending_node = history->node;
 #endif
     } else {
         history->rsc->pending_task = strdup(history->task);
         history->rsc->pending_node = history->node;
     }
 }
 
 static void
 unpack_rsc_op(pcmk_resource_t *rsc, pcmk_node_t *node, xmlNode *xml_op,
               xmlNode **last_failure, enum action_fail_response *on_fail)
 {
     int old_rc = 0;
     bool expired = false;
     pcmk_resource_t *parent = rsc;
     enum rsc_role_e fail_role = pcmk_role_unknown;
     enum action_fail_response failure_strategy = pcmk_on_fail_restart;
 
     struct action_history history = {
         .rsc = rsc,
         .node = node,
         .xml = xml_op,
         .execution_status = PCMK_EXEC_UNKNOWN,
     };
 
     CRM_CHECK(rsc && node && xml_op, return);
 
     history.id = pcmk__xe_id(xml_op);
     if (history.id == NULL) {
         pcmk__config_err("Ignoring resource history entry for %s on %s "
                          "without ID", rsc->id, pcmk__node_name(node));
         return;
     }
 
     // Task and interval
     history.task = crm_element_value(xml_op, PCMK_XA_OPERATION);
     if (history.task == NULL) {
         pcmk__config_err("Ignoring resource history entry %s for %s on %s "
                          "without " PCMK_XA_OPERATION,
                          history.id, rsc->id, pcmk__node_name(node));
         return;
     }
     crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &(history.interval_ms));
     if (!can_affect_state(&history)) {
         pcmk__rsc_trace(rsc,
                         "Ignoring resource history entry %s for %s on %s "
                         "with irrelevant action '%s'",
                         history.id, rsc->id, pcmk__node_name(node),
                         history.task);
         return;
     }
 
     if (unpack_action_result(&history) != pcmk_rc_ok) {
         return; // Error already logged
     }
 
     history.expected_exit_status = pe__target_rc_from_xml(xml_op);
     history.key = pcmk__xe_history_key(xml_op);
     crm_element_value_int(xml_op, PCMK__XA_CALL_ID, &(history.call_id));
 
     pcmk__rsc_trace(rsc, "Unpacking %s (%s call %d on %s): %s (%s)",
                     history.id, history.task, history.call_id,
                     pcmk__node_name(node),
                     pcmk_exec_status_str(history.execution_status),
                     crm_exit_str(history.exit_status));
 
     if (node->details->unclean) {
         pcmk__rsc_trace(rsc,
                         "%s is running on %s, which is unclean (further action "
                         "depends on value of stop's on-fail attribute)",
                         rsc->id, pcmk__node_name(node));
     }
 
     expired = check_operation_expiry(&history);
     old_rc = history.exit_status;
 
     remap_operation(&history, on_fail, expired);
 
     if (expired && (process_expired_result(&history, old_rc) == pcmk_rc_ok)) {
         goto done;
     }
 
     if (!pcmk__is_bundled(rsc) && pcmk_xe_mask_probe_failure(xml_op)) {
         mask_probe_failure(&history, old_rc, *last_failure, on_fail);
         goto done;
     }
 
     if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) {
         parent = uber_parent(rsc);
     }
 
     switch (history.execution_status) {
         case PCMK_EXEC_PENDING:
             process_pending_action(&history, *last_failure);
             goto done;
 
         case PCMK_EXEC_DONE:
             update_resource_state(&history, history.exit_status, *last_failure,
                                   on_fail);
             goto done;
 
         case PCMK_EXEC_NOT_INSTALLED:
             unpack_failure_handling(&history, &failure_strategy, &fail_role);
             if (failure_strategy == pcmk_on_fail_ignore) {
                 crm_warn("Cannot ignore failed %s of %s on %s: "
                          "Resource agent doesn't exist "
                          CRM_XS " status=%d rc=%d id=%s",
                          history.task, rsc->id, pcmk__node_name(node),
                          history.execution_status, history.exit_status,
                          history.id);
                 /* Also for printing it as "FAILED" by marking it as
                  * pcmk_rsc_failed later
                  */
                 *on_fail = pcmk_on_fail_ban;
             }
-            resource_location(parent, node, -INFINITY, "hard-error",
-                              rsc->cluster);
+            resource_location(parent, node, -PCMK_SCORE_INFINITY,
+                              "hard-error", rsc->cluster);
             unpack_rsc_op_failure(&history, failure_strategy, fail_role,
                                   last_failure, on_fail);
             goto done;
 
         case PCMK_EXEC_NOT_CONNECTED:
             if (pcmk__is_pacemaker_remote_node(node)
                 && pcmk_is_set(node->details->remote_rsc->flags,
                                pcmk_rsc_managed)) {
                 /* We should never get into a situation where a managed remote
                  * connection resource is considered OK but a resource action
                  * behind the connection gets a "not connected" status. But as a
                  * fail-safe in case a bug or unusual circumstances do lead to
                  * that, ensure the remote connection is considered failed.
                  */
                 pcmk__set_rsc_flags(node->details->remote_rsc,
                                     pcmk_rsc_failed|pcmk_rsc_stop_if_failed);
             }
             break; // Not done, do error handling
 
         case PCMK_EXEC_ERROR:
         case PCMK_EXEC_ERROR_HARD:
         case PCMK_EXEC_ERROR_FATAL:
         case PCMK_EXEC_TIMEOUT:
         case PCMK_EXEC_NOT_SUPPORTED:
         case PCMK_EXEC_INVALID:
             break; // Not done, do error handling
 
         default: // No other value should be possible at this point
             break;
     }
 
     unpack_failure_handling(&history, &failure_strategy, &fail_role);
     if ((failure_strategy == pcmk_on_fail_ignore)
         || ((failure_strategy == pcmk_on_fail_restart_container)
             && (strcmp(history.task, PCMK_ACTION_STOP) == 0))) {
 
         char *last_change_s = last_change_str(xml_op);
 
         crm_warn("Pretending failed %s (%s%s%s) of %s on %s at %s succeeded "
                  CRM_XS " %s",
                  history.task, services_ocf_exitcode_str(history.exit_status),
                  (pcmk__str_empty(history.exit_reason)? "" : ": "),
                  pcmk__s(history.exit_reason, ""), rsc->id,
                  pcmk__node_name(node), last_change_s, history.id);
         free(last_change_s);
 
         update_resource_state(&history, history.expected_exit_status,
                               *last_failure, on_fail);
         crm_xml_add(xml_op, PCMK_XA_UNAME, node->details->uname);
         pcmk__set_rsc_flags(rsc, pcmk_rsc_ignore_failure);
 
         record_failed_op(&history);
 
         if ((failure_strategy == pcmk_on_fail_restart_container)
             && cmp_on_fail(*on_fail, pcmk_on_fail_restart) <= 0) {
             *on_fail = failure_strategy;
         }
 
     } else {
         unpack_rsc_op_failure(&history, failure_strategy, fail_role,
                               last_failure, on_fail);
 
         if (history.execution_status == PCMK_EXEC_ERROR_HARD) {
             uint8_t log_level = LOG_ERR;
 
             if (history.exit_status == PCMK_OCF_NOT_INSTALLED) {
                 log_level = LOG_NOTICE;
             }
             do_crm_log(log_level,
                        "Preventing %s from restarting on %s because "
                        "of hard failure (%s%s%s) " CRM_XS " %s",
                        parent->id, pcmk__node_name(node),
                        services_ocf_exitcode_str(history.exit_status),
                        (pcmk__str_empty(history.exit_reason)? "" : ": "),
                        pcmk__s(history.exit_reason, ""), history.id);
-            resource_location(parent, node, -INFINITY, "hard-error",
-                              rsc->cluster);
+            resource_location(parent, node, -PCMK_SCORE_INFINITY,
+                              "hard-error", rsc->cluster);
 
         } else if (history.execution_status == PCMK_EXEC_ERROR_FATAL) {
             pcmk__sched_err("Preventing %s from restarting anywhere because "
                             "of fatal failure (%s%s%s) " CRM_XS " %s",
                             parent->id,
                             services_ocf_exitcode_str(history.exit_status),
                             (pcmk__str_empty(history.exit_reason)? "" : ": "),
                             pcmk__s(history.exit_reason, ""), history.id);
-            resource_location(parent, NULL, -INFINITY, "fatal-error",
-                              rsc->cluster);
+            resource_location(parent, NULL, -PCMK_SCORE_INFINITY,
+                              "fatal-error", rsc->cluster);
         }
     }
 
 done:
     pcmk__rsc_trace(rsc, "%s role on %s after %s is %s (next %s)",
                     rsc->id, pcmk__node_name(node), history.id,
                     pcmk_role_text(rsc->role),
                     pcmk_role_text(rsc->next_role));
 }
 
 static void
 add_node_attrs(const xmlNode *xml_obj, pcmk_node_t *node, bool overwrite,
                pcmk_scheduler_t *scheduler)
 {
     const char *cluster_name = NULL;
 
     pe_rule_eval_data_t rule_data = {
         .node_hash = NULL,
         .now = scheduler->now,
         .match_data = NULL,
         .rsc_data = NULL,
         .op_data = NULL
     };
 
     pcmk__insert_dup(node->details->attrs,
                      CRM_ATTR_UNAME, node->details->uname);
 
     pcmk__insert_dup(node->details->attrs, CRM_ATTR_ID, node->details->id);
     if (pcmk__str_eq(node->details->id, scheduler->dc_uuid, pcmk__str_casei)) {
         scheduler->dc_node = node;
         node->details->is_dc = TRUE;
         pcmk__insert_dup(node->details->attrs,
                          CRM_ATTR_IS_DC, PCMK_VALUE_TRUE);
     } else {
         pcmk__insert_dup(node->details->attrs,
                          CRM_ATTR_IS_DC, PCMK_VALUE_FALSE);
     }
 
     cluster_name = g_hash_table_lookup(scheduler->config_hash,
                                        PCMK_OPT_CLUSTER_NAME);
     if (cluster_name) {
         pcmk__insert_dup(node->details->attrs, CRM_ATTR_CLUSTER_NAME,
                          cluster_name);
     }
 
     pe__unpack_dataset_nvpairs(xml_obj, PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data,
                                node->details->attrs, NULL, overwrite,
                                scheduler);
 
     pe__unpack_dataset_nvpairs(xml_obj, PCMK_XE_UTILIZATION, &rule_data,
                                node->details->utilization, NULL,
                                FALSE, scheduler);
 
     if (pcmk__node_attr(node, CRM_ATTR_SITE_NAME, NULL,
                         pcmk__rsc_node_current) == NULL) {
         const char *site_name = pcmk__node_attr(node, "site-name", NULL,
                                                 pcmk__rsc_node_current);
 
         if (site_name) {
             pcmk__insert_dup(node->details->attrs,
                              CRM_ATTR_SITE_NAME, site_name);
 
         } else if (cluster_name) {
             /* Default to cluster-name if unset */
             pcmk__insert_dup(node->details->attrs,
                              CRM_ATTR_SITE_NAME, cluster_name);
         }
     }
 }
 
 static GList *
 extract_operations(const char *node, const char *rsc, xmlNode * rsc_entry, gboolean active_filter)
 {
     int counter = -1;
     int stop_index = -1;
     int start_index = -1;
 
     xmlNode *rsc_op = NULL;
 
     GList *gIter = NULL;
     GList *op_list = NULL;
     GList *sorted_op_list = NULL;
 
     /* extract operations */
     op_list = NULL;
     sorted_op_list = NULL;
 
     for (rsc_op = pcmk__xe_first_child(rsc_entry);
          rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) {
 
         if (pcmk__xe_is(rsc_op, PCMK__XE_LRM_RSC_OP)) {
             crm_xml_add(rsc_op, PCMK_XA_RESOURCE, rsc);
             crm_xml_add(rsc_op, PCMK_XA_UNAME, node);
             op_list = g_list_prepend(op_list, rsc_op);
         }
     }
 
     if (op_list == NULL) {
         /* if there are no operations, there is nothing to do */
         return NULL;
     }
 
     sorted_op_list = g_list_sort(op_list, sort_op_by_callid);
 
     /* create active recurring operations as optional */
     if (active_filter == FALSE) {
         return sorted_op_list;
     }
 
     op_list = NULL;
 
     calculate_active_ops(sorted_op_list, &start_index, &stop_index);
 
     for (gIter = sorted_op_list; gIter != NULL; gIter = gIter->next) {
         xmlNode *rsc_op = (xmlNode *) gIter->data;
 
         counter++;
 
         if (start_index < stop_index) {
             crm_trace("Skipping %s: not active", pcmk__xe_id(rsc_entry));
             break;
 
         } else if (counter < start_index) {
             crm_trace("Skipping %s: old", pcmk__xe_id(rsc_op));
             continue;
         }
         op_list = g_list_append(op_list, rsc_op);
     }
 
     g_list_free(sorted_op_list);
     return op_list;
 }
 
 GList *
 find_operations(const char *rsc, const char *node, gboolean active_filter,
                 pcmk_scheduler_t *scheduler)
 {
     GList *output = NULL;
     GList *intermediate = NULL;
 
     xmlNode *tmp = NULL;
     xmlNode *status = find_xml_node(scheduler->input, PCMK_XE_STATUS, TRUE);
 
     pcmk_node_t *this_node = NULL;
 
     xmlNode *node_state = NULL;
 
     for (node_state = pcmk__xe_first_child(status); node_state != NULL;
          node_state = pcmk__xe_next(node_state)) {
 
         if (pcmk__xe_is(node_state, PCMK__XE_NODE_STATE)) {
             const char *uname = crm_element_value(node_state, PCMK_XA_UNAME);
 
             if (node != NULL && !pcmk__str_eq(uname, node, pcmk__str_casei)) {
                 continue;
             }
 
             this_node = pe_find_node(scheduler->nodes, uname);
             if(this_node == NULL) {
                 CRM_LOG_ASSERT(this_node != NULL);
                 continue;
 
             } else if (pcmk__is_pacemaker_remote_node(this_node)) {
                 determine_remote_online_status(scheduler, this_node);
 
             } else {
                 determine_online_status(node_state, this_node, scheduler);
             }
 
             if (this_node->details->online
                 || pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
                 /* offline nodes run no resources...
                  * unless stonith is enabled in which case we need to
                  *   make sure rsc start events happen after the stonith
                  */
                 xmlNode *lrm_rsc = NULL;
 
                 tmp = find_xml_node(node_state, PCMK__XE_LRM, FALSE);
                 tmp = find_xml_node(tmp, PCMK__XE_LRM_RESOURCES, FALSE);
 
                 for (lrm_rsc = pcmk__xe_first_child(tmp); lrm_rsc != NULL;
                      lrm_rsc = pcmk__xe_next(lrm_rsc)) {
 
                     if (pcmk__xe_is(lrm_rsc, PCMK__XE_LRM_RESOURCE)) {
                         const char *rsc_id = crm_element_value(lrm_rsc,
                                                                PCMK_XA_ID);
 
                         if (rsc != NULL && !pcmk__str_eq(rsc_id, rsc, pcmk__str_casei)) {
                             continue;
                         }
 
                         intermediate = extract_operations(uname, rsc_id, lrm_rsc, active_filter);
                         output = g_list_concat(output, intermediate);
                     }
                 }
             }
         }
     }
 
     return output;
 }
diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c
index ed9612a76e..fad44589ca 100644
--- a/lib/pengine/utils.c
+++ b/lib/pengine/utils.c
@@ -1,918 +1,918 @@
 /*
  * 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 <crm_internal.h>
 
 #include <glib.h>
 #include <stdbool.h>
 
 #include <crm/crm.h>
 #include <crm/common/xml.h>
 #include <crm/pengine/rules.h>
 #include <crm/pengine/internal.h>
 
 #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)) {
         /* Guest nodes are fenced by stopping their container resource. We can
          * do that if the container's host is either online or fenceable.
          */
         pcmk_resource_t *rsc = node->details->remote_rsc->container;
 
         for (GList *n = rsc->running_on; n != NULL; n = n->next) {
             pcmk_node_t *container_node = n->data;
 
             if (!container_node->details->online
                 && !pe_can_fence(scheduler, container_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;
 
     CRM_ASSERT(this_node != NULL);
 
     new_node = calloc(1, sizeof(pcmk_node_t));
     CRM_ASSERT(new_node != NULL);
 
     new_node->rsc_discover_mode = this_node->rsc_discover_mode;
     new_node->weight = this_node->weight;
     new_node->fixed = this_node->fixed; // @COMPAT deprecated and unused
     new_node->count = this_node->count;
     new_node->details = this_node->details;
 
     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->details->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->details->uname,
                                     node2->details->uname);
 }
 
 /*!
  * \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;
 
     // 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->details->uname,
                      pcmk_readable_score(node->weight));
     }
     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->weight));
         } else {
             qb_log_from_external_source(function, file, "%s: %s = %s",
                                         LOG_TRACE, line, 0,
                                         comment, pcmk__node_name(node),
                                         pcmk_readable_score(node->weight));
         }
     }
 }
 
 /*!
  * \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 this resource has children, repeat recursively for each
     if (rsc && rsc->children) {
         for (GList *gIter = rsc->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->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 \c a->priority > \c b->priority (or \c b is \c NULL and \c a is
  *            not)
  * \retval  0 \c a->priority == \c b->priority (or both \c a and \c b are
  *            \c NULL)
  * \retval  1 \c a->priority < \c b->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->priority > resource2->priority) {
         return -1;
     }
 
     if (resource1->priority < resource2->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 ((rsc->exclusive_discover
          || (node->rsc_discover_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 if (rsc->children) {
         GList *gIter = rsc->children;
 
         for (; 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->allowed_nodes, node->details->id);
     if (match == NULL) {
         match = pe__copy_node(node);
         g_hash_table_insert(rsc->allowed_nodes, (gpointer) match->details->id, match);
     }
     match->weight = pcmk__add_scores(match->weight, 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->weight));
 }
 
 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->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 == -INFINITY) {
+    if ((node == NULL) && (score == -PCMK_SCORE_INFINITY)) {
         if (rsc->allocated_to) {
             crm_info("Deallocating %s from %s",
                      rsc->id, pcmk__node_name(rsc->allocated_to));
             free(rsc->allocated_to);
             rsc->allocated_to = NULL;
         }
     }
 }
 
 time_t
 get_effective_time(pcmk_scheduler_t *scheduler)
 {
     if(scheduler) {
         if (scheduler->now == NULL) {
             crm_trace("Recording a new 'now'");
             scheduler->now = crm_time_new(NULL);
         }
         return crm_time_get_seconds_since_epoch(scheduler->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->meta, PCMK_META_TARGET_ROLE);
 
     CRM_CHECK(role != NULL, return FALSE);
 
     if (pcmk__str_eq(value, "started", pcmk__str_null_matches | pcmk__str_casei)
         || pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
         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 *lh_action, pcmk_action_t *rh_action,
               uint32_t flags)
 {
     GList *gIter = NULL;
     pcmk__related_action_t *wrapper = NULL;
     GList *list = NULL;
 
     if (flags == pcmk__ar_none) {
         return FALSE;
     }
 
     if (lh_action == NULL || rh_action == NULL) {
         return FALSE;
     }
 
     crm_trace("Creating action wrappers for ordering: %s then %s",
               lh_action->uuid, rh_action->uuid);
 
     /* Ensure we never create a dependency on ourselves... it's happened */
     CRM_ASSERT(lh_action != rh_action);
 
     /* Filter dups, otherwise update_action_states() has too much work to do */
     gIter = lh_action->actions_after;
     for (; gIter != NULL; gIter = gIter->next) {
         pcmk__related_action_t *after = gIter->data;
 
         if (after->action == rh_action && (after->type & flags)) {
             return FALSE;
         }
     }
 
     wrapper = calloc(1, sizeof(pcmk__related_action_t));
     wrapper->action = rh_action;
     wrapper->type = flags;
     list = lh_action->actions_after;
     list = g_list_prepend(list, wrapper);
     lh_action->actions_after = list;
 
     wrapper = calloc(1, sizeof(pcmk__related_action_t));
     wrapper->action = lh_action;
     wrapper->type = flags;
     list = rh_action->actions_before;
     list = g_list_prepend(list, wrapper);
     rh_action->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->tickets == NULL) {
         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
     }
 
     ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
     if (ticket == NULL) {
 
         ticket = calloc(1, sizeof(pcmk_ticket_t));
         if (ticket == NULL) {
             pcmk__sched_err("Cannot allocate ticket '%s'", ticket_id);
             return NULL;
         }
 
         crm_trace("Creaing ticket entry for %s", ticket_id);
 
         ticket->id = strdup(ticket_id);
         ticket->granted = FALSE;
         ticket->last_granted = -1;
         ticket->standby = FALSE;
         ticket->state = pcmk__strkey_table(free, free);
 
         g_hash_table_insert(scheduler->tickets, strdup(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->xml);
 }
 
 void
 pe__clear_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags)
 {
     pcmk__clear_rsc_flags(rsc, flags);
     for (GList *gIter = rsc->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->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->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->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);
             }
         }
     }
 }
 
 gboolean
 add_tag_ref(GHashTable * tags, const char * tag_name,  const char * obj_ref)
 {
     pcmk_tag_t *tag = NULL;
     GList *gIter = NULL;
     gboolean is_existing = FALSE;
 
     CRM_CHECK(tags && tag_name && obj_ref, return FALSE);
 
     tag = g_hash_table_lookup(tags, tag_name);
     if (tag == NULL) {
         tag = calloc(1, sizeof(pcmk_tag_t));
         if (tag == NULL) {
             pcmk__sched_err("Could not allocate memory for tag %s", tag_name);
             return FALSE;
         }
         tag->id = strdup(tag_name);
         tag->refs = NULL;
         g_hash_table_insert(tags, strdup(tag_name), tag);
     }
 
     for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) {
         const char *existing_ref = (const char *) gIter->data;
 
         if (pcmk__str_eq(existing_ref, obj_ref, pcmk__str_none)){
             is_existing = TRUE;
             break;
         }
     }
 
     if (is_existing == FALSE) {
         tag->refs = g_list_append(tag->refs, strdup(obj_ref));
         crm_trace("Added: tag=%s ref=%s", tag->id, obj_ref);
     }
 
     return TRUE;
 }
 
 /*!
  * \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->recheck_by == 0)
             || (scheduler->recheck_by > recheck))) {
         scheduler->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
  * \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]     overwrite     Whether to replace existing values with same name
  * \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,
                            gboolean overwrite, pcmk_scheduler_t *scheduler)
 {
     crm_time_t *next_change = crm_time_new_undefined();
 
     pe_eval_nvpairs(scheduler->input, xml_obj, set_name, rule_data, hash,
                     always_first, overwrite, 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->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->running_on)
             && pcmk__same_node((const pcmk_node_t *) rsc->running_on->data,
                                node);
 }
 
 bool
 pe__rsc_running_on_any(pcmk_resource_t *rsc, GList *node_list)
 {
     for (GList *ele = rsc->running_on; ele; ele = ele->next) {
         pcmk_node_t *node = (pcmk_node_t *) ele->data;
         if (pcmk__str_in_list(node->details->uname, 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->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->parent && pcmk__str_in_list(rsc_printable_id(rsc->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 = pe_find_node(scheduler->nodes, 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->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;
 
     if (parent->variant == pcmk_rsc_variant_clone) {
         rsc_id = pe__clone_child_id(parent);
     }
 
     for (xmlNode *xml_op = pcmk__xml_first_child(rsc->cluster->failed); xml_op != NULL;
          xml_op = pcmk__xml_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;
 }