diff --git a/cts/scheduler/exp/bug-lf-2551.exp b/cts/scheduler/exp/bug-lf-2551.exp
index d62465339a..b02de4cc28 100644
--- a/cts/scheduler/exp/bug-lf-2551.exp
+++ b/cts/scheduler/exp/bug-lf-2551.exp
@@ -1,588 +1,588 @@
-
+
-
+
-
+
+
+
+
+
-
+
-
+
-
+
-
+
-
-
-
-
-
+
diff --git a/cts/scheduler/exp/group-unmanaged-stopped.exp b/cts/scheduler/exp/group-unmanaged-stopped.exp
index 1256c534f6..cc874ea217 100644
--- a/cts/scheduler/exp/group-unmanaged-stopped.exp
+++ b/cts/scheduler/exp/group-unmanaged-stopped.exp
@@ -1,38 +1,38 @@
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/interleave-0.exp b/cts/scheduler/exp/interleave-0.exp
index ae0f2fd194..444d8ea3d2 100644
--- a/cts/scheduler/exp/interleave-0.exp
+++ b/cts/scheduler/exp/interleave-0.exp
@@ -1,1761 +1,1761 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/interleave-1.exp b/cts/scheduler/exp/interleave-1.exp
index ae0f2fd194..444d8ea3d2 100644
--- a/cts/scheduler/exp/interleave-1.exp
+++ b/cts/scheduler/exp/interleave-1.exp
@@ -1,1761 +1,1761 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/interleave-2.exp b/cts/scheduler/exp/interleave-2.exp
index ae0f2fd194..444d8ea3d2 100644
--- a/cts/scheduler/exp/interleave-2.exp
+++ b/cts/scheduler/exp/interleave-2.exp
@@ -1,1761 +1,1761 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/interleave-3.exp b/cts/scheduler/exp/interleave-3.exp
index ae0f2fd194..444d8ea3d2 100644
--- a/cts/scheduler/exp/interleave-3.exp
+++ b/cts/scheduler/exp/interleave-3.exp
@@ -1,1761 +1,1761 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/promoted-unmanaged-monitor.exp b/cts/scheduler/exp/promoted-unmanaged-monitor.exp
index 7bf739747a..bd53d1fb29 100644
--- a/cts/scheduler/exp/promoted-unmanaged-monitor.exp
+++ b/cts/scheduler/exp/promoted-unmanaged-monitor.exp
@@ -1,92 +1,92 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/cts/scheduler/exp/rsc-defaults.exp b/cts/scheduler/exp/rsc-defaults.exp
index 4aec36097c..11cb218209 100644
--- a/cts/scheduler/exp/rsc-defaults.exp
+++ b/cts/scheduler/exp/rsc-defaults.exp
@@ -1,124 +1,124 @@
-
+
diff --git a/cts/scheduler/exp/template-3.exp b/cts/scheduler/exp/template-3.exp
index 370be894af..9c65b5d0f2 100644
--- a/cts/scheduler/exp/template-3.exp
+++ b/cts/scheduler/exp/template-3.exp
@@ -1,144 +1,144 @@
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am
index dfc8f80c95..354a1429a2 100644
--- a/lib/pacemaker/Makefile.am
+++ b/lib/pacemaker/Makefile.am
@@ -1,66 +1,67 @@
#
# Copyright 2004-2022 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# or later (GPLv2+) WITHOUT ANY WARRANTY.
#
include $(top_srcdir)/mk/common.mk
AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir)
noinst_HEADERS = libpacemaker_private.h
## libraries
lib_LTLIBRARIES = libpacemaker.la
## SOURCES
libpacemaker_la_LDFLAGS = -version-info 5:0:4
libpacemaker_la_CFLAGS = $(CFLAGS_HARDENED_LIB)
libpacemaker_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB)
libpacemaker_la_LIBADD = $(top_builddir)/lib/pengine/libpe_status.la \
$(top_builddir)/lib/cib/libcib.la \
$(top_builddir)/lib/lrmd/liblrmd.la \
$(top_builddir)/lib/fencing/libstonithd.la \
$(top_builddir)/lib/services/libcrmservice.la \
$(top_builddir)/lib/common/libcrmcommon.la
# -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version
# Use += rather than backlashed continuation lines for parsing by bumplibs
libpacemaker_la_SOURCES =
libpacemaker_la_SOURCES += pcmk_acl.c
libpacemaker_la_SOURCES += pcmk_cluster_queries.c
libpacemaker_la_SOURCES += pcmk_fence.c
libpacemaker_la_SOURCES += pcmk_graph_consumer.c
libpacemaker_la_SOURCES += pcmk_graph_logging.c
libpacemaker_la_SOURCES += pcmk_graph_producer.c
libpacemaker_la_SOURCES += pcmk_injections.c
libpacemaker_la_SOURCES += pcmk_output.c
libpacemaker_la_SOURCES += pcmk_resource.c
libpacemaker_la_SOURCES += pcmk_result_code.c
libpacemaker_la_SOURCES += pcmk_rule.c
libpacemaker_la_SOURCES += pcmk_sched_actions.c
libpacemaker_la_SOURCES += pcmk_sched_allocate.c
libpacemaker_la_SOURCES += pcmk_sched_bundle.c
libpacemaker_la_SOURCES += pcmk_sched_clone.c
libpacemaker_la_SOURCES += pcmk_sched_colocation.c
libpacemaker_la_SOURCES += pcmk_sched_constraints.c
libpacemaker_la_SOURCES += pcmk_sched_fencing.c
libpacemaker_la_SOURCES += pcmk_sched_group.c
libpacemaker_la_SOURCES += pcmk_sched_location.c
libpacemaker_la_SOURCES += pcmk_sched_nodes.c
libpacemaker_la_SOURCES += pcmk_sched_ordering.c
libpacemaker_la_SOURCES += pcmk_sched_primitive.c
libpacemaker_la_SOURCES += pcmk_sched_probes.c
libpacemaker_la_SOURCES += pcmk_sched_promotable.c
+libpacemaker_la_SOURCES += pcmk_sched_recurring.c
libpacemaker_la_SOURCES += pcmk_sched_remote.c
libpacemaker_la_SOURCES += pcmk_sched_resource.c
libpacemaker_la_SOURCES += pcmk_sched_tickets.c
libpacemaker_la_SOURCES += pcmk_sched_utilization.c
libpacemaker_la_SOURCES += pcmk_simulate.c
libpacemaker_la_SOURCES += pcmk_status.c
diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index bf1d71d01b..d0c4c23c78 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,701 +1,720 @@
/*
* Copyright 2021-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__LIBPACEMAKER_PRIVATE__H
# define PCMK__LIBPACEMAKER_PRIVATE__H
/* This header is for the sole use of libpacemaker, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include // pe_action_t, pe_node_t, pe_working_set_t
// Flags to modify the behavior of the add_colocated_node_scores() method
enum pcmk__coloc_select {
// With no other flags, apply all "with this" colocations
pcmk__coloc_select_default = 0,
// Apply "this with" colocations instead of "with this" colocations
pcmk__coloc_select_this_with = (1 << 0),
// Apply only colocations with non-negative scores
pcmk__coloc_select_nonnegative = (1 << 1),
// Apply only colocations with at least one matching node
pcmk__coloc_select_active = (1 << 2),
};
// Flags the update_ordered_actions() method can return
enum pcmk__updated {
pcmk__updated_none = 0, // Nothing changed
pcmk__updated_first = (1 << 0), // First action was updated
pcmk__updated_then = (1 << 1), // Then action was updated
};
#define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \
au_flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_set), #flags_to_set); \
} while (0)
#define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \
au_flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
// Resource allocation methods
struct resource_alloc_functions_s {
/*!
* \internal
* \brief Assign a resource to a node
*
* \param[in] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*/
pe_node_t *(*assign)(pe_resource_t *rsc, pe_node_t *prefer);
void (*create_actions)(pe_resource_t *rsc);
/*!
* \internal
* \brief Schedule any probes needed for a resource on a node
*
* \param[in] rsc Resource to create probe for
* \param[in] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool (*create_probe)(pe_resource_t *rsc, pe_node_t *node);
void (*internal_constraints)(pe_resource_t *rsc);
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node weights (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
*/
void (*apply_coloc_score) (pe_resource_t *dependent, pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent);
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] rsc Resource to check colocations for
* \param[in] log_id Resource ID to use in logs (if NULL, use rsc ID)
* \param[in,out] nodes Nodes to update
* \param[in] attr Colocation attribute (NULL to use default)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note The caller remains responsible for freeing \p *nodes.
*/
void (*add_colocated_node_scores)(pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr,
float factor,
enum pcmk__coloc_select flags);
/*!
* \internal
* \brief Create list of all resources in colocations with a given resource
*
* Given a resource, create a list of all resources involved in mandatory
* colocations with it, whether directly or indirectly via chained colocations.
*
* \param[in] rsc Resource to add to colocated list
* \param[in] orig_rsc Resource originally requested
* \param[in] colocated_rscs Existing list
*
* \return List of given resource and all resources involved in colocations
*
* \note This function is recursive; top-level callers should pass NULL as
* \p colocated_rscs and \p orig_rsc, and the desired resource as
* \p rsc. The recursive calls will use other values.
*/
GList *(*colocated_resources)(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs);
void (*rsc_location) (pe_resource_t *, pe__location_t *);
enum pe_action_flags (*action_flags) (pe_action_t *, pe_node_t *);
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions'
* flags (and runnable_before members if appropriate) as appropriate for the
* ordering. In some cases, the ordering could be disabled as well.
*
* \param[in] first 'First' action in an ordering
* \param[in] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pe_action_optional to affect only mandatory
* actions, and pe_action_runnable to affect only
* runnable actions)
* \param[in] type Group of enum pe_ordering flags to apply
* \param[in] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t (*update_ordered_actions)(pe_action_t *first, pe_action_t *then,
pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set);
void (*output_actions)(pe_resource_t *rsc);
/*!
* \internal
* \brief Add a resource's actions to the transition graph
*
* \param[in] rsc Resource whose actions should be added
*/
void (*add_actions_to_graph)(pe_resource_t *rsc);
void (*append_meta) (pe_resource_t * rsc, xmlNode * xml);
/*!
* \internal
* \brief Add a resource's utilization to a table of utilization values
*
* This function is used when summing the utilization of a resource and all
* resources colocated with it, to determine whether a node has sufficient
* capacity. Given a resource and a table of utilization values, it will add
* the resource's utilization to the existing values, if the resource has
* not yet been allocated to a node.
*
* \param[in] rsc Resource with utilization to add
* \param[in] orig_rsc Resource being allocated (for logging only)
* \param[in] all_rscs List of all resources that will be summed
* \param[in] utilization Table of utilization values to add to
*/
void (*add_utilization)(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
/*!
* \internal
* \brief Apply a shutdown lock for a resource, if appropriate
*
* \param[in] rsc Resource to check for shutdown lock
*/
void (*shutdown_lock)(pe_resource_t *rsc);
};
// Actions (pcmk_sched_actions.c)
G_GNUC_INTERNAL
void pcmk__update_action_for_orderings(pe_action_t *action,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
uint32_t pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then,
pe_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__log_action(const char *pre_text, pe_action_t *action, bool details);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_cancel_action(pe_resource_t *rsc, const char *name,
guint interval_ms, const pe_node_t *node);
G_GNUC_INTERNAL
pe_action_t *pcmk__new_shutdown_action(pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_locks_rsc_to_node(const pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__deduplicate_action_inputs(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__output_actions(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node,
xmlNode *xml_op);
G_GNUC_INTERNAL
void pcmk__handle_rsc_config_changes(pe_working_set_t *data_set);
+
+// Recurring actions (pcmk_sched_recurring.c)
+
+G_GNUC_INTERNAL
+void pcmk__create_recurring_actions(pe_resource_t *rsc);
+
+G_GNUC_INTERNAL
+void pcmk__schedule_cancel(pe_resource_t *rsc, const char *call_id,
+ const char *task, guint interval_ms,
+ const pe_node_t *node, const char *reason);
+
+G_GNUC_INTERNAL
+void pcmk__reschedule_recurring(pe_resource_t *rsc, const char *task,
+ guint interval_ms, pe_node_t *node);
+
+G_GNUC_INTERNAL
+bool pcmk__action_is_recurring(const pe_action_t *action);
+
+
// Producing transition graphs (pcmk_graph_producer.c)
G_GNUC_INTERNAL
bool pcmk__graph_has_loop(pe_action_t *init_action, pe_action_t *action,
pe_action_wrapper_t *input);
G_GNUC_INTERNAL
void pcmk__add_rsc_actions_to_graph(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_graph(pe_working_set_t *data_set);
// Fencing (pcmk_sched_fencing.c)
G_GNUC_INTERNAL
void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_vs_unfence(pe_resource_t *rsc, pe_node_t *node,
pe_action_t *action, enum pe_ordering order);
G_GNUC_INTERNAL
void pcmk__fence_guest(pe_node_t *node);
G_GNUC_INTERNAL
bool pcmk__node_unfenced(pe_node_t *node);
// Injected scheduler inputs (pcmk_sched_injections.c)
void pcmk__inject_scheduler_input(pe_working_set_t *data_set, cib_t *cib,
pcmk_injections_t *injections);
// Constraints of any type (pcmk_sched_constraints.c)
G_GNUC_INTERNAL
pe_resource_t *pcmk__find_constraint_resource(GList *rsc_list, const char *id);
G_GNUC_INTERNAL
xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__valid_resource_or_tag(pe_working_set_t *data_set, const char *id,
pe_resource_t **rsc, pe_tag_t **tag);
G_GNUC_INTERNAL
bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
bool convert_rsc, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__create_internal_constraints(pe_working_set_t *data_set);
// Location constraints
G_GNUC_INTERNAL
void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe__location_t *pcmk__new_location(const char *id, pe_resource_t *rsc,
int node_weight, const char *discover_mode,
pe_node_t *foo_node,
pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_locations(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__apply_location(pe__location_t *constraint, pe_resource_t *rsc);
// Colocation constraints (pcmk_sched_colocation.c)
enum pcmk__coloc_affects {
pcmk__coloc_affects_nothing = 0,
pcmk__coloc_affects_location,
pcmk__coloc_affects_role,
};
G_GNUC_INTERNAL
enum pcmk__coloc_affects pcmk__colocation_affects(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint,
bool preview);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_weights(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_priority(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *constraint);
G_GNUC_INTERNAL
void pcmk__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id,
GHashTable **nodes, const char *attr,
float factor, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__new_colocation(const char *id, const char *node_attr, int score,
pe_resource_t *dependent, pe_resource_t *primary,
const char *dependent_role, const char *primary_role,
bool influence, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__block_colocated_starts(pe_action_t *action,
pe_working_set_t *data_set);
/*!
* \internal
* \brief Check whether colocation's dependent preferences should be considered
*
* \param[in] colocation Colocation constraint
* \param[in] rsc Primary instance (normally this will be
* colocation->primary, which NULL will be treated as,
* but for clones or bundles with multiple instances
* this can be a particular instance)
*
* \return true if colocation influence should be effective, otherwise false
*/
static inline bool
pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
const pe_resource_t *rsc)
{
if (rsc == NULL) {
rsc = colocation->primary;
}
/* A bundle replica colocates its remote connection with its container,
* using a finite score so that the container can run on Pacemaker Remote
* nodes.
*
* Moving a connection is lightweight and does not interrupt the service,
* while moving a container is heavyweight and does interrupt the service,
* so don't move a clean, active container based solely on the preferences
* of its connection.
*
* This also avoids problematic scenarios where two containers want to
* perpetually swap places.
*/
if (pcmk_is_set(colocation->dependent->flags, pe_rsc_allow_remote_remotes)
&& !pcmk_is_set(rsc->flags, pe_rsc_failed)
&& pcmk__list_of_1(rsc->running_on)) {
return false;
}
/* The dependent in a colocation influences the primary's location
* if the influence option is true or the primary is not yet active.
*/
return colocation->influence || (rsc->running_on == NULL);
}
// Ordering constraints (pcmk_sched_ordering.c)
G_GNUC_INTERNAL
void pcmk__new_ordering(pe_resource_t *first_rsc, char *first_task,
pe_action_t *first_action, pe_resource_t *then_rsc,
char *then_task, pe_action_t *then_action,
enum pe_ordering type, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__disable_invalid_orderings(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_stops_before_shutdown(pe_node_t *node,
pe_action_t *shutdown_op);
G_GNUC_INTERNAL
void pcmk__apply_orderings(pe_working_set_t *data_set);
G_GNUC_INTERNAL
void pcmk__order_after_each(pe_action_t *after, GList *list);
/*!
* \internal
* \brief Create a new ordering between two resource actions
*
* \param[in] first_rsc Resource for 'first' action
* \param[in] then_rsc Resource for 'then' action
* \param[in] first_task Action key for 'first' action
* \param[in] then_task Action key for 'then' action
* \param[in] flags Bitmask of enum pe_ordering flags
* \param[in] data_set Cluster working set to add ordering to
*/
#define pcmk__order_resource_actions(first_rsc, first_task, \
then_rsc, then_task, flags) \
pcmk__new_ordering((first_rsc), \
pcmk__op_key((first_rsc)->id, (first_task), 0), \
NULL, \
(then_rsc), \
pcmk__op_key((then_rsc)->id, (then_task), 0), \
NULL, (flags), (first_rsc)->cluster)
#define pcmk__order_starts(rsc1, rsc2, type) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \
(rsc2), CRMD_ACTION_START, (type))
#define pcmk__order_stops(rsc1, rsc2, type) \
pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \
(rsc2), CRMD_ACTION_STOP, (type))
// Ticket constraints (pcmk_sched_tickets.c)
G_GNUC_INTERNAL
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set);
// Promotable clone resources (pcmk_sched_promotable.c)
G_GNUC_INTERNAL
void pcmk__require_promotion_tickets(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__set_instance_roles(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_promotable_actions(pe_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__promotable_restart_ordering(pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__order_promotable_instances(pe_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__update_dependent_with_promotable(pe_resource_t *primary,
pe_resource_t *dependent,
pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__update_promotable_dependent_priority(pe_resource_t *primary,
pe_resource_t *dependent,
pcmk__colocation_t *colocation);
// Pacemaker Remote nodes (pcmk_sched_remote.c)
G_GNUC_INTERNAL
bool pcmk__is_failed_remote_node(pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_remote_connection_actions(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__rsc_corresponds_to_guest(pe_resource_t *rsc, pe_node_t *node);
G_GNUC_INTERNAL
pe_node_t *pcmk__connection_host_for_action(pe_action_t *action);
G_GNUC_INTERNAL
void pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params);
G_GNUC_INTERNAL
void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action);
// Primitives (pcmk_sched_primitive.c)
G_GNUC_INTERNAL
pe_node_t *pcmk__primitive_assign(pe_resource_t *rsc, pe_node_t *prefer);
G_GNUC_INTERNAL
void pcmk__primitive_apply_coloc_score(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent);
// Groups (pcmk_sched_group.c)
G_GNUC_INTERNAL
void pcmk__group_apply_coloc_score(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__group_add_colocated_node_scores(pe_resource_t *rsc,
const char *log_id,
GHashTable **nodes, const char *attr,
float factor, uint32_t flags);
G_GNUC_INTERNAL
GList *pcmk__group_colocated_resources(pe_resource_t *rsc,
pe_resource_t *orig_rsc,
GList *colocated_rscs);
// Clones (pcmk_sched_clone.c)
G_GNUC_INTERNAL
void pcmk__clone_apply_coloc_score(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent);
// Bundles (pcmk_sched_bundle.c)
G_GNUC_INTERNAL
void pcmk__bundle_apply_coloc_score(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__output_bundle_actions(pe_resource_t *rsc);
// Injections (pcmk_injections.c)
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
bool up);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
const char *resource,
const char *lrm_name,
const char *rclass,
const char *rtype,
const char *rprovider);
G_GNUC_INTERNAL
void pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node,
const char *resource, const char *task,
guint interval_ms, int rc);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
lrmd_event_data_t *op, int target_rc);
// Nodes (pcmk_sched_nodes.c)
G_GNUC_INTERNAL
bool pcmk__node_available(const pe_node_t *node, bool consider_score,
bool consider_guest);
G_GNUC_INTERNAL
bool pcmk__any_node_available(GHashTable *nodes);
G_GNUC_INTERNAL
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
G_GNUC_INTERNAL
GList *pcmk__sort_nodes(GList *nodes, pe_node_t *active_node);
G_GNUC_INTERNAL
void pcmk__apply_node_health(pe_working_set_t *data_set);
G_GNUC_INTERNAL
pe_node_t *pcmk__top_allowed_node(const pe_resource_t *rsc,
const pe_node_t *node);
// Functions applying to more than one variant (pcmk_sched_resource.c)
G_GNUC_INTERNAL
void pcmk__set_allocation_methods(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__rsc_agent_changed(pe_resource_t *rsc, pe_node_t *node,
const xmlNode *rsc_entry, bool active_on_node);
G_GNUC_INTERNAL
GList *pcmk__rscs_matching_id(const char *id, pe_working_set_t *data_set);
G_GNUC_INTERNAL
GList *pcmk__colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__output_resource_actions(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__assign_primitive(pe_resource_t *rsc, pe_node_t *chosen, bool force);
G_GNUC_INTERNAL
bool pcmk__assign_resource(pe_resource_t *rsc, pe_node_t *node, bool force);
G_GNUC_INTERNAL
void pcmk__unassign_resource(pe_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__threshold_reached(pe_resource_t *rsc, pe_node_t *node,
pe_resource_t **failed);
G_GNUC_INTERNAL
void pcmk__sort_resources(pe_working_set_t *data_set);
G_GNUC_INTERNAL
gint pcmk__cmp_instance(gconstpointer a, gconstpointer b);
G_GNUC_INTERNAL
gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b);
// Functions related to probes (pcmk_sched_probes.c)
G_GNUC_INTERNAL
bool pcmk__probe_rsc_on_node(pe_resource_t *rsc, pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_probes(pe_working_set_t *data_set);
G_GNUC_INTERNAL
bool pcmk__probe_resource_list(GList *rscs, pe_node_t *node);
G_GNUC_INTERNAL
void pcmk__schedule_probes(pe_working_set_t *data_set);
// Functions related to node utilization (pcmk_sched_utilization.c)
G_GNUC_INTERNAL
int pcmk__compare_node_capacities(const pe_node_t *node1,
const pe_node_t *node2);
G_GNUC_INTERNAL
void pcmk__consume_node_capacity(GHashTable *current_utilization,
pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__release_node_capacity(GHashTable *current_utilization,
pe_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__ban_insufficient_capacity(pe_resource_t *rsc, pe_node_t **prefer);
G_GNUC_INTERNAL
void pcmk__create_utilization_constraints(pe_resource_t *rsc,
GList *allowed_nodes);
G_GNUC_INTERNAL
void pcmk__show_node_capacities(const char *desc, pe_working_set_t *data_set);
#endif // PCMK__LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_sched_actions.c b/lib/pacemaker/pcmk_sched_actions.c
index b1a3b0e387..d3e63e3597 100644
--- a/lib/pacemaker/pcmk_sched_actions.c
+++ b/lib/pacemaker/pcmk_sched_actions.c
@@ -1,2021 +1,1928 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include "libpacemaker_private.h"
extern gboolean DeleteRsc(pe_resource_t *rsc, pe_node_t *node,
gboolean optional, pe_working_set_t *data_set);
/*!
* \internal
* \brief Get the action flags relevant to ordering constraints
*
* \param[in] action Action to check
* \param[in] node Node that *other* action in the ordering is on
* (used only for clone resource actions)
*
* \return Action flags that should be used for orderings
*/
static enum pe_action_flags
action_flags_for_ordering(pe_action_t *action, pe_node_t *node)
{
bool runnable = false;
enum pe_action_flags flags;
// For non-resource actions, return the action flags
if (action->rsc == NULL) {
return action->flags;
}
/* For non-clone resources, or a clone action not assigned to a node,
* return the flags as determined by the resource method without a node
* specified.
*/
flags = action->rsc->cmds->action_flags(action, NULL);
if ((node == NULL) || !pe_rsc_is_clone(action->rsc)) {
return flags;
}
/* Otherwise (i.e., for clone resource actions on a specific node), first
* remember whether the non-node-specific action is runnable.
*/
runnable = pcmk_is_set(flags, pe_action_runnable);
// Then recheck the resource method with the node
flags = action->rsc->cmds->action_flags(action, node);
/* For clones in ordering constraints, the node-specific "runnable" doesn't
* matter, just the non-node-specific setting (i.e., is the action runnable
* anywhere).
*
* This applies only to runnable, and only for ordering constraints. This
* function shouldn't be used for other types of constraints without
* changes. Not very satisfying, but it's logical and appears to work well.
*/
if (runnable && !pcmk_is_set(flags, pe_action_runnable)) {
pe__set_raw_action_flags(flags, action->rsc->id,
pe_action_runnable);
}
return flags;
}
/*!
* \internal
* \brief Get action UUID that should be used with a resource ordering
*
* When an action is ordered relative to an action for a collective resource
* (clone, group, or bundle), it actually needs to be ordered after all
* instances of the collective have completed the relevant action (for example,
* given "start CLONE then start RSC", RSC must wait until all instances of
* CLONE have started). Given the UUID and resource of the first action in an
* ordering, this returns the UUID of the action that should actually be used
* for ordering (for example, "CLONE_started_0" instead of "CLONE_start_0").
*
* \param[in] first_uuid UUID of first action in ordering
* \param[in] first_rsc Resource of first action in ordering
*
* \return Newly allocated copy of UUID to use with ordering
* \note It is the caller's responsibility to free the return value.
*/
static char *
action_uuid_for_ordering(const char *first_uuid, pe_resource_t *first_rsc)
{
guint interval_ms = 0;
char *uuid = NULL;
char *rid = NULL;
char *first_task_str = NULL;
enum action_tasks first_task = no_action;
enum action_tasks remapped_task = no_action;
// Only non-notify actions for collective resources need remapping
if ((strstr(first_uuid, "notify") != NULL)
|| (first_rsc->variant < pe_group)) {
goto done;
}
// Only non-recurring actions need remapping
CRM_ASSERT(parse_op_key(first_uuid, &rid, &first_task_str, &interval_ms));
if (interval_ms > 0) {
goto done;
}
first_task = text2task(first_task_str);
switch (first_task) {
case stop_rsc:
case start_rsc:
case action_notify:
case action_promote:
case action_demote:
remapped_task = first_task + 1;
break;
case stopped_rsc:
case started_rsc:
case action_notified:
case action_promoted:
case action_demoted:
remapped_task = first_task;
break;
case monitor_rsc:
case shutdown_crm:
case stonith_node:
break;
default:
crm_err("Unknown action '%s' in ordering", first_task_str);
break;
}
if (remapped_task != no_action) {
/* If a (clone) resource has notifications enabled, we want to order
* relative to when all notifications have been sent for the remapped
* task. Only outermost resources or those in bundles have
* notifications.
*/
if (pcmk_is_set(first_rsc->flags, pe_rsc_notify)
&& ((first_rsc->parent == NULL)
|| (pe_rsc_is_clone(first_rsc)
&& (first_rsc->parent->variant == pe_container)))) {
uuid = pcmk__notify_key(rid, "confirmed-post",
task2text(remapped_task));
} else {
uuid = pcmk__op_key(rid, task2text(remapped_task), 0);
}
pe_rsc_trace(first_rsc,
"Remapped action UUID %s to %s for ordering purposes",
first_uuid, uuid);
}
done:
if (uuid == NULL) {
uuid = strdup(first_uuid);
CRM_ASSERT(uuid != NULL);
}
free(first_task_str);
free(rid);
return uuid;
}
/*!
* \internal
* \brief Get actual action that should be used with an ordering
*
* When an action is ordered relative to an action for a collective resource
* (clone, group, or bundle), it actually needs to be ordered after all
* instances of the collective have completed the relevant action (for example,
* given "start CLONE then start RSC", RSC must wait until all instances of
* CLONE have started). Given the first action in an ordering, this returns the
* the action that should actually be used for ordering (for example, the
* started action instead of the start action).
*
* \param[in] action First action in an ordering
*
* \return Actual action that should be used for the ordering
*/
static pe_action_t *
action_for_ordering(pe_action_t *action)
{
pe_action_t *result = action;
pe_resource_t *rsc = action->rsc;
if ((rsc != NULL) && (rsc->variant >= pe_group) && (action->uuid != NULL)) {
char *uuid = action_uuid_for_ordering(action->uuid, rsc);
result = find_first_action(rsc->actions, uuid, NULL, NULL);
if (result == NULL) {
crm_warn("Not remapping %s to %s because %s does not have "
"remapped action", action->uuid, uuid, rsc->id);
result = action;
}
free(uuid);
}
return result;
}
/*!
* \internal
* \brief Update flags for ordering's actions appropriately for ordering's flags
*
* \param[in] first First action in an ordering
* \param[in] then Then action in an ordering
* \param[in] first_flags Action flags for \p first for ordering purposes
* \param[in] then_flags Action flags for \p then for ordering purposes
* \param[in] order Action wrapper for \p first in ordering
* \param[in] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags
*/
static uint32_t
update_action_for_ordering_flags(pe_action_t *first, pe_action_t *then,
enum pe_action_flags first_flags,
enum pe_action_flags then_flags,
pe_action_wrapper_t *order,
pe_working_set_t *data_set)
{
uint32_t changed = pcmk__updated_none;
/* The node will only be used for clones. If interleaved, node will be NULL,
* otherwise the ordering scope will be limited to the node. Normally, the
* whole 'then' clone should restart if 'first' is restarted, so then->node
* is needed.
*/
pe_node_t *node = then->node;
if (pcmk_is_set(order->type, pe_order_implies_then_on_node)) {
/* For unfencing, only instances of 'then' on the same node as 'first'
* (the unfencing operation) should restart, so reset node to
* first->node, at which point this case is handled like a normal
* pe_order_implies_then.
*/
pe__clear_order_flags(order->type, pe_order_implies_then_on_node);
pe__set_order_flags(order->type, pe_order_implies_then);
node = first->node;
pe_rsc_trace(then->rsc,
"%s then %s: mapped pe_order_implies_then_on_node to "
"pe_order_implies_then on %s",
first->uuid, then->uuid, pe__node_name(node));
}
if (pcmk_is_set(order->type, pe_order_implies_then)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags & pe_action_optional,
pe_action_optional,
pe_order_implies_then,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_optional)
&& pcmk_is_set(then->flags, pe_action_optional)) {
pe__clear_action_flags(then, pe_action_optional);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_then",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_restart) && (then->rsc != NULL)) {
enum pe_action_flags restart = pe_action_optional|pe_action_runnable;
changed |= then->rsc->cmds->update_ordered_actions(first, then, node,
first_flags, restart,
pe_order_restart,
data_set);
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_restart",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_implies_first)) {
if (first->rsc != NULL) {
changed |= first->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_implies_first,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_optional)
&& pcmk_is_set(first->flags, pe_action_runnable)) {
pe__clear_action_flags(first, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_promoted_implies_first)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags & pe_action_optional,
pe_action_optional,
pe_order_promoted_implies_first,
data_set);
}
pe_rsc_trace(then->rsc,
"%s then %s: %s after pe_order_promoted_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_one_or_more)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_one_or_more,
data_set);
} else if (pcmk_is_set(first_flags, pe_action_runnable)) {
// We have another runnable instance of "first"
then->runnable_before++;
/* Mark "then" as runnable if it requires a certain number of
* "before" instances to be runnable, and they now are.
*/
if ((then->runnable_before >= then->required_runnable_before)
&& !pcmk_is_set(then->flags, pe_action_runnable)) {
pe__set_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_one_or_more",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_probe) && (then->rsc != NULL)) {
if (!pcmk_is_set(first_flags, pe_action_runnable)
&& (first->rsc->running_on != NULL)) {
pe_rsc_trace(then->rsc,
"%s then %s: ignoring because first is stopping",
first->uuid, then->uuid);
order->type = pe_order_none;
} else {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_runnable_left,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_probe",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_runnable_left)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_runnable_left,
data_set);
} else if (!pcmk_is_set(first_flags, pe_action_runnable)
&& pcmk_is_set(then->flags, pe_action_runnable)) {
pe__clear_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_runnable_left",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_implies_first_migratable)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_implies_first_migratable,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after "
"pe_order_implies_first_migratable",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_pseudo_left)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_optional,
pe_order_pseudo_left,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_pseudo_left",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_optional)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_optional,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_optional",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pe_order_asymmetrical)) {
if (then->rsc != NULL) {
changed |= then->rsc->cmds->update_ordered_actions(first, then,
node,
first_flags,
pe_action_runnable,
pe_order_asymmetrical,
data_set);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_asymmetrical",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(first->flags, pe_action_runnable)
&& pcmk_is_set(order->type, pe_order_implies_then_printed)
&& !pcmk_is_set(first_flags, pe_action_optional)) {
pe_rsc_trace(then->rsc, "%s will be in graph because %s is required",
then->uuid, first->uuid);
pe__set_action_flags(then, pe_action_print_always);
// Don't bother marking 'then' as changed just for this
}
if (pcmk_is_set(order->type, pe_order_implies_first_printed)
&& !pcmk_is_set(then_flags, pe_action_optional)) {
pe_rsc_trace(then->rsc, "%s will be in graph because %s is required",
first->uuid, then->uuid);
pe__set_action_flags(first, pe_action_print_always);
// Don't bother marking 'first' as changed just for this
}
if (pcmk_any_flags_set(order->type, pe_order_implies_then
|pe_order_implies_first
|pe_order_restart)
&& (first->rsc != NULL)
&& !pcmk_is_set(first->rsc->flags, pe_rsc_managed)
&& pcmk_is_set(first->rsc->flags, pe_rsc_block)
&& !pcmk_is_set(first->flags, pe_action_runnable)
&& pcmk__str_eq(first->task, RSC_STOP, pcmk__str_casei)) {
if (pcmk_is_set(then->flags, pe_action_runnable)) {
pe__clear_action_flags(then, pe_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pe_rsc_trace(then->rsc, "%s then %s: %s after checking whether first "
"is blocked, unmanaged, unrunnable stop",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
return changed;
}
// Convenience macros for logging action properties
#define action_type_str(flags) \
(pcmk_is_set((flags), pe_action_pseudo)? "pseudo-action" : "action")
#define action_optional_str(flags) \
(pcmk_is_set((flags), pe_action_optional)? "optional" : "required")
#define action_runnable_str(flags) \
(pcmk_is_set((flags), pe_action_runnable)? "runnable" : "unrunnable")
#define action_node_str(a) \
(((a)->node == NULL)? "no node" : (a)->node->details->uname)
/*!
* \internal
* \brief Update an action's flags for all orderings where it is "then"
*
* \param[in] then Action to update
* \param[in] data_set Cluster working set
*/
void
pcmk__update_action_for_orderings(pe_action_t *then, pe_working_set_t *data_set)
{
GList *lpc = NULL;
uint32_t changed = pcmk__updated_none;
int last_flags = then->flags;
pe_rsc_trace(then->rsc, "Updating %s %s (%s %s) on %s",
action_type_str(then->flags), then->uuid,
action_optional_str(then->flags),
action_runnable_str(then->flags), action_node_str(then));
if (pcmk_is_set(then->flags, pe_action_requires_any)) {
/* Initialize current known "runnable before" actions. As
* update_action_for_ordering_flags() is called for each of then's
* before actions, this number will increment as runnable 'first'
* actions are encountered.
*/
then->runnable_before = 0;
if (then->required_runnable_before == 0) {
/* @COMPAT This ordering constraint uses the deprecated
* "require-all=false" attribute. Treat it like "clone-min=1".
*/
then->required_runnable_before = 1;
}
/* The pe_order_one_or_more clause of update_action_for_ordering_flags()
* (called below) will reset runnable if appropriate.
*/
pe__clear_action_flags(then, pe_action_runnable);
}
for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data;
pe_action_t *first = other->action;
pe_node_t *then_node = then->node;
pe_node_t *first_node = first->node;
if ((first->rsc != NULL)
&& (first->rsc->variant == pe_group)
&& pcmk__str_eq(first->task, RSC_START, pcmk__str_casei)) {
first_node = first->rsc->fns->location(first->rsc, NULL, FALSE);
if (first_node != NULL) {
pe_rsc_trace(first->rsc, "Found %s for 'first' %s",
pe__node_name(first_node), first->uuid);
}
}
if ((then->rsc != NULL)
&& (then->rsc->variant == pe_group)
&& pcmk__str_eq(then->task, RSC_START, pcmk__str_casei)) {
then_node = then->rsc->fns->location(then->rsc, NULL, FALSE);
if (then_node != NULL) {
pe_rsc_trace(then->rsc, "Found %s for 'then' %s",
pe__node_name(then_node), then->uuid);
}
}
// Disable constraint if it only applies when on same node, but isn't
if (pcmk_is_set(other->type, pe_order_same_node)
&& (first_node != NULL) && (then_node != NULL)
&& (first_node->details != then_node->details)) {
pe_rsc_trace(then->rsc,
"Disabled ordering %s on %s then %s on %s: not same node",
other->action->uuid, pe__node_name(first_node),
then->uuid, pe__node_name(then_node));
other->type = pe_order_none;
continue;
}
pcmk__clear_updated_flags(changed, then, pcmk__updated_first);
if ((first->rsc != NULL)
&& pcmk_is_set(other->type, pe_order_then_cancels_first)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
/* 'then' is required, so we must abandon 'first'
* (e.g. a required stop cancels any agent reload).
*/
pe__set_action_flags(other->action, pe_action_optional);
if (!strcmp(first->task, CRMD_ACTION_RELOAD_AGENT)) {
pe__clear_resource_flags(first->rsc, pe_rsc_reload);
}
}
if ((first->rsc != NULL) && (then->rsc != NULL)
&& (first->rsc != then->rsc) && !is_parent(then->rsc, first->rsc)) {
first = action_for_ordering(first);
}
if (first != other->action) {
pe_rsc_trace(then->rsc, "Ordering %s after %s instead of %s",
then->uuid, first->uuid, other->action->uuid);
}
pe_rsc_trace(then->rsc,
"%s (%#.6x) then %s (%#.6x): type=%#.6x node=%s",
first->uuid, first->flags, then->uuid, then->flags,
other->type, action_node_str(first));
if (first == other->action) {
/* 'first' was not remapped (e.g. from 'start' to 'running'), which
* could mean it is a non-resource action, a primitive resource
* action, or already expanded.
*/
enum pe_action_flags first_flags, then_flags;
first_flags = action_flags_for_ordering(first, then_node);
then_flags = action_flags_for_ordering(then, first_node);
changed |= update_action_for_ordering_flags(first, then,
first_flags, then_flags,
other, data_set);
/* 'first' was for a complex resource (clone, group, etc),
* create a new dependency if necessary
*/
} else if (order_actions(first, then, other->type)) {
/* This was the first time 'first' and 'then' were associated,
* start again to get the new actions_before list
*/
pcmk__set_updated_flags(changed, then, pcmk__updated_then);
pe_rsc_trace(then->rsc,
"Disabled ordering %s then %s in favor of %s then %s",
other->action->uuid, then->uuid, first->uuid,
then->uuid);
other->type = pe_order_none;
}
if (pcmk_is_set(changed, pcmk__updated_first)) {
crm_trace("Re-processing %s and its 'after' actions "
"because it changed", first->uuid);
for (GList *lpc2 = first->actions_after; lpc2 != NULL;
lpc2 = lpc2->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc2->data;
pcmk__update_action_for_orderings(other->action, data_set);
}
pcmk__update_action_for_orderings(first, data_set);
}
}
if (pcmk_is_set(then->flags, pe_action_requires_any)) {
if (last_flags == then->flags) {
pcmk__clear_updated_flags(changed, then, pcmk__updated_then);
} else {
pcmk__set_updated_flags(changed, then, pcmk__updated_then);
}
}
if (pcmk_is_set(changed, pcmk__updated_then)) {
crm_trace("Re-processing %s and its 'after' actions because it changed",
then->uuid);
if (pcmk_is_set(last_flags, pe_action_runnable)
&& !pcmk_is_set(then->flags, pe_action_runnable)) {
pcmk__block_colocated_starts(then, data_set);
}
pcmk__update_action_for_orderings(then, data_set);
for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data;
pcmk__update_action_for_orderings(other->action, data_set);
}
}
}
static inline bool
is_primitive_action(pe_action_t *action)
{
return action && action->rsc && (action->rsc->variant == pe_native);
}
/*!
* \internal
* \brief Clear a single action flag and set reason text
*
* \param[in] action Action whose flag should be cleared
* \param[in] flag Action flag that should be cleared
* \param[in] reason Action that is the reason why flag is being cleared
*/
#define clear_action_flag_because(action, flag, reason) do { \
if (pcmk_is_set((action)->flags, (flag))) { \
pe__clear_action_flags(action, flag); \
if ((action)->rsc != (reason)->rsc) { \
char *reason_text = pe__action2reason((reason), (flag)); \
pe_action_set_reason((action), reason_text, \
((flag) == pe_action_migrate_runnable)); \
free(reason_text); \
} \
} \
} while (0)
/*!
* \internal
* \brief Update actions in an asymmetric ordering
*
* \param[in] first 'First' action in an asymmetric ordering
* \param[in] then 'Then' action in an asymmetric ordering
*/
static void
handle_asymmetric_ordering(pe_action_t *first, pe_action_t *then)
{
enum rsc_role_e then_rsc_role = RSC_ROLE_UNKNOWN;
GList *then_on = NULL;
if (then->rsc == NULL) {
// Asymmetric orderings only matter if there's a resource involved
return;
}
then_rsc_role = then->rsc->fns->state(then->rsc, TRUE);
then_on = then->rsc->running_on;
if ((then_rsc_role == RSC_ROLE_STOPPED)
&& pcmk__str_eq(then->task, RSC_STOP, pcmk__str_none)) {
/* Nothing needs to be done for asymmetric ordering if 'then' is
* supposed to be stopped after 'first' but is already stopped.
*/
return;
}
if ((then_rsc_role >= RSC_ROLE_STARTED)
&& pcmk_is_set(then->flags, pe_action_optional)
&& (then->node != NULL)
&& pcmk__list_of_1(then_on)
&& (then->node->details == ((pe_node_t *) then_on->data)->details)
&& pcmk__str_eq(then->task, RSC_START, pcmk__str_none)) {
/* Nothing needs to be done for asymmetric ordering if 'then' is
* supposed to be started after 'first' but is already started --
* unless the start is mandatory, which indicates the resource is
* restarting and the ordering is still needed.
*/
return;
}
if (!pcmk_is_set(first->flags, pe_action_runnable)) {
// 'First' can't run, so 'then' can't either
clear_action_flag_because(then, pe_action_optional, first);
clear_action_flag_because(then, pe_action_runnable, first);
}
}
/*!
* \internal
* \brief Set action bits appropriately when pe_restart_order is used
*
* \param[in] first 'First' action in an ordering with pe_restart_order
* \param[in] then 'Then' action in an ordering with pe_restart_order
* \param[in] filter What action flags to care about
*
* \note pe_restart_order is set for "stop resource before starting it" and
* "stop later group member before stopping earlier group member"
*/
static void
handle_restart_ordering(pe_action_t *first, pe_action_t *then, uint32_t filter)
{
const char *reason = NULL;
CRM_ASSERT(is_primitive_action(first));
CRM_ASSERT(is_primitive_action(then));
// We need to update the action in two cases:
// ... if 'then' is required
if (pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
reason = "restart";
}
/* ... if 'then' is unrunnable action on same resource (if a resource
* should restart but can't start, we still want to stop)
*/
if (pcmk_is_set(filter, pe_action_runnable)
&& !pcmk_is_set(then->flags, pe_action_runnable)
&& pcmk_is_set(then->rsc->flags, pe_rsc_managed)
&& (first->rsc == then->rsc)) {
reason = "stop";
}
if (reason == NULL) {
return;
}
pe_rsc_trace(first->rsc, "Handling %s -> %s for %s",
first->uuid, then->uuid, reason);
// Make 'first' required if it is runnable
if (pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(first, pe_action_optional, then);
}
// Make 'first' required if 'then' is required
if (!pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
// Make 'first' unmigratable if 'then' is unmigratable
if (!pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable, then);
}
// Make 'then' unrunnable if 'first' is required but unrunnable
if (!pcmk_is_set(first->flags, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_runnable, first);
}
}
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions'
* flags (and runnable_before members if appropriate) as appropriate for the
* ordering. In some cases, the ordering could be disabled as well.
*
* \param[in] first 'First' action in an ordering
* \param[in] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pe_action_optional to affect only mandatory
* actions, and pe_action_runnable to affect only
* runnable actions)
* \param[in] type Group of enum pe_ordering flags to apply
* \param[in] data_set Cluster working set
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then,
pe_node_t *node, uint32_t flags, uint32_t filter,
uint32_t type, pe_working_set_t *data_set)
{
uint32_t changed = pcmk__updated_none;
uint32_t then_flags = then->flags;
uint32_t first_flags = first->flags;
if (pcmk_is_set(type, pe_order_asymmetrical)) {
handle_asymmetric_ordering(first, then);
}
if (pcmk_is_set(type, pe_order_implies_first)
&& !pcmk_is_set(then_flags, pe_action_optional)) {
// Then is required, and implies first should be, too
if (pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(flags, pe_action_optional)
&& pcmk_is_set(first_flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
if (pcmk_is_set(flags, pe_action_migrate_runnable)
&& !pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable, then);
}
}
if (pcmk_is_set(type, pe_order_promoted_implies_first)
&& (then->rsc != NULL) && (then->rsc->role == RSC_ROLE_PROMOTED)
&& pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
if (pcmk_is_set(first->flags, pe_action_migrate_runnable)
&& !pcmk_is_set(then->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(first, pe_action_migrate_runnable,
then);
}
}
if (pcmk_is_set(type, pe_order_implies_first_migratable)
&& pcmk_is_set(filter, pe_action_optional)) {
if (!pcmk_all_flags_set(then->flags,
pe_action_migrate_runnable|pe_action_runnable)) {
clear_action_flag_because(first, pe_action_runnable, then);
}
if (!pcmk_is_set(then->flags, pe_action_optional)) {
clear_action_flag_because(first, pe_action_optional, then);
}
}
if (pcmk_is_set(type, pe_order_pseudo_left)
&& pcmk_is_set(filter, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_migrate_runnable, first);
pe__clear_action_flags(then, pe_action_pseudo);
}
if (pcmk_is_set(type, pe_order_runnable_left)
&& pcmk_is_set(filter, pe_action_runnable)
&& pcmk_is_set(then->flags, pe_action_runnable)
&& !pcmk_is_set(flags, pe_action_runnable)) {
clear_action_flag_because(then, pe_action_runnable, first);
clear_action_flag_because(then, pe_action_migrate_runnable, first);
}
if (pcmk_is_set(type, pe_order_implies_then)
&& pcmk_is_set(filter, pe_action_optional)
&& pcmk_is_set(then->flags, pe_action_optional)
&& !pcmk_is_set(flags, pe_action_optional)
&& !pcmk_is_set(first->flags, pe_action_migrate_runnable)) {
clear_action_flag_because(then, pe_action_optional, first);
}
if (pcmk_is_set(type, pe_order_restart)) {
handle_restart_ordering(first, then, filter);
}
if (then_flags != then->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
pe_rsc_trace(then->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'first' %s (%#.6x)",
then->uuid,
then->node? then->node->details->uname : "no node",
then->flags, then_flags, first->uuid, first->flags);
if ((then->rsc != NULL) && (then->rsc->parent != NULL)) {
// Required to handle "X_stop then X_start" for cloned groups
pcmk__update_action_for_orderings(then, data_set);
}
}
if (first_flags != first->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
pe_rsc_trace(first->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'then' %s (%#.6x)",
first->uuid,
first->node? first->node->details->uname : "no node",
first->flags, first_flags, then->uuid, then->flags);
}
return changed;
}
/*!
* \internal
* \brief Trace-log an action (optionally with its dependent actions)
*
* \param[in] pre_text If not NULL, prefix the log with this plus ": "
* \param[in] action Action to log
* \param[in] details If true, recursively log dependent actions
*/
void
pcmk__log_action(const char *pre_text, pe_action_t *action, bool details)
{
const char *node_uname = NULL;
const char *node_uuid = NULL;
const char *desc = NULL;
CRM_CHECK(action != NULL, return);
if (!pcmk_is_set(action->flags, pe_action_pseudo)) {
if (action->node != NULL) {
node_uname = action->node->details->uname;
node_uuid = action->node->details->id;
} else {
node_uname = "";
}
}
switch (text2task(action->task)) {
case stonith_node:
case shutdown_crm:
if (pcmk_is_set(action->flags, pe_action_pseudo)) {
desc = "Pseudo ";
} else if (pcmk_is_set(action->flags, pe_action_optional)) {
desc = "Optional ";
} else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
desc = "!!Non-Startable!! ";
} else if (pcmk_is_set(action->flags, pe_action_processed)) {
desc = "";
} else {
desc = "(Provisional) ";
}
crm_trace("%s%s%sAction %d: %s%s%s%s%s%s",
((pre_text == NULL)? "" : pre_text),
((pre_text == NULL)? "" : ": "),
desc, action->id, action->uuid,
(node_uname? "\ton " : ""), (node_uname? node_uname : ""),
(node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""),
(node_uuid? ")" : ""));
break;
default:
if (pcmk_is_set(action->flags, pe_action_optional)) {
desc = "Optional ";
} else if (pcmk_is_set(action->flags, pe_action_pseudo)) {
desc = "Pseudo ";
} else if (!pcmk_is_set(action->flags, pe_action_runnable)) {
desc = "!!Non-Startable!! ";
} else if (pcmk_is_set(action->flags, pe_action_processed)) {
desc = "";
} else {
desc = "(Provisional) ";
}
crm_trace("%s%s%sAction %d: %s %s%s%s%s%s%s",
((pre_text == NULL)? "" : pre_text),
((pre_text == NULL)? "" : ": "),
desc, action->id, action->uuid,
(action->rsc? action->rsc->id : ""),
(node_uname? "\ton " : ""), (node_uname? node_uname : ""),
(node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""),
(node_uuid? ")" : ""));
break;
}
if (details) {
GList *iter = NULL;
crm_trace("\t\t====== Preceding Actions");
for (iter = action->actions_before; iter != NULL; iter = iter->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) iter->data;
pcmk__log_action("\t\t", other->action, false);
}
crm_trace("\t\t====== Subsequent Actions");
for (iter = action->actions_after; iter != NULL; iter = iter->next) {
pe_action_wrapper_t *other = (pe_action_wrapper_t *) iter->data;
pcmk__log_action("\t\t", other->action, false);
}
crm_trace("\t\t====== End");
} else {
crm_trace("\t\t(before=%d, after=%d)",
g_list_length(action->actions_before),
g_list_length(action->actions_after));
}
}
-/*!
- * \internal
- * \brief Create an executor cancel action
- *
- * \param[in] rsc Resource of action to cancel
- * \param[in] task Name of action to cancel
- * \param[in] interval_ms Interval of action to cancel
- * \param[in] node Node of action to cancel
- * \param[in] data_set Working set of cluster
- *
- * \return Created op
- */
-pe_action_t *
-pcmk__new_cancel_action(pe_resource_t *rsc, const char *task, guint interval_ms,
- const pe_node_t *node)
-{
- pe_action_t *cancel_op = NULL;
- char *key = NULL;
- char *interval_ms_s = NULL;
-
- CRM_ASSERT((rsc != NULL) && (task != NULL) && (node != NULL));
-
- // @TODO dangerous if possible to schedule another action with this key
- key = pcmk__op_key(rsc->id, task, interval_ms);
-
- cancel_op = custom_action(rsc, key, RSC_CANCEL, node, FALSE, TRUE,
- rsc->cluster);
-
- pcmk__str_update(&cancel_op->task, RSC_CANCEL);
- pcmk__str_update(&cancel_op->cancel_task, task);
-
- interval_ms_s = crm_strdup_printf("%u", interval_ms);
- add_hash_param(cancel_op->meta, XML_LRM_ATTR_TASK, task);
- add_hash_param(cancel_op->meta, XML_LRM_ATTR_INTERVAL_MS, interval_ms_s);
- free(interval_ms_s);
-
- return cancel_op;
-}
-
/*!
* \internal
* \brief Create a new shutdown action for a node
*
* \param[in] node Node being shut down
*
* \return Newly created shutdown action for \p node
*/
pe_action_t *
pcmk__new_shutdown_action(pe_node_t *node)
{
char *shutdown_id = NULL;
pe_action_t *shutdown_op = NULL;
CRM_ASSERT(node != NULL);
shutdown_id = crm_strdup_printf("%s-%s", CRM_OP_SHUTDOWN,
node->details->uname);
shutdown_op = custom_action(NULL, shutdown_id, CRM_OP_SHUTDOWN, node, FALSE,
TRUE, node->details->data_set);
pcmk__order_stops_before_shutdown(node, shutdown_op);
add_hash_param(shutdown_op->meta, XML_ATTR_TE_NOWAIT, XML_BOOLEAN_TRUE);
return shutdown_op;
}
/*!
* \internal
* \brief Calculate and add an operation digest to XML
*
* Calculate an operation digest, which enables us to later determine when a
* restart is needed due to the resource's parameters being changed, and add it
* to given XML.
*
* \param[in] op Operation result from executor
* \param[in] update XML to add digest to
*/
static void
add_op_digest_to_xml(lrmd_event_data_t *op, xmlNode *update)
{
char *digest = NULL;
xmlNode *args_xml = NULL;
if (op->params == NULL) {
return;
}
args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
g_hash_table_foreach(op->params, hash2field, args_xml);
pcmk__filter_op_for_digest(args_xml);
digest = calculate_operation_digest(args_xml, NULL);
crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
free_xml(args_xml);
free(digest);
}
#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
/*!
* \internal
* \brief Create XML for resource operation history update
*
* \param[in,out] parent Parent XML node to add to
* \param[in,out] op Operation event data
* \param[in] caller_version DC feature set
* \param[in] target_rc Expected result of operation
* \param[in] node Name of node on which operation was performed
* \param[in] origin Arbitrary description of update source
*
* \return Newly created XML node for history update
*/
xmlNode *
pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *op,
const char *caller_version, int target_rc,
const char *node, const char *origin)
{
char *key = NULL;
char *magic = NULL;
char *op_id = NULL;
char *op_id_additional = NULL;
char *local_user_data = NULL;
const char *exit_reason = NULL;
xmlNode *xml_op = NULL;
const char *task = NULL;
CRM_CHECK(op != NULL, return NULL);
crm_trace("Creating history XML for %s-interval %s action for %s on %s "
"(DC version: %s, origin: %s)",
pcmk__readable_interval(op->interval_ms), op->op_type, op->rsc_id,
((node == NULL)? "no node" : node), caller_version, origin);
task = op->op_type;
/* Record a successful agent reload as a start, and a failed one as a
* monitor, to make life easier for the scheduler when determining the
* current state.
*
* @COMPAT We should check "reload" here only if the operation was for a
* pre-OCF-1.1 resource agent, but we don't know that here, and we should
* only ever get results for actions scheduled by us, so we can reasonably
* assume any "reload" is actually a pre-1.1 agent reload.
*/
if (pcmk__str_any_of(task, CRMD_ACTION_RELOAD, CRMD_ACTION_RELOAD_AGENT,
NULL)) {
if (op->op_status == PCMK_EXEC_DONE) {
task = CRMD_ACTION_START;
} else {
task = CRMD_ACTION_STATUS;
}
}
key = pcmk__op_key(op->rsc_id, task, op->interval_ms);
if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_none)) {
const char *n_type = crm_meta_value(op->params, "notify_type");
const char *n_task = crm_meta_value(op->params, "notify_operation");
CRM_LOG_ASSERT(n_type != NULL);
CRM_LOG_ASSERT(n_task != NULL);
op_id = pcmk__notify_key(op->rsc_id, n_type, n_task);
if (op->op_status != PCMK_EXEC_PENDING) {
/* Ignore notify errors.
*
* @TODO It might be better to keep the correct result here, and
* ignore it in process_graph_event().
*/
lrmd__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
}
/* Migration history is preserved separately, which usually matters for
* multiple nodes and is important for future cluster transitions.
*/
} else if (pcmk__str_any_of(op->op_type, CRMD_ACTION_MIGRATE,
CRMD_ACTION_MIGRATED, NULL)) {
op_id = strdup(key);
} else if (did_rsc_op_fail(op, target_rc)) {
op_id = pcmk__op_key(op->rsc_id, "last_failure", 0);
if (op->interval_ms == 0) {
// Ensure 'last' gets updated, in case record-pending is true
op_id_additional = pcmk__op_key(op->rsc_id, "last", 0);
}
exit_reason = op->exit_reason;
} else if (op->interval_ms > 0) {
op_id = strdup(key);
} else {
op_id = pcmk__op_key(op->rsc_id, "last", 0);
}
again:
xml_op = pcmk__xe_match(parent, XML_LRM_TAG_RSC_OP, XML_ATTR_ID, op_id);
if (xml_op == NULL) {
xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
}
if (op->user_data == NULL) {
crm_debug("Generating fake transition key for: " PCMK__OP_FMT
" %d from %s", op->rsc_id, op->op_type, op->interval_ms,
op->call_id, origin);
local_user_data = pcmk__transition_key(-1, op->call_id, target_rc,
FAKE_TE_ID);
op->user_data = local_user_data;
}
if (magic == NULL) {
magic = crm_strdup_printf("%d:%d;%s", op->op_status, op->rc,
(const char *) op->user_data);
}
crm_xml_add(xml_op, XML_ATTR_ID, op_id);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
crm_xml_add(xml_op, XML_LRM_ATTR_EXIT_REASON, exit_reason == NULL ? "" : exit_reason);
crm_xml_add(xml_op, XML_LRM_ATTR_TARGET, node); /* For context during triage */
crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
crm_xml_add_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, op->interval_ms);
if (compare_version("2.1", caller_version) <= 0) {
if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
crm_trace("Timing data (" PCMK__OP_FMT
"): last=%u change=%u exec=%u queue=%u",
op->rsc_id, op->op_type, op->interval_ms,
op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
if ((op->interval_ms != 0) && (op->t_rcchange != 0)) {
// Recurring ops may have changed rc after initial run
crm_xml_add_ll(xml_op, XML_RSC_OP_LAST_CHANGE,
(long long) op->t_rcchange);
} else {
crm_xml_add_ll(xml_op, XML_RSC_OP_LAST_CHANGE,
(long long) op->t_run);
}
crm_xml_add_int(xml_op, XML_RSC_OP_T_EXEC, op->exec_time);
crm_xml_add_int(xml_op, XML_RSC_OP_T_QUEUE, op->queue_time);
}
}
if (pcmk__str_any_of(op->op_type, CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, NULL)) {
/*
* Record migrate_source and migrate_target always for migrate ops.
*/
const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
name = XML_LRM_ATTR_MIGRATE_TARGET;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
}
add_op_digest_to_xml(op, xml_op);
if (op_id_additional) {
free(op_id);
op_id = op_id_additional;
op_id_additional = NULL;
goto again;
}
if (local_user_data) {
free(local_user_data);
op->user_data = NULL;
}
free(magic);
free(op_id);
free(key);
return xml_op;
}
/*!
* \internal
* \brief Check whether an action shutdown-locks a resource to a node
*
* If the shutdown-lock cluster property is set, resources will not be recovered
* on a different node if cleanly stopped, and may start only on that same node.
* This function checks whether that applies to a given action, so that the
* transition graph can be marked appropriately.
*
* \param[in] action Action to check
*
* \return true if \p action locks its resource to the action's node,
* otherwise false
*/
bool
pcmk__action_locks_rsc_to_node(const pe_action_t *action)
{
// Only resource actions taking place on resource's lock node are locked
if ((action == NULL) || (action->rsc == NULL)
|| (action->rsc->lock_node == NULL) || (action->node == NULL)
|| (action->node->details != action->rsc->lock_node->details)) {
return false;
}
/* During shutdown, only stops are locked (otherwise, another action such as
* a demote would cause the controller to clear the lock)
*/
if (action->node->details->shutdown && (action->task != NULL)
&& (strcmp(action->task, RSC_STOP) != 0)) {
return false;
}
return true;
}
/* lowest to highest */
static gint
sort_action_id(gconstpointer a, gconstpointer b)
{
const pe_action_wrapper_t *action_wrapper2 = (const pe_action_wrapper_t *)a;
const pe_action_wrapper_t *action_wrapper1 = (const pe_action_wrapper_t *)b;
if (a == NULL) {
return 1;
}
if (b == NULL) {
return -1;
}
if (action_wrapper1->action->id < action_wrapper2->action->id) {
return 1;
}
if (action_wrapper1->action->id > action_wrapper2->action->id) {
return -1;
}
return 0;
}
/*!
* \internal
* \brief Remove any duplicate action inputs, merging action flags
*
* \param[in] action Action whose inputs should be checked
*/
void
pcmk__deduplicate_action_inputs(pe_action_t *action)
{
GList *item = NULL;
GList *next = NULL;
pe_action_wrapper_t *last_input = NULL;
action->actions_before = g_list_sort(action->actions_before,
sort_action_id);
for (item = action->actions_before; item != NULL; item = next) {
pe_action_wrapper_t *input = (pe_action_wrapper_t *) item->data;
next = item->next;
if ((last_input != NULL)
&& (input->action->id == last_input->action->id)) {
crm_trace("Input %s (%d) duplicate skipped for action %s (%d)",
input->action->uuid, input->action->id,
action->uuid, action->id);
/* For the purposes of scheduling, the ordering flags no longer
* matter, but crm_simulate looks at certain ones when creating a
* dot graph. Combining the flags is sufficient for that purpose.
*/
last_input->type |= input->type;
if (input->state == pe_link_dumped) {
last_input->state = pe_link_dumped;
}
free(item->data);
action->actions_before = g_list_delete_link(action->actions_before,
item);
} else {
last_input = input;
input->state = pe_link_not_dumped;
}
}
}
/*!
* \internal
* \brief Output all scheduled actions
*
* \param[in] data_set Cluster working set
*/
void
pcmk__output_actions(pe_working_set_t *data_set)
{
pcmk__output_t *out = data_set->priv;
// Output node (non-resource) actions
for (GList *iter = data_set->actions; iter != NULL; iter = iter->next) {
char *node_name = NULL;
char *task = NULL;
pe_action_t *action = (pe_action_t *) iter->data;
if (action->rsc != NULL) {
continue; // Resource actions will be output later
} else if (pcmk_is_set(action->flags, pe_action_optional)) {
continue; // This action was not scheduled
}
if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) {
task = strdup("Shutdown");
} else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) {
const char *op = g_hash_table_lookup(action->meta, "stonith_action");
task = crm_strdup_printf("Fence (%s)", op);
} else {
continue; // Don't display other node action types
}
if (pe__is_guest_node(action->node)) {
node_name = crm_strdup_printf("%s (resource: %s)",
pe__node_name(action->node),
action->node->details->remote_rsc->container->id);
} else if (action->node != NULL) {
node_name = crm_strdup_printf("%s", pe__node_name(action->node));
}
out->message(out, "node-action", task, node_name, action->reason);
free(node_name);
free(task);
}
// Output resource actions
for (GList *iter = data_set->resources; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
rsc->cmds->output_actions(rsc);
}
}
-/*!
- * \internal
- * \brief Schedule cancellation of a recurring action
- *
- * \param[in] rsc Resource that action is for
- * \param[in] call_id Action's call ID from history
- * \param[in] task Action name
- * \param[in] interval_ms Action interval
- * \param[in] node Node that history entry is for
- * \param[in] reason Short description of why action is being cancelled
- */
-static void
-schedule_cancel(pe_resource_t *rsc, const char *call_id, const char *task,
- guint interval_ms, pe_node_t *node, const char *reason)
-{
- pe_action_t *cancel = NULL;
-
- CRM_CHECK((rsc != NULL) && (task != NULL)
- && (node != NULL) && (reason != NULL),
- return);
-
- crm_info("Recurring %s-interval %s for %s will be stopped on %s: %s",
- pcmk__readable_interval(interval_ms), task, rsc->id,
- pe__node_name(node), reason);
- cancel = pcmk__new_cancel_action(rsc, task, interval_ms, node);
- add_hash_param(cancel->meta, XML_LRM_ATTR_CALLID, call_id);
-
- // Cancellations happen after stops
- pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel,
- pe_order_optional, rsc->cluster);
-}
-
/*!
* \internal
* \brief Check whether action from resource history is still in configuration
*
* \param[in] rsc Resource that action is for
* \param[in] task Action's name
* \param[in] interval_ms Action's interval (in milliseconds)
*
* \return true if action is still in resource configuration, otherwise false
*/
static bool
action_in_config(pe_resource_t *rsc, const char *task, guint interval_ms)
{
char *key = pcmk__op_key(rsc->id, task, interval_ms);
bool config = (find_rsc_op_entry(rsc, key) != NULL);
free(key);
return config;
}
/*!
* \internal
* \brief Get action name needed to compare digest for configuration changes
*
* \param[in] task Action name from history
* \param[in] interval_ms Action interval (in milliseconds)
*
* \return Action name whose digest should be compared
*/
static const char *
task_for_digest(const char *task, guint interval_ms)
{
/* Certain actions need to be compared against the parameters used to start
* the resource.
*/
if ((interval_ms == 0)
&& pcmk__str_any_of(task, RSC_STATUS, RSC_MIGRATED, RSC_PROMOTE, NULL)) {
task = RSC_START;
}
return task;
}
/*!
* \internal
* \brief Check whether only sanitized parameters to an action changed
*
* When collecting CIB files for troubleshooting, crm_report will mask
* sensitive resource parameters. If simulations were run using that, affected
* resources would appear to need a restart, which would complicate
* troubleshooting. To avoid that, we save a "secure digest" of non-sensitive
* parameters. This function used that digest to check whether only masked
* parameters are different.
*
* \param[in] xml_op Resource history entry with secure digest
* \param[in] digest_data Operation digest information being compared
* \param[in] data_set Cluster working set
*
* \return true if only sanitized parameters changed, otherwise false
*/
static bool
only_sanitized_changed(xmlNode *xml_op, const op_digest_cache_t *digest_data,
pe_working_set_t *data_set)
{
const char *digest_secure = NULL;
if (!pcmk_is_set(data_set->flags, pe_flag_sanitized)) {
// The scheduler is not being run as a simulation
return false;
}
digest_secure = crm_element_value(xml_op, XML_LRM_ATTR_SECURE_DIGEST);
return (digest_data->rc != RSC_DIGEST_MATCH) && (digest_secure != NULL)
&& (digest_data->digest_secure_calc != NULL)
&& (strcmp(digest_data->digest_secure_calc, digest_secure) == 0);
}
/*!
* \internal
* \brief Force a restart due to a configuration change
*
* \param[in] rsc Resource that action is for
* \param[in] task Name of action whose configuration changed
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] node Node where resource should be restarted
*/
static void
force_restart(pe_resource_t *rsc, const char *task, guint interval_ms,
pe_node_t *node)
{
char *key = pcmk__op_key(rsc->id, task, interval_ms);
pe_action_t *required = custom_action(rsc, key, task, NULL, FALSE, TRUE,
rsc->cluster);
pe_action_set_reason(required, "resource definition change", true);
trigger_unfencing(rsc, node, "Device parameters changed", NULL,
rsc->cluster);
}
-/*!
- * \internal
- * \brief Reschedule a recurring action
- *
- * \param[in] rsc Resource that action is for
- * \param[in] task Name of action being rescheduled
- * \param[in] interval_ms Action interval (in milliseconds)
- * \param[in] node Node where action should be rescheduled
- */
-static void
-reschedule_recurring(pe_resource_t *rsc, const char *task, guint interval_ms,
- pe_node_t *node)
-{
- pe_action_t *op = NULL;
-
- trigger_unfencing(rsc, node, "Device parameters changed (reschedule)",
- NULL, rsc->cluster);
- op = custom_action(rsc, pcmk__op_key(rsc->id, task, interval_ms),
- task, node, TRUE, TRUE, rsc->cluster);
- pe__set_action_flags(op, pe_action_reschedule);
-}
-
/*!
* \internal
* \brief Schedule a reload of a resource on a node
*
* \param[in] rsc Resource to reload
* \param[in] node Where resource should be reloaded
*/
static void
schedule_reload(pe_resource_t *rsc, pe_node_t *node)
{
pe_action_t *reload = NULL;
// For collective resources, just call recursively for children
if (rsc->variant > pe_native) {
g_list_foreach(rsc->children, (GFunc) schedule_reload, node);
return;
}
// Skip the reload in certain situations
if ((node == NULL)
|| !pcmk_is_set(rsc->flags, pe_rsc_managed)
|| pcmk_is_set(rsc->flags, pe_rsc_failed)) {
pe_rsc_trace(rsc, "Skip reload of %s:%s%s %s",
rsc->id,
pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : " unmanaged",
pcmk_is_set(rsc->flags, pe_rsc_failed)? " failed" : "",
(node == NULL)? "inactive" : node->details->uname);
return;
}
/* If a resource's configuration changed while a start was pending,
* force a full restart instead of a reload.
*/
if (pcmk_is_set(rsc->flags, pe_rsc_start_pending)) {
pe_rsc_trace(rsc, "%s: preventing agent reload because start pending",
rsc->id);
custom_action(rsc, stop_key(rsc), CRMD_ACTION_STOP, node, FALSE, TRUE,
rsc->cluster);
return;
}
// Schedule the reload
pe__set_resource_flags(rsc, pe_rsc_reload);
reload = custom_action(rsc, reload_key(rsc), CRMD_ACTION_RELOAD_AGENT, node,
FALSE, TRUE, rsc->cluster);
pe_action_set_reason(reload, "resource definition change", FALSE);
// Set orderings so that a required stop or demote cancels the reload
pcmk__new_ordering(NULL, NULL, reload, rsc, stop_key(rsc), NULL,
pe_order_optional|pe_order_then_cancels_first,
rsc->cluster);
pcmk__new_ordering(NULL, NULL, reload, rsc, demote_key(rsc), NULL,
pe_order_optional|pe_order_then_cancels_first,
rsc->cluster);
}
/*!
* \internal
* \brief Handle any configuration change for an action
*
* Given an action from resource history, if the resource's configuration
* changed since the action was done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, etc.).
*
* \param[in] rsc Resource that action is for
* \param[in] node Node that action was on
* \param[in] xml_op Action XML from resource history
*
* \return true if action configuration changed, otherwise false
*/
bool
pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op)
{
guint interval_ms = 0;
const char *task = NULL;
const op_digest_cache_t *digest_data = NULL;
CRM_CHECK((rsc != NULL) && (node != NULL) && (xml_op != NULL),
return false);
task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
CRM_CHECK(task != NULL, return false);
crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
// If this is a recurring action, check whether it has been orphaned
if (interval_ms > 0) {
if (action_in_config(rsc, task, interval_ms)) {
pe_rsc_trace(rsc, "%s-interval %s for %s on %s is in configuration",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
} else if (pcmk_is_set(rsc->cluster->flags,
pe_flag_stop_action_orphans)) {
- schedule_cancel(rsc,
- crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
- task, interval_ms, node, "orphan");
+ pcmk__schedule_cancel(rsc,
+ crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
+ task, interval_ms, node, "orphan");
return true;
} else {
pe_rsc_debug(rsc, "%s-interval %s for %s on %s is orphaned",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
return true;
}
}
crm_trace("Checking %s-interval %s for %s on %s for configuration changes",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node));
task = task_for_digest(task, interval_ms);
digest_data = rsc_action_digest_cmp(rsc, xml_op, node, rsc->cluster);
if (only_sanitized_changed(xml_op, digest_data, rsc->cluster)) {
if (!pcmk__is_daemon && (rsc->cluster->priv != NULL)) {
pcmk__output_t *out = rsc->cluster->priv;
out->info(out,
"Only 'private' parameters to %s-interval %s for %s "
"on %s changed: %s",
pcmk__readable_interval(interval_ms), task, rsc->id,
pe__node_name(node),
crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC));
}
return false;
}
switch (digest_data->rc) {
case RSC_DIGEST_RESTART:
crm_log_xml_debug(digest_data->params_restart, "params:restart");
force_restart(rsc, task, interval_ms, node);
return true;
case RSC_DIGEST_ALL:
case RSC_DIGEST_UNKNOWN:
// Changes that can potentially be handled by an agent reload
if (interval_ms > 0) {
/* Recurring actions aren't reloaded per se, they are just
* re-scheduled so the next run uses the new parameters.
* The old instance will be cancelled automatically.
*/
crm_log_xml_debug(digest_data->params_all, "params:reschedule");
- reschedule_recurring(rsc, task, interval_ms, node);
+ pcmk__reschedule_recurring(rsc, task, interval_ms, node);
} else if (crm_element_value(xml_op,
XML_LRM_ATTR_RESTART_DIGEST) != NULL) {
// Agent supports reload, so use it
trigger_unfencing(rsc, node,
"Device parameters changed (reload)", NULL,
rsc->cluster);
crm_log_xml_debug(digest_data->params_all, "params:reload");
schedule_reload(rsc, node);
} else {
pe_rsc_trace(rsc,
"Restarting %s because agent doesn't support reload",
rsc->id);
crm_log_xml_debug(digest_data->params_restart,
"params:restart");
force_restart(rsc, task, interval_ms, node);
}
return true;
default:
break;
}
return false;
}
/*!
* \internal
* \brief Create a list of resource's action history entries, sorted by call ID
*
* \param[in] rsc Resource whose history should be checked
* \param[in] rsc_entry Resource's status XML
* \param[out] start_index Where to store index of start-like action, if any
* \param[out] stop_index Where to store index of stop action, if any
*/
static GList *
rsc_history_as_list(pe_resource_t *rsc, xmlNode *rsc_entry,
int *start_index, int *stop_index)
{
GList *ops = NULL;
for (xmlNode *rsc_op = first_named_child(rsc_entry, XML_LRM_TAG_RSC_OP);
rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) {
ops = g_list_prepend(ops, rsc_op);
}
ops = g_list_sort(ops, sort_op_by_callid);
calculate_active_ops(ops, start_index, stop_index);
return ops;
}
/*!
* \internal
* \brief Process a resource's action history from the CIB status
*
* Given a resource's action history, if the resource's configuration
* changed since the actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] rsc_entry Resource's status XML
* \param[in] rsc Resource whose history is being processed
* \param[in] node Node whose history is being processed
*/
static void
process_rsc_history(xmlNode *rsc_entry, pe_resource_t *rsc, pe_node_t *node)
{
int offset = -1;
int stop_index = 0;
int start_index = 0;
GList *sorted_op_list = NULL;
if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
if (pe_rsc_is_anon_clone(uber_parent(rsc))) {
pe_rsc_trace(rsc,
"Skipping configuration check "
"for orphaned clone instance %s",
rsc->id);
} else {
pe_rsc_trace(rsc,
"Skipping configuration check and scheduling clean-up "
"for orphaned resource %s", rsc->id);
DeleteRsc(rsc, node, FALSE, rsc->cluster);
}
return;
}
if (pe_find_node_id(rsc->running_on, node->details->id) == NULL) {
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, false)) {
DeleteRsc(rsc, node, FALSE, rsc->cluster);
}
pe_rsc_trace(rsc,
"Skipping configuration check for %s "
"because no longer active on %s",
rsc->id, pe__node_name(node));
return;
}
pe_rsc_trace(rsc, "Checking for configuration changes for %s on %s",
rsc->id, pe__node_name(node));
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, true)) {
DeleteRsc(rsc, node, FALSE, rsc->cluster);
}
sorted_op_list = rsc_history_as_list(rsc, rsc_entry, &start_index,
&stop_index);
if (start_index < stop_index) {
return; // Resource is stopped
}
for (GList *iter = sorted_op_list; iter != NULL; iter = iter->next) {
xmlNode *rsc_op = (xmlNode *) iter->data;
const char *task = NULL;
guint interval_ms = 0;
if (++offset < start_index) {
// Skip actions that happened before a start
continue;
}
task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
crm_element_value_ms(rsc_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
if ((interval_ms > 0)
&& (pcmk_is_set(rsc->flags, pe_rsc_maintenance)
|| node->details->maintenance)) {
// Maintenance mode cancels recurring operations
- schedule_cancel(rsc,
- crm_element_value(rsc_op, XML_LRM_ATTR_CALLID),
- task, interval_ms, node, "maintenance mode");
+ pcmk__schedule_cancel(rsc,
+ crm_element_value(rsc_op, XML_LRM_ATTR_CALLID),
+ task, interval_ms, node, "maintenance mode");
} else if ((interval_ms > 0)
|| pcmk__strcase_any_of(task, RSC_STATUS, RSC_START,
RSC_PROMOTE, RSC_MIGRATED, NULL)) {
/* If a resource operation failed, and the operation's definition
* has changed, clear any fail count so they can be retried fresh.
*/
if (pe__bundle_needs_remote_name(rsc, rsc->cluster)) {
/* We haven't allocated resources to nodes yet, so if the
* REMOTE_CONTAINER_HACK is used, we may calculate the digest
* based on the literal "#uname" value rather than the properly
* substituted value. That would mistakenly make the action
* definition appear to have been changed. Defer the check until
* later in this case.
*/
pe__add_param_check(rsc_op, rsc, node, pe_check_active,
rsc->cluster);
} else if (pcmk__check_action_config(rsc, node, rsc_op)
&& (pe_get_failcount(node, rsc, NULL, pe_fc_effective,
NULL, rsc->cluster) != 0)) {
pe__clear_failcount(rsc, node, "action definition changed",
rsc->cluster);
}
}
}
g_list_free(sorted_op_list);
}
/*!
* \internal
* \brief Process a node's action history from the CIB status
*
* Given a node's resource history, if the resource's configuration changed
* since the actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] node Node whose history is being processed
* \param[in] lrm_rscs Node's from CIB status XML
* \param[in] data_set Cluster working set
*/
static void
process_node_history(pe_node_t *node, xmlNode *lrm_rscs, pe_working_set_t *data_set)
{
crm_trace("Processing node history for %s", pe__node_name(node));
for (xmlNode *rsc_entry = first_named_child(lrm_rscs, XML_LRM_TAG_RESOURCE);
rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
if (xml_has_children(rsc_entry)) {
GList *result = pcmk__rscs_matching_id(ID(rsc_entry), data_set);
for (GList *iter = result; iter != NULL; iter = iter->next) {
pe_resource_t *rsc = (pe_resource_t *) iter->data;
if (rsc->variant == pe_native) {
process_rsc_history(rsc_entry, rsc, node);
}
}
g_list_free(result);
}
}
}
// XPath to find a node's resource history
#define XPATH_NODE_HISTORY "/" XML_TAG_CIB "/" XML_CIB_TAG_STATUS \
"/" XML_CIB_TAG_STATE "[@" XML_ATTR_UNAME "='%s']" \
"/" XML_CIB_TAG_LRM "/" XML_LRM_TAG_RESOURCES
/*!
* \internal
* \brief Process any resource configuration changes in the CIB status
*
* Go through all nodes' resource history, and if a resource's configuration
* changed since its actions were done, schedule any actions needed (restart,
* reload, unfencing, rescheduling recurring actions, clean-up, etc.).
* (This also cancels recurring actions for maintenance mode, which is not
* entirely related but convenient to do here.)
*
* \param[in] data_set Cluster working set
*/
void
pcmk__handle_rsc_config_changes(pe_working_set_t *data_set)
{
crm_trace("Check resource and action configuration for changes");
/* Rather than iterate through the status section, iterate through the nodes
* and search for the appropriate status subsection for each. This skips
* orphaned nodes and lets us eliminate some cases before searching the XML.
*/
for (GList *iter = data_set->nodes; iter != NULL; iter = iter->next) {
pe_node_t *node = (pe_node_t *) iter->data;
/* Don't bother checking actions for a node that can't run actions ...
* unless it's in maintenance mode, in which case we still need to
* cancel any existing recurring monitors.
*/
if (node->details->maintenance
|| pcmk__node_available(node, false, false)) {
char *xpath = NULL;
xmlNode *history = NULL;
xpath = crm_strdup_printf(XPATH_NODE_HISTORY, node->details->uname);
history = get_xpath_object(xpath, data_set->input, LOG_NEVER);
free(xpath);
process_node_history(node, history, data_set);
}
}
}
diff --git a/lib/pacemaker/pcmk_sched_primitive.c b/lib/pacemaker/pcmk_sched_primitive.c
index 8c568206cf..baa7696ad2 100644
--- a/lib/pacemaker/pcmk_sched_primitive.c
+++ b/lib/pacemaker/pcmk_sched_primitive.c
@@ -1,2018 +1,1469 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
-#include
#include
#include "libpacemaker_private.h"
-static void Recurring(pe_resource_t *rsc, pe_action_t *start, pe_node_t *node,
- pe_working_set_t *data_set);
-static void Recurring_Stopped(pe_resource_t *rsc, pe_action_t *start, pe_node_t *node,
- pe_working_set_t *data_set);
-static void RecurringOp_Stopped(pe_resource_t *rsc, pe_action_t *start, pe_node_t *node,
- xmlNode *operation, pe_working_set_t *data_set);
-
gboolean DeleteRsc(pe_resource_t * rsc, pe_node_t * node, gboolean optional, pe_working_set_t * data_set);
static bool StopRsc(pe_resource_t *rsc, pe_node_t *next, bool optional);
static bool StartRsc(pe_resource_t *rsc, pe_node_t *next, bool optional);
static bool DemoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional);
static bool PromoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional);
static bool RoleError(pe_resource_t *rsc, pe_node_t *next, bool optional);
static bool NullOp(pe_resource_t *rsc, pe_node_t *next, bool optional);
/* This array says what the *next* role should be when transitioning from one
* role to another. For example going from Stopped to Promoted, the next role is
* RSC_ROLE_UNPROMOTED, because the resource must be started before being promoted.
* The current state then becomes Started, which is fed into this array again,
* giving a next role of RSC_ROLE_PROMOTED.
*/
static enum rsc_role_e rsc_state_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
/* Current state Next state*/
/* Unknown Stopped Started Unpromoted Promoted */
/* Unknown */ { RSC_ROLE_UNKNOWN, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED },
/* Stopped */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STARTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED },
/* Started */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STARTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED },
/* Unpromoted */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED },
/* Promoted */ { RSC_ROLE_STOPPED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED },
};
typedef bool (*rsc_transition_fn)(pe_resource_t *rsc, pe_node_t *next,
bool optional);
// This array picks the function needed to transition from one role to another
static rsc_transition_fn rsc_action_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = {
/* Current state Next state */
/* Unknown Stopped Started Unpromoted Promoted */
/* Unknown */ { RoleError, StopRsc, RoleError, RoleError, RoleError, },
/* Stopped */ { RoleError, NullOp, StartRsc, StartRsc, RoleError, },
/* Started */ { RoleError, StopRsc, NullOp, NullOp, PromoteRsc, },
/* Unpromoted */ { RoleError, StopRsc, StopRsc, NullOp, PromoteRsc, },
/* Promoted */ { RoleError, DemoteRsc, DemoteRsc, DemoteRsc, NullOp, },
};
/*!
* \internal
* \brief Get a list of a resource's allowed nodes sorted by node weight
*
* \param[in] rsc Resource to check
*
* \return List of allowed nodes sorted by node weight
*/
static GList *
sorted_allowed_nodes(const pe_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, pe__current_node(rsc));
}
}
return NULL;
}
/*!
* \internal
* \brief Assign a resource to its best allowed node, if possible
*
* \param[in] rsc Resource to choose a node for
* \param[in] prefer If not NULL, prefer this node when all else equal
*
* \return true if \p rsc could be assigned to a node, otherwise false
*/
static bool
assign_best_node(pe_resource_t *rsc, pe_node_t *prefer)
{
GList *nodes = NULL;
pe_node_t *chosen = NULL;
pe_node_t *best = NULL;
bool result = false;
pcmk__ban_insufficient_capacity(rsc, &prefer);
if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
// We've already finished assignment of resources to nodes
return rsc->allocated_to != NULL;
}
// Sort allowed nodes by weight
nodes = sorted_allowed_nodes(rsc);
if (nodes != NULL) {
best = (pe_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) {
pe_rsc_trace(rsc, "Preferred node %s for %s was unknown",
pe__node_name(prefer), rsc->id);
/* Favor the preferred node as long as its weight 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 weight is less than INFINITY.
*/
} else if (chosen->weight < best->weight) {
pe_rsc_trace(rsc, "Preferred node %s for %s was unsuitable",
pe__node_name(chosen), rsc->id);
chosen = NULL;
} else if (!pcmk__node_available(chosen, true, false)) {
pe_rsc_trace(rsc, "Preferred node %s for %s was unavailable",
pe__node_name(chosen), rsc->id);
chosen = NULL;
} else {
pe_rsc_trace(rsc,
"Chose preferred node %s for %s (ignoring %d candidates)",
pe__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 (!pe_rsc_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
* distribute_children() 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.
*/
pe_node_t *running = pe__current_node(rsc);
if (running == NULL) {
// Nothing to do
} else if (!pcmk__node_available(running, true, false)) {
pe_rsc_trace(rsc, "Current node for %s (%s) can't run resources",
rsc->id, pe__node_name(running));
} else {
int nodes_with_best_score = 1;
for (GList *iter = nodes->next; iter; iter = iter->next) {
pe_node_t *allowed = (pe_node_t *) iter->data;
if (allowed->weight != chosen->weight) {
// The nodes are sorted by weight, so no more are equal
break;
}
if (allowed->details == running->details) {
// Scores are equal, so prefer the current node
chosen = allowed;
}
nodes_with_best_score++;
}
if (nodes_with_best_score > 1) {
do_crm_log(((chosen->weight >= INFINITY)? LOG_WARNING : LOG_INFO),
"Chose %s for %s from %d nodes with score %s",
pe__node_name(chosen), rsc->id,
nodes_with_best_score,
pcmk_readable_score(chosen->weight));
}
}
}
pe_rsc_trace(rsc, "Chose %s for %s from %d candidates",
pe__node_name(chosen), rsc->id, g_list_length(nodes));
}
result = pcmk__assign_primitive(rsc, chosen, false);
g_list_free(nodes);
return result;
}
/*!
* \internal
* \brief Apply a "this with" colocation to a node's allowed node scores
*
* \param[in] data Colocation to apply
* \param[in] user_data Resource being assigned
*/
static void
apply_this_with(void *data, void *user_data)
{
pcmk__colocation_t *colocation = (pcmk__colocation_t *) data;
pe_resource_t *rsc = (pe_resource_t *) user_data;
GHashTable *archive = NULL;
pe_resource_t *other = colocation->primary;
// In certain cases, we will need to revert the node scores
if ((colocation->dependent_role >= RSC_ROLE_PROMOTED)
|| ((colocation->score < 0) && (colocation->score > -INFINITY))) {
archive = pcmk__copy_node_table(rsc->allowed_nodes);
}
pe_rsc_trace(rsc,
"%s: Assigning colocation %s primary %s first"
"(score=%d role=%s)",
rsc->id, colocation->id, other->id,
colocation->score, role2text(colocation->dependent_role));
other->cmds->assign(other, NULL);
// 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)) {
pe_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 Apply a "with this" colocation to a node's allowed node scores
*
* \param[in] data Colocation to apply
* \param[in] user_data Resource being assigned
*/
static void
apply_with_this(void *data, void *user_data)
{
pcmk__colocation_t *colocation = (pcmk__colocation_t *) data;
pe_resource_t *rsc = (pe_resource_t *) user_data;
pe_resource_t *other = colocation->dependent;
const float factor = colocation->score / (float) INFINITY;
if (!pcmk__colocation_has_influence(colocation, NULL)) {
return;
}
pe_rsc_trace(rsc,
"%s: Incorporating attenuated %s assignment scores due "
"to colocation %s", rsc->id, other->id, colocation->id);
other->cmds->add_colocated_node_scores(other, rsc->id,
&rsc->allowed_nodes,
colocation->node_attribute,
factor, pcmk__coloc_select_active);
}
/*!
* \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(pe_resource_t *connection)
{
pe_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 != RSC_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" : ""),
role2text(connection->next_role));
remote_node->details->shutdown = TRUE;
}
}
/*!
* \internal
* \brief Assign a primitive resource to a node
*
* \param[in] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*/
pe_node_t *
pcmk__primitive_assign(pe_resource_t *rsc, pe_node_t *prefer)
{
CRM_ASSERT(rsc != NULL);
// Never assign a child without parent being assigned first
if ((rsc->parent != NULL)
&& !pcmk_is_set(rsc->parent->flags, pe_rsc_allocating)) {
pe_rsc_debug(rsc, "%s: Assigning parent %s first",
rsc->id, rsc->parent->id);
rsc->parent->cmds->assign(rsc->parent, prefer);
}
if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
return rsc->allocated_to; // Assignment has already been done
}
// Ensure we detect assignment loops
if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) {
pe_rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id);
return NULL;
}
pe__set_resource_flags(rsc, pe_rsc_allocating);
pe__show_node_weights(true, rsc, "Pre-assignment", rsc->allowed_nodes,
rsc->cluster);
g_list_foreach(rsc->rsc_cons, apply_this_with, rsc);
pe__show_node_weights(true, rsc, "Post-this-with", rsc->allowed_nodes,
rsc->cluster);
g_list_foreach(rsc->rsc_cons_lhs, apply_with_this, rsc);
if (rsc->next_role == RSC_ROLE_STOPPED) {
pe_rsc_trace(rsc,
"Banning %s from all nodes because it will be stopped",
rsc->id);
resource_location(rsc, NULL, -INFINITY, XML_RSC_ATTR_TARGET_ROLE,
rsc->cluster);
} else if ((rsc->next_role > rsc->role)
&& !pcmk_is_set(rsc->cluster->flags, pe_flag_have_quorum)
&& (rsc->cluster->no_quorum_policy == no_quorum_freeze)) {
crm_notice("Resource %s cannot be elevated from %s to %s due to "
"no-quorum-policy=freeze",
rsc->id, role2text(rsc->role), role2text(rsc->next_role));
pe__set_next_role(rsc, rsc->role, "no-quorum-policy=freeze");
}
pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_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, pe_flag_stonith_enabled)
&& !pcmk_is_set(rsc->cluster->flags, pe_flag_have_stonith_resource)) {
pe__clear_resource_flags(rsc, pe_rsc_managed);
}
if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
// Unmanaged resources stay on their current node
const char *reason = NULL;
pe_node_t *assign_to = NULL;
pe__set_next_role(rsc, rsc->role, "unmanaged");
assign_to = pe__current_node(rsc);
if (assign_to == NULL) {
reason = "inactive";
} else if (rsc->role == RSC_ROLE_PROMOTED) {
reason = "promoted";
} else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
reason = "failed";
} else {
reason = "active";
}
pe_rsc_info(rsc, "Unmanaged resource %s assigned to %s: %s", rsc->id,
(assign_to? assign_to->details->uname : "no node"), reason);
pcmk__assign_primitive(rsc, assign_to, true);
} else if (pcmk_is_set(rsc->cluster->flags, pe_flag_stop_everything)) {
pe_rsc_debug(rsc, "Forcing %s to stop: stop-all-resources", rsc->id);
pcmk__assign_primitive(rsc, NULL, true);
} else if (pcmk_is_set(rsc->flags, pe_rsc_provisional)
&& assign_best_node(rsc, prefer)) {
// Assignment successful
} else if (rsc->allocated_to == NULL) {
if (!pcmk_is_set(rsc->flags, pe_rsc_orphan)) {
pe_rsc_info(rsc, "Resource %s cannot run anywhere", rsc->id);
} else if (rsc->running_on != NULL) {
pe_rsc_info(rsc, "Stopping orphan resource %s", rsc->id);
}
} else {
pe_rsc_debug(rsc, "%s: pre-assigned to %s", rsc->id,
pe__node_name(rsc->allocated_to));
}
pe__clear_resource_flags(rsc, pe_rsc_allocating);
if (rsc->is_remote_node) {
remote_connection_assigned(rsc);
}
return rsc->allocated_to;
}
-/*!
- * \internal
- * \brief Parse an interval from XML
- *
- * \param[in] xml XML containing an interval attribute
- *
- * \return Interval parsed from XML (or 0 as default)
- */
-static guint
-xe_interval(const xmlNode *xml)
-{
- return crm_parse_interval_spec(crm_element_value(xml,
- XML_LRM_ATTR_INTERVAL));
-}
-
-/*!
- * \internal
- * \brief Check whether an operation exists multiple times in resource history
- *
- * \param[in] rsc Resource with history to search
- * \param[in] name Name of action to search for
- * \param[in] interval_ms Interval (in milliseconds) of action to search for
- *
- * \return true if an operation with \p name and \p interval_ms exists more than
- * once in the operation history of \p rsc, otherwise false
- */
-static bool
-is_op_dup(const pe_resource_t *rsc, const char *name, guint interval_ms)
-{
- const char *id = NULL;
-
- for (xmlNode *op = first_named_child(rsc->ops_xml, "op");
- op != NULL; op = crm_next_same_xml(op)) {
-
- // Check whether action name and interval match
- if (!pcmk__str_eq(crm_element_value(op, "name"),
- name, pcmk__str_none)
- || (xe_interval(op) != interval_ms)) {
- continue;
- }
-
- if (ID(op) == NULL) {
- continue; // Shouldn't be possible
- }
-
- if (id == NULL) {
- id = ID(op); // First matching op
- } else {
- pcmk__config_err("Operation %s is duplicate of %s (do not use "
- "same name and interval combination more "
- "than once per resource)", ID(op), id);
- return true;
- }
- }
- return false;
-}
-
-/*!
- * \internal
- * \brief Check whether an action name is one that can be recurring
- *
- * \param[in] name Action name to check
- *
- * \return true if \p name is an action known to be unsuitable as a recurring
- * operation, otherwise false
- *
- * \note Pacemaker's current philosophy is to allow users to configure recurring
- * operations except for a short list of actions known not to be suitable
- * for that (as opposed to allowing only actions known to be suitable,
- * which includes only monitor). Among other things, this approach allows
- * users to define their own custom operations and make them recurring,
- * though that use case is not well tested.
- */
-static bool
-op_cannot_recur(const char *name)
-{
- return pcmk__str_any_of(name, RSC_STOP, RSC_START, RSC_DEMOTE, RSC_PROMOTE,
- CRMD_ACTION_RELOAD_AGENT, CRMD_ACTION_MIGRATE,
- CRMD_ACTION_MIGRATED, NULL);
-}
-
-/*!
- * \internal
- * \brief Check whether a resource history entry is for a recurring action
- *
- * \param[in] rsc Resource that history entry is for
- * \param[in] op Resource history entry to check
- * \param[out] key Will be set to operation key if recurring
- * \param[out] interval_ms Will be set to interval from history entry
- */
-static bool
-is_recurring_history(const pe_resource_t *rsc, const xmlNode *op, char **key,
- guint *interval_ms)
-{
- const char *name = NULL;
-
- *interval_ms = xe_interval(op);
- if (*interval_ms == 0) {
- return false; // Not recurring
- }
-
- if (pcmk__str_empty(ID(op))) {
- pcmk__config_err("Ignoring resource history entry without ID");
- return false; // Shouldn't be possible (unless CIB was manually edited)
- }
-
- name = crm_element_value(op, "name");
- if (op_cannot_recur(name)) {
- pcmk__config_err("Ignoring %s because action '%s' cannot be recurring",
- ID(op), name);
- return false;
- }
-
- // There should only be one recurring operation per action/interval
- if (is_op_dup(rsc, name, *interval_ms)) {
- return false;
- }
-
- // Disabled resources don't get monitored
- *key = pcmk__op_key(rsc->id, name, *interval_ms);
- if (find_rsc_op_entry(rsc, *key) == NULL) {
- crm_trace("Not creating recurring action %s for disabled resource %s",
- ID(op), rsc->id);
- free(*key);
- return false;
- }
-
- return true;
-}
-
-/*!
- * \internal
- * \brief Check whether a recurring action for an active role should be optional
- *
- * \param[in] rsc Resource that recurring action is for
- * \param[in] node Node that \p rsc will be active on (if any)
- * \param[in] key Operation key for recurring action to check
- * \param[in] start Start action for \p rsc
- *
- * \return true if recurring action should be optional, otherwise false
- */
-static bool
-active_recurring_should_be_optional(const pe_resource_t *rsc,
- const pe_node_t *node, const char *key,
- pe_action_t *start)
-{
- GList *possible_matches = NULL;
-
- if (node == NULL) { // Should only be possible if unmanaged and stopped
- pe_rsc_trace(rsc, "%s will be mandatory because resource is unmanaged",
- key);
- return false;
- }
-
- if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL),
- pe_action_optional)) {
- pe_rsc_trace(rsc, "%s will be mandatory because %s is",
- key, start->uuid);
- return false;
- }
-
- possible_matches = find_actions_exact(rsc->actions, key, node);
- if (possible_matches == NULL) {
- pe_rsc_trace(rsc, "%s will be mandatory because it is not active on %s",
- key, pe__node_name(node));
- return false;
- }
-
- for (GList *iter = possible_matches; iter != NULL; iter = iter->next) {
- pe_action_t *op = (pe_action_t *) iter->data;
-
- if (pcmk_is_set(op->flags, pe_action_reschedule)) {
- pe_rsc_trace(rsc,
- "%s will be mandatory because "
- "it needs to be rescheduled", key);
- g_list_free(possible_matches);
- return false;
- }
- }
-
- g_list_free(possible_matches);
- return true;
-}
-
-/*!
- * \internal
- * \brief Create recurring action from resource history entry for an active role
- *
- * \param[in,out] rsc Resource that resource history is for
- * \param[in] start Start action for \p rsc on \p node
- * \param[in] node Node that resource will be active on (if any)
- * \param[in] op Resource history entry
- */
-static void
-recurring_op_for_active(pe_resource_t *rsc, pe_action_t *start,
- const pe_node_t *node, const xmlNode *op)
-{
- char *key = NULL;
- const char *name = NULL;
- const char *role = NULL;
-
- guint interval_ms = 0;
- pe_action_t *mon = NULL;
- bool is_optional = true;
-
- // We're only interested in recurring actions for active roles
- role = crm_element_value(op, "role");
- if ((role != NULL) && (text2role(role) == RSC_ROLE_STOPPED)) {
- return;
- }
-
- if (!is_recurring_history(rsc, op, &key, &interval_ms)) {
- return;
- }
-
- name = crm_element_value(op, "name");
- is_optional = active_recurring_should_be_optional(rsc, node, key, start);
-
- if (((role != NULL) && (rsc->next_role != text2role(role)))
- || ((role == NULL) && (rsc->next_role == RSC_ROLE_PROMOTED))) {
- // Configured monitor role doesn't match role resource will have
-
- if (is_optional) { // It's running, so cancel it
- char *after_key = NULL;
- pe_action_t *cancel_op = pcmk__new_cancel_action(rsc, name,
- interval_ms, node);
-
- switch (rsc->role) {
- case RSC_ROLE_UNPROMOTED:
- case RSC_ROLE_STARTED:
- if (rsc->next_role == RSC_ROLE_PROMOTED) {
- after_key = promote_key(rsc);
-
- } else if (rsc->next_role == RSC_ROLE_STOPPED) {
- after_key = stop_key(rsc);
- }
-
- break;
- case RSC_ROLE_PROMOTED:
- after_key = demote_key(rsc);
- break;
- default:
- break;
- }
-
- if (after_key) {
- pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL,
- pe_order_runnable_left, rsc->cluster);
- }
- }
-
- do_crm_log((is_optional? LOG_INFO : LOG_TRACE),
- "%s recurring action %s because %s configured for %s role "
- "(not %s)",
- (is_optional? "Cancelling" : "Ignoring"), key, ID(op),
- ((role == NULL)? role2text(RSC_ROLE_UNPROMOTED) : role),
- role2text(rsc->next_role));
- free(key);
- return;
- }
-
- pe_rsc_trace(rsc,
- "Creating %s recurring action %s for %s (%s %s on %s)",
- (is_optional? "optional" : "mandatory"), key,
- ID(op), rsc->id, role2text(rsc->next_role),
- pe__node_name(node));
-
- mon = custom_action(rsc, key, name, node, is_optional, TRUE, rsc->cluster);
-
- if (!pcmk_is_set(start->flags, pe_action_runnable)) {
- pe_rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid);
- pe__clear_action_flags(mon, pe_action_runnable);
-
- } else if ((node == NULL) || !node->details->online
- || node->details->unclean) {
- pe_rsc_trace(rsc, "%s is unrunnable because no node is available",
- mon->uuid);
- pe__clear_action_flags(mon, pe_action_runnable);
-
- } else if (!pcmk_is_set(mon->flags, pe_action_optional)) {
- pe_rsc_info(rsc, "Start %s-interval %s for %s on %s",
- pcmk__readable_interval(interval_ms), mon->task, rsc->id,
- pe__node_name(node));
- }
-
- if (rsc->next_role == RSC_ROLE_PROMOTED) {
- pe__add_action_expected_result(mon, CRM_EX_PROMOTED);
- }
-
- // Order monitor relative to other actions
- if ((node == NULL) || pcmk_is_set(rsc->flags, pe_rsc_managed)) {
- pcmk__new_ordering(rsc, start_key(rsc), NULL,
- NULL, strdup(mon->uuid), mon,
- pe_order_implies_then|pe_order_runnable_left,
- rsc->cluster);
-
- pcmk__new_ordering(rsc, reload_key(rsc), NULL,
- NULL, strdup(mon->uuid), mon,
- pe_order_implies_then|pe_order_runnable_left,
- rsc->cluster);
-
- if (rsc->next_role == RSC_ROLE_PROMOTED) {
- pcmk__new_ordering(rsc, promote_key(rsc), NULL,
- rsc, NULL, mon,
- pe_order_optional|pe_order_runnable_left,
- rsc->cluster);
-
- } else if (rsc->role == RSC_ROLE_PROMOTED) {
- pcmk__new_ordering(rsc, demote_key(rsc), NULL,
- rsc, NULL, mon,
- pe_order_optional|pe_order_runnable_left,
- rsc->cluster);
- }
- }
-}
-
-static void
-Recurring(pe_resource_t * rsc, pe_action_t * start, pe_node_t * node, pe_working_set_t * data_set)
-{
- if (!pcmk_is_set(rsc->flags, pe_rsc_maintenance) &&
- (node == NULL || node->details->maintenance == FALSE)) {
- xmlNode *operation = NULL;
-
- for (operation = pcmk__xe_first_child(rsc->ops_xml);
- operation != NULL;
- operation = pcmk__xe_next(operation)) {
-
- if (pcmk__str_eq((const char *)operation->name, "op", pcmk__str_none)) {
- recurring_op_for_active(rsc, start, node, operation);
- }
- }
- }
-}
-
-static void
-RecurringOp_Stopped(pe_resource_t * rsc, pe_action_t * start, pe_node_t * node,
- xmlNode * operation, pe_working_set_t * data_set)
-{
- char *key = NULL;
- const char *name = NULL;
- const char *role = NULL;
- const char *node_uname = node? node->details->uname : "n/a";
-
- guint interval_ms = 0;
- GList *possible_matches = NULL;
- GList *gIter = NULL;
-
- /* Only process for the operations with role="Stopped" */
- role = crm_element_value(operation, "role");
- if (role == NULL || text2role(role) != RSC_ROLE_STOPPED) {
- return;
- }
-
- if (!is_recurring_history(rsc, operation, &key, &interval_ms)) {
- return;
- }
-
- name = crm_element_value(operation, "name");
-
- // @TODO add support
- if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) {
- crm_notice("Ignoring %s (recurring monitors for Stopped role are "
- "not supported for anonymous clones)",
- ID(operation));
- return;
- }
-
- pe_rsc_trace(rsc,
- "Creating recurring action %s for %s in role %s on nodes where it should not be running",
- ID(operation), rsc->id, role2text(rsc->next_role));
-
- /* if the monitor exists on the node where the resource will be running, cancel it */
- if (node != NULL) {
- possible_matches = find_actions_exact(rsc->actions, key, node);
- if (possible_matches) {
- pe_action_t *cancel_op = NULL;
-
- g_list_free(possible_matches);
-
- cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node);
-
- if ((rsc->next_role == RSC_ROLE_STARTED)
- || (rsc->next_role == RSC_ROLE_UNPROMOTED)) {
- /* rsc->role == RSC_ROLE_STOPPED: cancel the monitor before start */
- /* rsc->role == RSC_ROLE_STARTED: for a migration, cancel the monitor on the target node before start */
- pcmk__new_ordering(rsc, NULL, cancel_op, rsc, start_key(rsc),
- NULL, pe_order_runnable_left, data_set);
- }
-
- pe_rsc_info(rsc, "Cancel action %s (%s vs. %s) on %s",
- key, role, role2text(rsc->next_role), node_uname);
- }
- }
-
- for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
- pe_node_t *stop_node = (pe_node_t *) gIter->data;
- const char *stop_node_uname = stop_node->details->uname;
- gboolean is_optional = TRUE;
- gboolean probe_is_optional = TRUE;
- gboolean stop_is_optional = TRUE;
- pe_action_t *stopped_mon = NULL;
- GList *stop_ops = NULL;
- GList *local_gIter = NULL;
-
- if (node && pcmk__str_eq(stop_node_uname, node_uname, pcmk__str_casei)) {
- continue;
- }
-
- pe_rsc_trace(rsc, "Creating recurring action %s for %s on %s",
- ID(operation), rsc->id,
- pcmk__s(stop_node_uname, "unknown node"));
-
- /* start a monitor for an already stopped resource */
- possible_matches = find_actions_exact(rsc->actions, key, stop_node);
- if (possible_matches == NULL) {
- pe_rsc_trace(rsc, "Marking %s mandatory on %s: not active", key,
- pcmk__s(stop_node_uname, "unknown node"));
- is_optional = FALSE;
- } else {
- pe_rsc_trace(rsc, "Marking %s optional on %s: already active", key,
- pcmk__s(stop_node_uname, "unknown node"));
- is_optional = TRUE;
- g_list_free(possible_matches);
- }
-
- stopped_mon = custom_action(rsc, strdup(key), name, stop_node, is_optional, TRUE, data_set);
-
- pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING);
-
- if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
- GList *probes = pe__resource_actions(rsc, stop_node, RSC_STATUS,
- FALSE);
- GList *pIter = NULL;
-
- for (pIter = probes; pIter != NULL; pIter = pIter->next) {
- pe_action_t *probe = (pe_action_t *) pIter->data;
-
- order_actions(probe, stopped_mon, pe_order_runnable_left);
- crm_trace("%s then %s on %s",
- probe->uuid, stopped_mon->uuid,
- pe__node_name(stop_node));
- }
-
- g_list_free(probes);
- }
-
- stop_ops = pe__resource_actions(rsc, stop_node, RSC_STOP, TRUE);
-
- for (local_gIter = stop_ops; local_gIter != NULL; local_gIter = local_gIter->next) {
- pe_action_t *stop = (pe_action_t *) local_gIter->data;
-
- if (!pcmk_is_set(stop->flags, pe_action_optional)) {
- stop_is_optional = FALSE;
- }
-
- if (!pcmk_is_set(stop->flags, pe_action_runnable)) {
- crm_debug("%s\t %s (cancelled : stop un-runnable)",
- pcmk__s(stop_node_uname, ""),
- stopped_mon->uuid);
- pe__clear_action_flags(stopped_mon, pe_action_runnable);
- }
-
- if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
- pcmk__new_ordering(rsc, stop_key(rsc), stop, NULL, strdup(key),
- stopped_mon,
- pe_order_implies_then|pe_order_runnable_left,
- data_set);
- }
-
- }
-
- if (stop_ops) {
- g_list_free(stop_ops);
- }
-
- if (is_optional == FALSE && probe_is_optional && stop_is_optional
- && !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
- pe_rsc_trace(rsc, "Marking %s optional on %s due to unmanaged",
- key, pcmk__s(stop_node_uname, "unknown node"));
- pe__set_action_flags(stopped_mon, pe_action_optional);
- }
-
- if (pcmk_is_set(stopped_mon->flags, pe_action_optional)) {
- pe_rsc_trace(rsc, "%s\t %s (optional)",
- pcmk__s(stop_node_uname, ""),
- stopped_mon->uuid);
- }
-
- if (stop_node->details->online == FALSE || stop_node->details->unclean) {
- pe_rsc_debug(rsc, "%s\t %s (cancelled : no node available)",
- pcmk__s(stop_node_uname, ""),
- stopped_mon->uuid);
- pe__clear_action_flags(stopped_mon, pe_action_runnable);
- }
-
- if (pcmk_is_set(stopped_mon->flags, pe_action_runnable)
- && !pcmk_is_set(stopped_mon->flags, pe_action_optional)) {
- crm_notice(" Start recurring %s (%us) for %s on %s", stopped_mon->task,
- interval_ms / 1000, rsc->id,
- pcmk__s(stop_node_uname, "unknown node"));
- }
- }
-
- free(key);
-}
-
-static void
-Recurring_Stopped(pe_resource_t * rsc, pe_action_t * start, pe_node_t * node, pe_working_set_t * data_set)
-{
- if (!pcmk_is_set(rsc->flags, pe_rsc_maintenance) &&
- (node == NULL || node->details->maintenance == FALSE)) {
- xmlNode *operation = NULL;
-
- for (operation = pcmk__xe_first_child(rsc->ops_xml);
- operation != NULL;
- operation = pcmk__xe_next(operation)) {
-
- if (pcmk__str_eq((const char *)operation->name, "op", pcmk__str_none)) {
- RecurringOp_Stopped(rsc, start, node, operation, data_set);
- }
- }
- }
-}
-
static void
handle_migration_actions(pe_resource_t * rsc, pe_node_t *current, pe_node_t *chosen, pe_working_set_t * data_set)
{
pe_action_t *migrate_to = NULL;
pe_action_t *migrate_from = NULL;
pe_action_t *start = NULL;
pe_action_t *stop = NULL;
gboolean partial = rsc->partial_migration_target ? TRUE : FALSE;
pe_rsc_trace(rsc, "Processing migration actions %s moving from %s to %s . partial migration = %s",
rsc->id, current->details->id, chosen->details->id, partial ? "TRUE" : "FALSE");
start = start_action(rsc, chosen, TRUE);
stop = stop_action(rsc, current, TRUE);
if (partial == FALSE) {
migrate_to = custom_action(rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0),
RSC_MIGRATE, current, TRUE, TRUE, data_set);
}
migrate_from = custom_action(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0),
RSC_MIGRATED, chosen, TRUE, TRUE, data_set);
if ((migrate_to && migrate_from) || (migrate_from && partial)) {
pe__set_action_flags(start, pe_action_migrate_runnable);
pe__set_action_flags(stop, pe_action_migrate_runnable);
// This is easier than trying to delete it from the graph
pe__set_action_flags(start, pe_action_pseudo);
/* order probes before migrations */
if (partial) {
pe__set_action_flags(migrate_from, pe_action_migrate_runnable);
migrate_from->needs = start->needs;
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0),
NULL, pe_order_optional, data_set);
} else {
pe__set_action_flags(migrate_from, pe_action_migrate_runnable);
pe__set_action_flags(migrate_to, pe_action_migrate_runnable);
migrate_to->needs = start->needs;
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0),
NULL, pe_order_optional, data_set);
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0),
NULL,
pe_order_optional|pe_order_implies_first_migratable,
data_set);
}
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL,
pe_order_optional|pe_order_implies_first_migratable,
data_set);
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL,
pe_order_optional|pe_order_implies_first_migratable|pe_order_pseudo_left,
data_set);
}
if (migrate_to) {
add_hash_param(migrate_to->meta, XML_LRM_ATTR_MIGRATE_SOURCE, current->details->uname);
add_hash_param(migrate_to->meta, XML_LRM_ATTR_MIGRATE_TARGET, chosen->details->uname);
/* Pacemaker Remote connections don't require pending to be recorded in
* the CIB. We can reduce CIB writes by not setting PENDING for them.
*/
if (rsc->is_remote_node == FALSE) {
/* migrate_to takes place on the source node, but can
* have an effect on the target node depending on how
* the agent is written. Because of this, we have to maintain
* a record that the migrate_to occurred, in case the source node
* loses membership while the migrate_to action is still in-flight.
*/
add_hash_param(migrate_to->meta, XML_OP_ATTR_PENDING, "true");
}
}
if (migrate_from) {
add_hash_param(migrate_from->meta, XML_LRM_ATTR_MIGRATE_SOURCE, current->details->uname);
add_hash_param(migrate_from->meta, XML_LRM_ATTR_MIGRATE_TARGET, chosen->details->uname);
}
}
/*!
* \internal
* \brief Schedule actions to bring resource down and back to current role
*
* \param[in] rsc Resource to restart
* \param[in] current Node that resource should be brought down on
* \param[in] chosen Node that resource should be brought up 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(pe_resource_t *rsc, pe_node_t *current,
pe_node_t *chosen, bool need_stop, bool need_promote)
{
enum rsc_role_e role = rsc->role;
enum rsc_role_e next_role;
pe__set_resource_flags(rsc, pe_rsc_restarting);
// Bring resource down to a stop on its current node
while (role != RSC_ROLE_STOPPED) {
next_role = rsc_state_matrix[role][RSC_ROLE_STOPPED];
pe_rsc_trace(rsc, "Creating %s action to take %s down from %s to %s",
(need_stop? "required" : "optional"), rsc->id,
role2text(role), role2text(next_role));
if (!rsc_action_matrix[role][next_role](rsc, current, !need_stop)) {
break;
}
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, pe_rsc_block)) {
bool required = need_stop;
next_role = rsc_state_matrix[role][rsc->role];
if ((next_role == RSC_ROLE_PROMOTED) && need_promote) {
required = true;
}
pe_rsc_trace(rsc, "Creating %s action to take %s up from %s to %s",
(required? "required" : "optional"), rsc->id,
role2text(role), role2text(next_role));
if (!rsc_action_matrix[role][next_role](rsc, chosen, !required)) {
break;
}
role = next_role;
}
pe__clear_resource_flags(rsc, pe_rsc_restarting);
}
void
native_create_actions(pe_resource_t *rsc)
{
pe_action_t *start = NULL;
pe_node_t *chosen = NULL;
pe_node_t *current = NULL;
gboolean need_stop = FALSE;
bool need_promote = FALSE;
gboolean is_moving = FALSE;
gboolean allow_migrate = FALSE;
GList *gIter = NULL;
unsigned int num_all_active = 0;
unsigned int num_clean_active = 0;
bool multiply_active = FALSE;
enum rsc_role_e role = RSC_ROLE_UNKNOWN;
enum rsc_role_e next_role = RSC_ROLE_UNKNOWN;
CRM_ASSERT(rsc != NULL);
allow_migrate = pcmk_is_set(rsc->flags, pe_rsc_allow_migrate)? TRUE : FALSE;
chosen = rsc->allocated_to;
next_role = rsc->next_role;
if (next_role == RSC_ROLE_UNKNOWN) {
pe__set_next_role(rsc,
(chosen == NULL)? RSC_ROLE_STOPPED : RSC_ROLE_STARTED,
"allocation");
}
pe_rsc_trace(rsc, "Creating all actions for %s transition from %s to %s (%s) on %s",
rsc->id, role2text(rsc->role), role2text(rsc->next_role),
((next_role == RSC_ROLE_UNKNOWN)? "implicit" : "explicit"),
pe__node_name(chosen));
current = pe__find_active_on(rsc, &num_all_active, &num_clean_active);
for (gIter = rsc->dangling_migrations; gIter != NULL; gIter = gIter->next) {
pe_node_t *dangling_source = (pe_node_t *) gIter->data;
pe_action_t *stop = NULL;
pe_rsc_trace(rsc, "Creating stop action %sfor %s on %s due to dangling migration",
pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)? "and cleanup " : "",
rsc->id, pe__node_name(dangling_source));
stop = stop_action(rsc, dangling_source, FALSE);
pe__set_action_flags(stop, pe_action_dangle);
if (pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)) {
DeleteRsc(rsc, dangling_source, FALSE, rsc->cluster);
}
}
if ((num_all_active == 2) && (num_clean_active == 2) && chosen
&& rsc->partial_migration_source && rsc->partial_migration_target
&& (current->details == rsc->partial_migration_source->details)
&& (chosen->details == rsc->partial_migration_target->details)) {
/* The chosen node is still the migration target from a partial
* migration. Attempt to continue the migration instead of recovering
* by stopping the resource everywhere and starting it on a single node.
*/
pe_rsc_trace(rsc, "Will attempt to continue with partial migration "
"to target %s from %s",
rsc->partial_migration_target->details->id,
rsc->partial_migration_source->details->id);
} else if (!pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) {
/* If a resource has "requires" set to nothing or quorum, don't consider
* it active on unclean nodes (similar to how all resources behave when
* 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);
} else {
multiply_active = (num_all_active > 1);
}
if (multiply_active) {
if (rsc->partial_migration_target && rsc->partial_migration_source) {
// Migration was in progress, but we've chosen a different target
crm_notice("Resource %s can no longer migrate from %s to %s "
"(will stop on both nodes)",
rsc->id, pe__node_name(rsc->partial_migration_source),
pe__node_name(rsc->partial_migration_target));
multiply_active = false;
} else {
const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
// Resource was (possibly) incorrectly multiply active
pe_proc_err("%s resource %s might be active on %u nodes (%s)",
pcmk__s(class, "Untyped"), rsc->id, num_all_active,
recovery2text(rsc->recovery_type));
crm_notice("See https://wiki.clusterlabs.org/wiki/FAQ#Resource_is_Too_Active for more information");
}
switch (rsc->recovery_type) {
case recovery_stop_start:
need_stop = TRUE;
break;
case recovery_stop_unexpected:
need_stop = TRUE; // StopRsc() will skip expected node
pe__set_resource_flags(rsc, pe_rsc_stop_unexpected);
break;
default:
break;
}
/* If by chance a partial migration is in process, but the migration
* target is not chosen still, clear all partial migration data.
*/
rsc->partial_migration_source = rsc->partial_migration_target = NULL;
allow_migrate = FALSE;
}
if (!multiply_active) {
pe__clear_resource_flags(rsc, pe_rsc_stop_unexpected);
}
if (pcmk_is_set(rsc->flags, pe_rsc_start_pending)) {
pe_rsc_trace(rsc, "Creating start action for %s to represent already pending start",
rsc->id);
start = start_action(rsc, chosen, TRUE);
pe__set_action_flags(start, pe_action_print_always);
}
if (current && chosen && current->details != chosen->details) {
pe_rsc_trace(rsc, "Moving %s from %s to %s",
rsc->id, pe__node_name(current), pe__node_name(chosen));
is_moving = TRUE;
need_stop = TRUE;
} else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
if (pcmk_is_set(rsc->flags, pe_rsc_stop)) {
need_stop = TRUE;
pe_rsc_trace(rsc, "Recovering %s", rsc->id);
} else {
pe_rsc_trace(rsc, "Recovering %s by demotion", rsc->id);
if (rsc->next_role == RSC_ROLE_PROMOTED) {
need_promote = TRUE;
}
}
} else if (pcmk_is_set(rsc->flags, pe_rsc_block)) {
pe_rsc_trace(rsc, "Blocking further actions on %s", rsc->id);
need_stop = TRUE;
} else if (rsc->role > RSC_ROLE_STARTED && current != NULL && chosen != NULL) {
pe_rsc_trace(rsc, "Creating start action for promoted resource %s",
rsc->id);
start = start_action(rsc, chosen, TRUE);
if (!pcmk_is_set(start->flags, pe_action_optional)) {
// Recovery of a promoted resource
pe_rsc_trace(rsc, "%s restart is required for recovery", rsc->id);
need_stop = TRUE;
}
}
/* Create any additional actions required when bringing resource down and
* back up to same level.
*/
schedule_restart_actions(rsc, current, chosen, need_stop, need_promote);
/* Required steps from this role to the next */
role = rsc->role;
while (role != rsc->next_role) {
next_role = rsc_state_matrix[role][rsc->next_role];
pe_rsc_trace(rsc, "Creating action to take %s from %s to %s (ending at %s)",
rsc->id, role2text(role), role2text(next_role),
role2text(rsc->next_role));
if (!rsc_action_matrix[role][next_role](rsc, chosen, false)) {
break;
}
role = next_role;
}
- if (pcmk_is_set(rsc->flags, pe_rsc_block)) {
- pe_rsc_trace(rsc, "Not creating recurring monitors for blocked resource %s",
- rsc->id);
-
- } else if ((rsc->next_role != RSC_ROLE_STOPPED)
- || !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
- pe_rsc_trace(rsc, "Creating recurring monitors for %s resource %s",
- ((rsc->next_role == RSC_ROLE_STOPPED)? "unmanaged" : "active"),
- rsc->id);
- start = start_action(rsc, chosen, TRUE);
- Recurring(rsc, start, chosen, rsc->cluster);
- Recurring_Stopped(rsc, start, chosen, rsc->cluster);
-
- } else {
- pe_rsc_trace(rsc, "Creating recurring monitors for inactive resource %s",
- rsc->id);
- Recurring_Stopped(rsc, NULL, NULL, rsc->cluster);
- }
+ pcmk__create_recurring_actions(rsc);
/* if we are stuck in a partial migration, where the target
* of the partial migration no longer matches the chosen target.
* A full stop/start is required */
if (rsc->partial_migration_target && (chosen == NULL || rsc->partial_migration_target->details != chosen->details)) {
pe_rsc_trace(rsc, "Not allowing partial migration of %s to continue",
rsc->id);
allow_migrate = FALSE;
} else if (!is_moving || !pcmk_is_set(rsc->flags, pe_rsc_managed)
|| pcmk_any_flags_set(rsc->flags,
pe_rsc_failed|pe_rsc_start_pending)
|| (current && current->details->unclean)
|| rsc->next_role < RSC_ROLE_STARTED) {
allow_migrate = FALSE;
}
if (allow_migrate) {
handle_migration_actions(rsc, current, chosen, rsc->cluster);
}
}
static void
rsc_avoids_remote_nodes(pe_resource_t *rsc)
{
GHashTableIter iter;
pe_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) {
node->weight = -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
* \param[in] data_set Cluster working set
*
* \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(pe_resource_t *rsc, pe_working_set_t *data_set)
{
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;
}
void
native_internal_constraints(pe_resource_t *rsc)
{
/* This function is on the critical path and worth optimizing as much as possible */
pe_resource_t *top = NULL;
GList *allowed_nodes = NULL;
bool check_unfencing = FALSE;
bool check_utilization = false;
if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) {
pe_rsc_trace(rsc,
"Skipping native constraints for unmanaged resource: %s",
rsc->id);
return;
}
top = uber_parent(rsc);
// Whether resource requires unfencing
check_unfencing = !pcmk_is_set(rsc->flags, pe_rsc_fence_device)
&& pcmk_is_set(rsc->cluster->flags, pe_flag_enable_unfencing)
&& pcmk_is_set(rsc->flags, pe_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,
"default", pcmk__str_casei);
// Order stops before starts (i.e. restart)
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL,
pe_order_optional|pe_order_implies_then|pe_order_restart,
rsc->cluster);
// Promotable ordering: demote before stop, start before promote
if (pcmk_is_set(top->flags, pe_rsc_promotable)
|| (rsc->role > RSC_ROLE_UNPROMOTED)) {
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_DEMOTE, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL,
pe_order_promoted_implies_first, rsc->cluster);
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL,
rsc, pcmk__op_key(rsc->id, RSC_PROMOTE, 0), NULL,
pe_order_runnable_left, rsc->cluster);
}
// Don't clear resource history if probing on same node
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, CRM_OP_LRM_DELETE, 0),
NULL, rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0),
NULL, pe_order_same_node|pe_order_then_cancels_first,
rsc->cluster);
// Certain checks need allowed nodes
if (check_unfencing || check_utilization || rsc->container) {
allowed_nodes = allowed_nodes_as_list(rsc, rsc->cluster);
}
if (check_unfencing) {
/* Check if the node needs to be unfenced first */
for (GList *item = allowed_nodes; item; item = item->next) {
pe_node_t *node = item->data;
pe_action_t *unfence = pe_fence_op(node, "on", TRUE, NULL, FALSE,
rsc->cluster);
crm_debug("Ordering any stops of %s before %s, and any starts after",
rsc->id, unfence->uuid);
/*
* It would be more efficient to order clone resources once,
* rather than order each instance, but ordering the instance
* allows us to avoid unnecessary dependencies that might conflict
* with user constraints.
*
* @TODO: This constraint can still produce a transition loop if the
* resource has a stop scheduled on the node being unfenced, and
* there is a user ordering constraint to start some other resource
* (which will be ordered after the unfence) before stopping this
* resource. An example is "start some slow-starting cloned service
* before stopping an associated virtual IP that may be moving to
* it":
* stop this -> unfencing -> start that -> stop this
*/
pcmk__new_ordering(rsc, stop_key(rsc), NULL,
NULL, strdup(unfence->uuid), unfence,
pe_order_optional|pe_order_same_node,
rsc->cluster);
pcmk__new_ordering(NULL, strdup(unfence->uuid), unfence,
rsc, start_key(rsc), NULL,
pe_order_implies_then_on_node|pe_order_same_node,
rsc->cluster);
}
}
if (check_utilization) {
pcmk__create_utilization_constraints(rsc, allowed_nodes);
}
if (rsc->container) {
pe_resource_t *remote_rsc = NULL;
if (rsc->is_remote_node) {
// rsc is the implicit remote connection for a guest or bundle node
/* Do not allow a guest resource to live on a Pacemaker Remote node,
* to avoid nesting remotes. However, allow bundles to run on remote
* nodes.
*/
if (!pcmk_is_set(rsc->flags, pe_rsc_allow_remote_remotes)) {
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, RSC_STATUS, rsc,
RSC_STOP, pe_order_optional);
/* 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) {
/* 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) {
pe_node_t *node = item->data;
if (node->details->remote_rsc != remote_rsc) {
node->weight = -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, RSC_START, 0),
NULL, rsc, pcmk__op_key(rsc->id, RSC_START, 0),
NULL,
pe_order_implies_then|pe_order_runnable_left,
rsc->cluster);
pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL,
rsc->container,
pcmk__op_key(rsc->container->id, RSC_STOP, 0),
NULL, pe_order_implies_first, rsc->cluster);
if (pcmk_is_set(rsc->flags, pe_rsc_allow_remote_remotes)) {
score = 10000; /* Highly preferred but not essential */
} else {
score = INFINITY; /* Force them to run on the same host */
}
pcmk__new_colocation("resource-with-container", NULL, score, rsc,
rsc->container, NULL, NULL, true, rsc->cluster);
}
}
if (rsc->is_remote_node || pcmk_is_set(rsc->flags, pe_rsc_fence_device)) {
/* don't allow remote nodes to run stonith devices
* or remote connection resources.*/
rsc_avoids_remote_nodes(rsc);
}
g_list_free(allowed_nodes);
}
/*!
* \internal
* \brief Apply a colocation's score to node weights or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node weights (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in] 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(pe_resource_t *dependent,
pe_resource_t *primary,
pcmk__colocation_t *colocation,
bool for_dependent)
{
enum pcmk__coloc_affects filter_results;
CRM_CHECK((colocation != NULL) && (dependent != NULL) && (primary != NULL),
return);
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);
pe_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_weights(dependent, primary, colocation);
break;
default: // pcmk__coloc_affects_nothing
return;
}
}
enum pe_action_flags
native_action_flags(pe_action_t * action, pe_node_t * node)
{
return action->flags;
}
void
native_rsc_location(pe_resource_t *rsc, pe__location_t *constraint)
{
pcmk__apply_location(constraint, rsc);
}
/*!
* \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 true if \p rsc is multiply active with multiple-active set to
* 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 pe_resource_t *rsc, const pe_node_t *node)
{
return pcmk_all_flags_set(rsc->flags,
pe_rsc_stop_unexpected|pe_rsc_restarting)
&& (rsc->next_role > RSC_ROLE_STOPPED)
&& (rsc->allocated_to != NULL) && (node != NULL)
&& (rsc->allocated_to->details == node->details);
}
static bool
StopRsc(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
GList *gIter = NULL;
CRM_ASSERT(rsc);
for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) {
pe_node_t *current = (pe_node_t *) gIter->data;
pe_action_t *stop;
if (is_expected_node(rsc, current)) {
/* We are scheduling restart actions for a multiply active resource
* with multiple-active=stop_unexpected, and this is where it should
* not be stopped.
*/
pe_rsc_trace(rsc,
"Skipping stop of multiply active resource %s "
"on expected node %s",
rsc->id, pe__node_name(current));
continue;
}
if (rsc->partial_migration_target) {
if (rsc->partial_migration_target->details == current->details
// Only if the allocated node still is the migration target.
&& rsc->allocated_to
&& rsc->allocated_to->details == rsc->partial_migration_target->details) {
pe_rsc_trace(rsc,
"Skipping stop of %s on %s "
"because migration to %s in progress",
rsc->id, pe__node_name(current),
pe__node_name(next));
continue;
} else {
pe_rsc_trace(rsc,
"Forcing stop of %s on %s "
"because migration target changed",
rsc->id, pe__node_name(current));
optional = false;
}
}
pe_rsc_trace(rsc, "Scheduling stop of %s on %s",
rsc->id, pe__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, pe_rsc_restarting
|pe_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, pe_rsc_managed)) {
pe__clear_action_flags(stop, pe_action_runnable);
}
if (pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)) {
DeleteRsc(rsc, current, optional, rsc->cluster);
}
if (pcmk_is_set(rsc->flags, pe_rsc_needs_unfencing)) {
pe_action_t *unfence = pe_fence_op(current, "on", TRUE, NULL, FALSE,
rsc->cluster);
order_actions(stop, unfence, pe_order_implies_first);
if (!pcmk__node_unfenced(current)) {
pe_proc_err("Stopping %s until %s can be unfenced",
rsc->id, pe__node_name(current));
}
}
}
return true;
}
static bool
StartRsc(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
pe_action_t *start = NULL;
CRM_ASSERT(rsc);
pe_rsc_trace(rsc, "Scheduling %s start of %s on %s (weight=%d)",
(optional? "optional" : "required"), rsc->id,
pe__node_name(next),
((next == NULL)? 0 : next->weight));
start = start_action(rsc, next, TRUE);
pcmk__order_vs_unfence(rsc, next, start, pe_order_implies_then);
if (pcmk_is_set(start->flags, pe_action_runnable) && !optional) {
pe__clear_action_flags(start, pe_action_optional);
}
if (is_expected_node(rsc, next)) {
/* This could be a problem if the start becomes necessary for other
* reasons later.
*/
pe_rsc_trace(rsc,
"Start of multiply active resouce %s "
"on expected node %s will be a pseudo-action",
rsc->id, pe__node_name(next));
pe__set_action_flags(start, pe_action_pseudo);
}
return true;
}
static bool
PromoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
GList *gIter = NULL;
gboolean runnable = TRUE;
GList *action_list = NULL;
CRM_ASSERT(rsc);
CRM_CHECK(next != NULL, return false);
pe_rsc_trace(rsc, "%s on %s", rsc->id, pe__node_name(next));
action_list = pe__resource_actions(rsc, next, RSC_START, TRUE);
for (gIter = action_list; gIter != NULL; gIter = gIter->next) {
pe_action_t *start = (pe_action_t *) gIter->data;
if (!pcmk_is_set(start->flags, pe_action_runnable)) {
runnable = FALSE;
}
}
g_list_free(action_list);
if (runnable) {
pe_action_t *promote = promote_action(rsc, next, optional);
if (is_expected_node(rsc, next)) {
/* This could be a problem if the promote becomes necessary for
* other reasons later.
*/
pe_rsc_trace(rsc,
"Promotion of multiply active resouce %s "
"on expected node %s will be a pseudo-action",
rsc->id, pe__node_name(next));
pe__set_action_flags(promote, pe_action_pseudo);
}
return true;
}
pe_rsc_debug(rsc, "%s\tPromote %s (canceled)",
pe__node_name(next), rsc->id);
action_list = pe__resource_actions(rsc, next, RSC_PROMOTE, TRUE);
for (gIter = action_list; gIter != NULL; gIter = gIter->next) {
pe_action_t *promote = (pe_action_t *) gIter->data;
pe__clear_action_flags(promote, pe_action_runnable);
}
g_list_free(action_list);
return true;
}
static bool
DemoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
GList *gIter = NULL;
CRM_ASSERT(rsc);
if (is_expected_node(rsc, next)) {
pe_rsc_trace(rsc,
"Skipping demote of multiply active resource %s "
"on expected node %s",
rsc->id, pe__node_name(next));
return true;
}
pe_rsc_trace(rsc, "%s", rsc->id);
/* CRM_CHECK(rsc->next_role == RSC_ROLE_UNPROMOTED, return FALSE); */
for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) {
pe_node_t *current = (pe_node_t *) gIter->data;
pe_rsc_trace(rsc, "%s on %s", rsc->id, pe__node_name(next));
demote_action(rsc, current, optional);
}
return true;
}
static bool
RoleError(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
CRM_ASSERT(rsc);
crm_err("%s on %s", rsc->id, pe__node_name(next));
CRM_CHECK(false, return false);
return false;
}
static bool
NullOp(pe_resource_t *rsc, pe_node_t *next, bool optional)
{
CRM_ASSERT(rsc);
pe_rsc_trace(rsc, "%s", rsc->id);
return FALSE;
}
gboolean
DeleteRsc(pe_resource_t * rsc, pe_node_t * node, gboolean optional, pe_working_set_t * data_set)
{
if (pcmk_is_set(rsc->flags, pe_rsc_failed)) {
pe_rsc_trace(rsc, "Resource %s not deleted from %s: failed",
rsc->id, pe__node_name(node));
return FALSE;
} else if (node == NULL) {
pe_rsc_trace(rsc, "Resource %s not deleted: NULL node", rsc->id);
return FALSE;
} else if (node->details->unclean || node->details->online == FALSE) {
pe_rsc_trace(rsc, "Resource %s not deleted from %s: unrunnable",
rsc->id, pe__node_name(node));
return FALSE;
}
crm_notice("Removing %s from %s", rsc->id, pe__node_name(node));
delete_action(rsc, node, optional);
pcmk__order_resource_actions(rsc, RSC_STOP, rsc, RSC_DELETE,
optional? pe_order_implies_then : pe_order_optional);
pcmk__order_resource_actions(rsc, RSC_DELETE, rsc, RSC_START,
optional? pe_order_implies_then : pe_order_optional);
return TRUE;
}
void
native_append_meta(pe_resource_t * rsc, xmlNode * xml)
{
char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION);
pe_resource_t *parent;
if (value) {
char *name = NULL;
name = crm_meta_name(XML_RSC_ATTR_INCARNATION);
crm_xml_add(xml, name, value);
free(name);
}
value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_REMOTE_NODE);
if (value) {
char *name = NULL;
name = crm_meta_name(XML_RSC_ATTR_REMOTE_NODE);
crm_xml_add(xml, name, value);
free(name);
}
for (parent = rsc; parent != NULL; parent = parent->parent) {
if (parent->container) {
crm_xml_add(xml, CRM_META"_"XML_RSC_ATTR_CONTAINER, parent->container->id);
}
}
}
// Primitive implementation of resource_alloc_functions_t:add_utilization()
void
pcmk__primitive_add_utilization(pe_resource_t *rsc, pe_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization)
{
if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) {
return;
}
pe_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] node Node to check
* \param[in] data_set Cluster working set
*
* \return Epoch time corresponding to shutdown attribute if set or now if not
*/
static time_t
shutdown_time(pe_node_t *node, pe_working_set_t *data_set)
{
const char *shutdown = pe_node_attribute_raw(node, XML_CIB_ATTR_SHUTDOWN);
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(data_set) : result;
}
// Primitive implementation of resource_alloc_functions_t:shutdown_lock()
void
pcmk__primitive_shutdown_lock(pe_resource_t *rsc)
{
const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
// Fence devices and remote connections can't be locked
if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_null_matches)
|| pe__resource_is_remote_conn(rsc, rsc->cluster)) {
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.
*/
pe_rsc_info(rsc,
"Cancelling shutdown lock because %s is already active",
rsc->id);
pe__clear_resource_history(rsc, rsc->lock_node, rsc->cluster);
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)) {
pe_node_t *node = rsc->running_on->data;
if (node->details->shutdown) {
if (node->details->unclean) {
pe_rsc_debug(rsc, "Not locking %s to unclean %s for shutdown",
rsc->id, pe__node_name(node));
} else {
rsc->lock_node = node;
rsc->lock_time = shutdown_time(node, rsc->cluster);
}
}
}
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;
pe_rsc_info(rsc, "Locking %s to %s due to shutdown (expires @%lld)",
rsc->id, pe__node_name(rsc->lock_node),
(long long) lock_expiration);
pe__update_recheck_time(++lock_expiration, rsc->cluster);
} else {
pe_rsc_info(rsc, "Locking %s to %s due to shutdown",
rsc->id, pe__node_name(rsc->lock_node));
}
// If resource is locked to one node, ban it from all other nodes
for (GList *item = rsc->cluster->nodes; item != NULL; item = item->next) {
pe_node_t *node = item->data;
if (strcmp(node->details->uname, rsc->lock_node->details->uname)) {
resource_location(rsc, node, -CRM_SCORE_INFINITY,
XML_CONFIG_ATTR_SHUTDOWN_LOCK, rsc->cluster);
}
}
}
diff --git a/lib/pacemaker/pcmk_sched_recurring.c b/lib/pacemaker/pcmk_sched_recurring.c
new file mode 100644
index 0000000000..0019350fd9
--- /dev/null
+++ b/lib/pacemaker/pcmk_sched_recurring.c
@@ -0,0 +1,714 @@
+/*
+ * Copyright 2004-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#include
+
+#include
+
+#include
+#include
+
+#include "libpacemaker_private.h"
+
+// Information parsed from an operation history entry in the CIB
+struct op_history {
+ // XML attributes
+ const char *id; // ID of history entry
+ const char *name; // Action name
+
+ // Parsed information
+ char *key; // Operation key for action
+ enum rsc_role_e role; // Action role (or RSC_ROLE_UNKNOWN for default)
+ guint interval_ms; // Action interval
+};
+
+/*!
+ * \internal
+ * \brief Parse an interval from XML
+ *
+ * \param[in] xml XML containing an interval attribute
+ *
+ * \return Interval parsed from XML (or 0 as default)
+ */
+static guint
+xe_interval(const xmlNode *xml)
+{
+ return crm_parse_interval_spec(crm_element_value(xml,
+ XML_LRM_ATTR_INTERVAL));
+}
+
+/*!
+ * \internal
+ * \brief Check whether an operation exists multiple times in resource history
+ *
+ * \param[in] rsc Resource with history to search
+ * \param[in] name Name of action to search for
+ * \param[in] interval_ms Interval (in milliseconds) of action to search for
+ *
+ * \return true if an operation with \p name and \p interval_ms exists more than
+ * once in the operation history of \p rsc, otherwise false
+ */
+static bool
+is_op_dup(const pe_resource_t *rsc, const char *name, guint interval_ms)
+{
+ const char *id = NULL;
+
+ for (xmlNode *op = first_named_child(rsc->ops_xml, "op");
+ op != NULL; op = crm_next_same_xml(op)) {
+
+ // Check whether action name and interval match
+ if (!pcmk__str_eq(crm_element_value(op, "name"),
+ name, pcmk__str_none)
+ || (xe_interval(op) != interval_ms)) {
+ continue;
+ }
+
+ if (ID(op) == NULL) {
+ continue; // Shouldn't be possible
+ }
+
+ if (id == NULL) {
+ id = ID(op); // First matching op
+ } else {
+ pcmk__config_err("Operation %s is duplicate of %s (do not use "
+ "same name and interval combination more "
+ "than once per resource)", ID(op), id);
+ return true;
+ }
+ }
+ return false;
+}
+
+/*!
+ * \internal
+ * \brief Check whether an action name is one that can be recurring
+ *
+ * \param[in] name Action name to check
+ *
+ * \return true if \p name is an action known to be unsuitable as a recurring
+ * operation, otherwise false
+ *
+ * \note Pacemaker's current philosophy is to allow users to configure recurring
+ * operations except for a short list of actions known not to be suitable
+ * for that (as opposed to allowing only actions known to be suitable,
+ * which includes only monitor). Among other things, this approach allows
+ * users to define their own custom operations and make them recurring,
+ * though that use case is not well tested.
+ */
+static bool
+op_cannot_recur(const char *name)
+{
+ return pcmk__str_any_of(name, RSC_STOP, RSC_START, RSC_DEMOTE, RSC_PROMOTE,
+ CRMD_ACTION_RELOAD_AGENT, CRMD_ACTION_MIGRATE,
+ CRMD_ACTION_MIGRATED, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Check whether a resource history entry is for a recurring action
+ *
+ * \param[in] rsc Resource that history entry is for
+ * \param[in] xml XML of resource history entry to check
+ * \param[out] op Where to store parsed info if recurring
+ *
+ * \return true if \p xml is for a recurring action, otherwise false
+ */
+static bool
+is_recurring_history(const pe_resource_t *rsc, const xmlNode *xml,
+ struct op_history *op)
+{
+ const char *role = NULL;
+
+ op->interval_ms = xe_interval(xml);
+ if (op->interval_ms == 0) {
+ return false; // Not recurring
+ }
+
+ op->id = ID(xml);
+ if (pcmk__str_empty(op->id)) {
+ pcmk__config_err("Ignoring resource history entry without ID");
+ return false; // Shouldn't be possible (unless CIB was manually edited)
+ }
+
+ op->name = crm_element_value(xml, "name");
+ if (op_cannot_recur(op->name)) {
+ pcmk__config_err("Ignoring %s because %s action cannot be recurring",
+ op->id, pcmk__s(op->name, "unnamed"));
+ return false;
+ }
+
+ // There should only be one recurring operation per action/interval
+ if (is_op_dup(rsc, op->name, op->interval_ms)) {
+ return false;
+ }
+
+ // Ensure role is valid if specified
+ role = crm_element_value(xml, "role");
+ if (role == NULL) {
+ op->role = RSC_ROLE_UNKNOWN;
+ } else {
+ op->role = text2role(role);
+ if (op->role == RSC_ROLE_UNKNOWN) {
+ pcmk__config_err("Ignoring %s because %s is not a valid role",
+ op->id, role);
+ }
+ }
+
+ // Disabled resources don't get monitored
+ op->key = pcmk__op_key(rsc->id, op->name, op->interval_ms);
+ if (find_rsc_op_entry(rsc, op->key) == NULL) {
+ crm_trace("Not creating recurring action %s for disabled resource %s",
+ op->id, rsc->id);
+ free(op->key);
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ * \internal
+ * \brief Check whether a recurring action for an active role should be optional
+ *
+ * \param[in] rsc Resource that recurring action is for
+ * \param[in] node Node that \p rsc will be active on (if any)
+ * \param[in] key Operation key for recurring action to check
+ * \param[in] start Start action for \p rsc
+ *
+ * \return true if recurring action should be optional, otherwise false
+ */
+static bool
+active_recurring_should_be_optional(const pe_resource_t *rsc,
+ const pe_node_t *node, const char *key,
+ pe_action_t *start)
+{
+ GList *possible_matches = NULL;
+
+ if (node == NULL) { // Should only be possible if unmanaged and stopped
+ pe_rsc_trace(rsc, "%s will be mandatory because resource is unmanaged",
+ key);
+ return false;
+ }
+
+ if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL),
+ pe_action_optional)) {
+ pe_rsc_trace(rsc, "%s will be mandatory because %s is",
+ key, start->uuid);
+ return false;
+ }
+
+ possible_matches = find_actions_exact(rsc->actions, key, node);
+ if (possible_matches == NULL) {
+ pe_rsc_trace(rsc, "%s will be mandatory because it is not active on %s",
+ key, pe__node_name(node));
+ return false;
+ }
+
+ for (GList *iter = possible_matches; iter != NULL; iter = iter->next) {
+ pe_action_t *op = (pe_action_t *) iter->data;
+
+ if (pcmk_is_set(op->flags, pe_action_reschedule)) {
+ pe_rsc_trace(rsc,
+ "%s will be mandatory because "
+ "it needs to be rescheduled", key);
+ g_list_free(possible_matches);
+ return false;
+ }
+ }
+
+ g_list_free(possible_matches);
+ return true;
+}
+
+/*!
+ * \internal
+ * \brief Create recurring action from resource history entry for an active role
+ *
+ * \param[in,out] rsc Resource that resource history is for
+ * \param[in] start Start action for \p rsc on \p node
+ * \param[in] node Node that resource will be active on (if any)
+ * \param[in] op Resource history entry
+ */
+static void
+recurring_op_for_active(pe_resource_t *rsc, pe_action_t *start,
+ const pe_node_t *node, const struct op_history *op)
+{
+ pe_action_t *mon = NULL;
+ bool is_optional = true;
+
+ // We're only interested in recurring actions for active roles
+ if (op->role == RSC_ROLE_STOPPED) {
+ return;
+ }
+
+ is_optional = active_recurring_should_be_optional(rsc, node, op->key,
+ start);
+
+ if (((op->role != RSC_ROLE_UNKNOWN) && (rsc->next_role != op->role))
+ || ((op->role == RSC_ROLE_UNKNOWN)
+ && (rsc->next_role == RSC_ROLE_PROMOTED))) {
+ // Configured monitor role doesn't match role resource will have
+
+ if (is_optional) { // It's running, so cancel it
+ char *after_key = NULL;
+ pe_action_t *cancel_op = pcmk__new_cancel_action(rsc, op->name,
+ op->interval_ms,
+ node);
+
+ switch (rsc->role) {
+ case RSC_ROLE_UNPROMOTED:
+ case RSC_ROLE_STARTED:
+ if (rsc->next_role == RSC_ROLE_PROMOTED) {
+ after_key = promote_key(rsc);
+
+ } else if (rsc->next_role == RSC_ROLE_STOPPED) {
+ after_key = stop_key(rsc);
+ }
+
+ break;
+ case RSC_ROLE_PROMOTED:
+ after_key = demote_key(rsc);
+ break;
+ default:
+ break;
+ }
+
+ if (after_key) {
+ pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL,
+ pe_order_runnable_left, rsc->cluster);
+ }
+ }
+
+ do_crm_log((is_optional? LOG_INFO : LOG_TRACE),
+ "%s recurring action %s because %s configured for %s role "
+ "(not %s)",
+ (is_optional? "Cancelling" : "Ignoring"), op->key, op->id,
+ role2text((op->role == RSC_ROLE_UNKNOWN)? RSC_ROLE_UNPROMOTED : op->role),
+ role2text(rsc->next_role));
+ return;
+ }
+
+ pe_rsc_trace(rsc,
+ "Creating %s recurring action %s for %s (%s %s on %s)",
+ (is_optional? "optional" : "mandatory"), op->key,
+ op->id, rsc->id, role2text(rsc->next_role),
+ pe__node_name(node));
+
+ mon = custom_action(rsc, strdup(op->key), op->name, node, is_optional, TRUE,
+ rsc->cluster);
+
+ if (!pcmk_is_set(start->flags, pe_action_runnable)) {
+ pe_rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid);
+ pe__clear_action_flags(mon, pe_action_runnable);
+
+ } else if ((node == NULL) || !node->details->online
+ || node->details->unclean) {
+ pe_rsc_trace(rsc, "%s is unrunnable because no node is available",
+ mon->uuid);
+ pe__clear_action_flags(mon, pe_action_runnable);
+
+ } else if (!pcmk_is_set(mon->flags, pe_action_optional)) {
+ pe_rsc_info(rsc, "Start %s-interval %s for %s on %s",
+ pcmk__readable_interval(op->interval_ms), mon->task,
+ rsc->id, pe__node_name(node));
+ }
+
+ if (rsc->next_role == RSC_ROLE_PROMOTED) {
+ pe__add_action_expected_result(mon, CRM_EX_PROMOTED);
+ }
+
+ // Order monitor relative to other actions
+ if ((node == NULL) || pcmk_is_set(rsc->flags, pe_rsc_managed)) {
+ pcmk__new_ordering(rsc, start_key(rsc), NULL,
+ NULL, strdup(mon->uuid), mon,
+ pe_order_implies_then|pe_order_runnable_left,
+ rsc->cluster);
+
+ pcmk__new_ordering(rsc, reload_key(rsc), NULL,
+ NULL, strdup(mon->uuid), mon,
+ pe_order_implies_then|pe_order_runnable_left,
+ rsc->cluster);
+
+ if (rsc->next_role == RSC_ROLE_PROMOTED) {
+ pcmk__new_ordering(rsc, promote_key(rsc), NULL,
+ rsc, NULL, mon,
+ pe_order_optional|pe_order_runnable_left,
+ rsc->cluster);
+
+ } else if (rsc->role == RSC_ROLE_PROMOTED) {
+ pcmk__new_ordering(rsc, demote_key(rsc), NULL,
+ rsc, NULL, mon,
+ pe_order_optional|pe_order_runnable_left,
+ rsc->cluster);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Cancel a recurring action if running on a node
+ *
+ * \param[in,out] rsc Resource that action is for
+ * \param[in] node Node to cancel action on
+ * \param[in] key Operation key for action
+ * \param[in] name Action name
+ * \param[in] interval_ms Action interval (in milliseconds)
+ */
+static void
+cancel_if_running(pe_resource_t *rsc, const pe_node_t *node, const char *key,
+ const char *name, guint interval_ms)
+{
+ GList *possible_matches = find_actions_exact(rsc->actions, key, node);
+ pe_action_t *cancel_op = NULL;
+
+ if (possible_matches == NULL) {
+ return; // Recurring action isn't running on this node
+ }
+ g_list_free(possible_matches);
+
+ cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node);
+
+ switch (rsc->next_role) {
+ case RSC_ROLE_STARTED:
+ case RSC_ROLE_UNPROMOTED:
+ /* Order starts after cancel. If the current role is
+ * stopped, this cancels the monitor before the resource
+ * starts; if the current role is started, then this cancels
+ * the monitor on a migration target before starting there.
+ */
+ pcmk__new_ordering(rsc, NULL, cancel_op,
+ rsc, start_key(rsc), NULL,
+ pe_order_runnable_left, rsc->cluster);
+ break;
+ default:
+ break;
+ }
+ pe_rsc_info(rsc,
+ "Cancelling %s-interval %s action for %s on %s because "
+ "configured for " RSC_ROLE_STOPPED_S " role (not %s)",
+ pcmk__readable_interval(interval_ms), name, rsc->id,
+ pe__node_name(node), role2text(rsc->next_role));
+}
+
+/*!
+ * \internal
+ * \brief Order an action after all probes of a resource on a node
+ *
+ * \param[in,out] rsc Resource to check for probes
+ * \param[in] node Node to check for probes of \p rsc
+ * \param[in,out] action Action to order after probes of \p rsc on \p node
+ */
+static void
+order_after_probes(pe_resource_t *rsc, const pe_node_t *node,
+ pe_action_t *action)
+{
+ GList *probes = pe__resource_actions(rsc, node, RSC_STATUS, FALSE);
+
+ for (GList *iter = probes; iter != NULL; iter = iter->next) {
+ order_actions((pe_action_t *) iter->data, action,
+ pe_order_runnable_left);
+ }
+ g_list_free(probes);
+}
+
+/*!
+ * \internal
+ * \brief Order an action after all stops of a resource on a node
+ *
+ * \param[in,out] rsc Resource to check for stops
+ * \param[in] node Node to check for stops of \p rsc
+ * \param[in,out] action Action to order after stops of \p rsc on \p node
+ */
+static void
+order_after_stops(pe_resource_t *rsc, const pe_node_t *node,
+ pe_action_t *action)
+{
+ GList *stop_ops = pe__resource_actions(rsc, node, RSC_STOP, TRUE);
+
+ for (GList *iter = stop_ops; iter != NULL; iter = iter->next) {
+ pe_action_t *stop = (pe_action_t *) iter->data;
+
+ if (!pcmk_is_set(stop->flags, pe_action_optional)
+ && !pcmk_is_set(action->flags, pe_action_optional)
+ && !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
+ pe_rsc_trace(rsc, "%s optional on %s: unmanaged",
+ action->uuid, pe__node_name(node));
+ pe__set_action_flags(action, pe_action_optional);
+ }
+
+ if (!pcmk_is_set(stop->flags, pe_action_runnable)) {
+ crm_debug("%s unrunnable on %s: stop is unrunnable",
+ action->uuid, pe__node_name(node));
+ pe__clear_action_flags(action, pe_action_runnable);
+ }
+
+ if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
+ pcmk__new_ordering(rsc, stop_key(rsc), stop,
+ NULL, NULL, action,
+ pe_order_implies_then|pe_order_runnable_left,
+ rsc->cluster);
+ }
+ }
+ g_list_free(stop_ops);
+}
+
+/*!
+ * \internal
+ * \brief Create recurring action from resource history entry for inactive role
+ *
+ * \param[in,out] rsc Resource that resource history is for
+ * \param[in] node Node that resource will be active on (if any)
+ * \param[in] op Resource history entry
+ */
+static void
+recurring_op_for_inactive(pe_resource_t *rsc, const pe_node_t *node,
+ const struct op_history *op)
+{
+ GList *possible_matches = NULL;
+
+ // We're only interested in recurring actions for the inactive role
+ if (op->role != RSC_ROLE_STOPPED) {
+ return;
+ }
+
+ if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) {
+ crm_notice("Ignoring %s (recurring monitors for " RSC_ROLE_STOPPED_S
+ " role are not supported for anonymous clones)", op->id);
+ return; // @TODO add support
+ }
+
+ pe_rsc_trace(rsc, "Creating recurring action %s for %s on nodes "
+ "where it should not be running", op->id, rsc->id);
+
+ for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) {
+ pe_node_t *stop_node = (pe_node_t *) iter->data;
+
+ bool is_optional = true;
+ pe_action_t *stopped_mon = NULL;
+
+ // Cancel action on node where resource will be active
+ if ((node != NULL)
+ && pcmk__str_eq(stop_node->details->uname, node->details->uname,
+ pcmk__str_casei)) {
+ cancel_if_running(rsc, node, op->key, op->name, op->interval_ms);
+ continue;
+ }
+
+ // Recurring action on this node is optional if it's already active here
+ possible_matches = find_actions_exact(rsc->actions, op->key, stop_node);
+ is_optional = (possible_matches != NULL);
+ g_list_free(possible_matches);
+
+ pe_rsc_trace(rsc,
+ "Creating %s recurring action %s for %s (%s "
+ RSC_ROLE_STOPPED_S " on %s)",
+ (is_optional? "optional" : "mandatory"),
+ op->key, op->id, rsc->id, pe__node_name(stop_node));
+
+ stopped_mon = custom_action(rsc, strdup(op->key), op->name, stop_node,
+ is_optional, TRUE, rsc->cluster);
+
+ pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING);
+
+ if (pcmk_is_set(rsc->flags, pe_rsc_managed)) {
+ order_after_probes(rsc, stop_node, stopped_mon);
+ }
+
+ /* The recurring action is for the inactive role, so it shouldn't be
+ * performed until the resource is inactive.
+ */
+ order_after_stops(rsc, stop_node, stopped_mon);
+
+ if (!stop_node->details->online || stop_node->details->unclean) {
+ pe_rsc_debug(rsc, "%s unrunnable on %s: node unavailable)",
+ stopped_mon->uuid, pe__node_name(stop_node));
+ pe__clear_action_flags(stopped_mon, pe_action_runnable);
+ }
+
+ if (pcmk_is_set(stopped_mon->flags, pe_action_runnable)
+ && !pcmk_is_set(stopped_mon->flags, pe_action_optional)) {
+ crm_notice("Start recurring %s-interval %s for "
+ RSC_ROLE_STOPPED_S " %s on %s",
+ pcmk__readable_interval(op->interval_ms),
+ stopped_mon->task, rsc->id, pe__node_name(stop_node));
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Create recurring actions for a resource
+ *
+ * \param[in,out] rsc Resource to create recurring actions for
+ */
+void
+pcmk__create_recurring_actions(pe_resource_t *rsc)
+{
+ pe_action_t *start = NULL;
+
+ if (pcmk_is_set(rsc->flags, pe_rsc_block)) {
+ pe_rsc_trace(rsc, "Skipping recurring actions for blocked resource %s",
+ rsc->id);
+ return;
+ }
+
+ if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) {
+ pe_rsc_trace(rsc, "Skipping recurring actions for %s "
+ "in maintenance mode", rsc->id);
+ return;
+ }
+
+ if (rsc->allocated_to == NULL) {
+ // Recurring actions for active roles not needed
+
+ } else if (rsc->allocated_to->details->maintenance) {
+ pe_rsc_trace(rsc,
+ "Skipping recurring actions for %s on %s "
+ "in maintenance mode",
+ rsc->id, pe__node_name(rsc->allocated_to));
+
+ } else if ((rsc->next_role != RSC_ROLE_STOPPED)
+ || !pcmk_is_set(rsc->flags, pe_rsc_managed)) {
+ // Recurring actions for active roles needed
+ start = start_action(rsc, rsc->allocated_to, TRUE);
+ }
+
+ pe_rsc_trace(rsc, "Creating any recurring actions needed for %s", rsc->id);
+
+ for (xmlNode *op = first_named_child(rsc->ops_xml, "op");
+ op != NULL; op = crm_next_same_xml(op)) {
+
+ struct op_history op_history = { NULL, };
+
+ if (!is_recurring_history(rsc, op, &op_history)) {
+ continue;
+ }
+
+ if (start != NULL) {
+ recurring_op_for_active(rsc, start, rsc->allocated_to, &op_history);
+ }
+ recurring_op_for_inactive(rsc, rsc->allocated_to, &op_history);
+
+ free(op_history.key);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Create an executor cancel action
+ *
+ * \param[in,out] rsc Resource of action to cancel
+ * \param[in] task Name of action to cancel
+ * \param[in] interval_ms Interval of action to cancel
+ * \param[in] node Node of action to cancel
+ *
+ * \return Created op
+ */
+pe_action_t *
+pcmk__new_cancel_action(pe_resource_t *rsc, const char *task, guint interval_ms,
+ const pe_node_t *node)
+{
+ pe_action_t *cancel_op = NULL;
+ char *key = NULL;
+ char *interval_ms_s = NULL;
+
+ CRM_ASSERT((rsc != NULL) && (task != NULL) && (node != NULL));
+
+ // @TODO dangerous if possible to schedule another action with this key
+ key = pcmk__op_key(rsc->id, task, interval_ms);
+
+ cancel_op = custom_action(rsc, key, RSC_CANCEL, node, FALSE, TRUE,
+ rsc->cluster);
+
+ pcmk__str_update(&cancel_op->task, RSC_CANCEL);
+ pcmk__str_update(&cancel_op->cancel_task, task);
+
+ interval_ms_s = crm_strdup_printf("%u", interval_ms);
+ add_hash_param(cancel_op->meta, XML_LRM_ATTR_TASK, task);
+ add_hash_param(cancel_op->meta, XML_LRM_ATTR_INTERVAL_MS, interval_ms_s);
+ free(interval_ms_s);
+
+ return cancel_op;
+}
+
+/*!
+ * \internal
+ * \brief Schedule cancellation of a recurring action
+ *
+ * \param[in,out] rsc Resource that action is for
+ * \param[in] call_id Action's call ID from history
+ * \param[in] task Action name
+ * \param[in] interval_ms Action interval
+ * \param[in] node Node that history entry is for
+ * \param[in] reason Short description of why action is being cancelled
+ */
+void
+pcmk__schedule_cancel(pe_resource_t *rsc, const char *call_id, const char *task,
+ guint interval_ms, const pe_node_t *node,
+ const char *reason)
+{
+ pe_action_t *cancel = NULL;
+
+ CRM_CHECK((rsc != NULL) && (task != NULL)
+ && (node != NULL) && (reason != NULL),
+ return);
+
+ crm_info("Recurring %s-interval %s for %s will be stopped on %s: %s",
+ pcmk__readable_interval(interval_ms), task, rsc->id,
+ pe__node_name(node), reason);
+ cancel = pcmk__new_cancel_action(rsc, task, interval_ms, node);
+ add_hash_param(cancel->meta, XML_LRM_ATTR_CALLID, call_id);
+
+ // Cancellations happen after stops
+ pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel,
+ pe_order_optional, rsc->cluster);
+}
+
+/*!
+ * \internal
+ * \brief Reschedule a recurring action
+ *
+ * \param[in,out] rsc Resource that action is for
+ * \param[in] task Name of action being rescheduled
+ * \param[in] interval_ms Action interval (in milliseconds)
+ * \param[in] node Node where action should be rescheduled
+ */
+void
+pcmk__reschedule_recurring(pe_resource_t *rsc, const char *task,
+ guint interval_ms, pe_node_t *node)
+{
+ pe_action_t *op = NULL;
+
+ trigger_unfencing(rsc, node, "Device parameters changed (reschedule)",
+ NULL, rsc->cluster);
+ op = custom_action(rsc, pcmk__op_key(rsc->id, task, interval_ms),
+ task, node, TRUE, TRUE, rsc->cluster);
+ pe__set_action_flags(op, pe_action_reschedule);
+}
+
+/*!
+ * \internal
+ * \brief Check whether an action is recurring
+ *
+ * \param[in] action Action to check
+ *
+ * \return true if \p action has a nonzero interval, otherwise false
+ */
+bool
+pcmk__action_is_recurring(const pe_action_t *action)
+{
+ guint interval_ms = 0;
+
+ if (pcmk__guint_from_hash(action->meta,
+ XML_LRM_ATTR_INTERVAL_MS, 0,
+ &interval_ms) != pcmk_rc_ok) {
+ return false;
+ }
+ return (interval_ms > 0);
+}
diff --git a/lib/pacemaker/pcmk_sched_remote.c b/lib/pacemaker/pcmk_sched_remote.c
index 97c5ea4854..b7f7993a0b 100644
--- a/lib/pacemaker/pcmk_sched_remote.c
+++ b/lib/pacemaker/pcmk_sched_remote.c
@@ -1,735 +1,722 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libpacemaker_private.h"
enum remote_connection_state {
remote_state_unknown = 0,
remote_state_alive = 1,
remote_state_resting = 2,
remote_state_failed = 3,
remote_state_stopped = 4
};
static const char *
state2text(enum remote_connection_state state)
{
switch (state) {
case remote_state_unknown:
return "unknown";
case remote_state_alive:
return "alive";
case remote_state_resting:
return "resting";
case remote_state_failed:
return "failed";
case remote_state_stopped:
return "stopped";
}
return "impossible";
}
/* We always use pe_order_preserve with these convenience functions to exempt
* internally generated constraints from the prohibition of user constraints
* involving remote connection resources.
*
* The start ordering additionally uses pe_order_runnable_left so that the
* specified action is not runnable if the start is not runnable.
*/
static inline void
order_start_then_action(pe_resource_t *first_rsc, pe_action_t *then_action,
enum pe_ordering extra, pe_working_set_t *data_set)
{
if ((first_rsc != NULL) && (then_action != NULL) && (data_set != NULL)) {
pcmk__new_ordering(first_rsc, start_key(first_rsc), NULL,
then_action->rsc, NULL, then_action,
pe_order_preserve|pe_order_runnable_left|extra,
data_set);
}
}
static inline void
order_action_then_stop(pe_action_t *first_action, pe_resource_t *then_rsc,
enum pe_ordering extra, pe_working_set_t *data_set)
{
if ((first_action != NULL) && (then_rsc != NULL) && (data_set != NULL)) {
pcmk__new_ordering(first_action->rsc, NULL, first_action,
then_rsc, stop_key(then_rsc), NULL,
pe_order_preserve|extra, data_set);
}
}
static enum remote_connection_state
get_remote_node_state(pe_node_t *node)
{
pe_resource_t *remote_rsc = NULL;
pe_node_t *cluster_node = NULL;
CRM_ASSERT(node != NULL);
remote_rsc = node->details->remote_rsc;
CRM_ASSERT(remote_rsc != NULL);
cluster_node = pe__current_node(remote_rsc);
/* If the cluster node the remote connection resource resides on
* is unclean or went offline, we can't process any operations
* on that remote node until after it starts elsewhere.
*/
if ((remote_rsc->next_role == RSC_ROLE_STOPPED)
|| (remote_rsc->allocated_to == NULL)) {
// The connection resource is not going to run anywhere
if ((cluster_node != NULL) && cluster_node->details->unclean) {
/* The remote connection is failed because its resource is on a
* failed node and can't be recovered elsewhere, so we must fence.
*/
return remote_state_failed;
}
if (!pcmk_is_set(remote_rsc->flags, pe_rsc_failed)) {
/* Connection resource is cleanly stopped */
return remote_state_stopped;
}
/* Connection resource is failed */
if ((remote_rsc->next_role == RSC_ROLE_STOPPED)
&& remote_rsc->remote_reconnect_ms
&& node->details->remote_was_fenced
&& !pe__shutdown_requested(node)) {
/* We won't know whether the connection is recoverable until the
* reconnect interval expires and we reattempt connection.
*/
return remote_state_unknown;
}
/* The remote connection is in a failed state. If there are any
* resources known to be active on it (stop) or in an unknown state
* (probe), we must assume the worst and fence it.
*/
return remote_state_failed;
} else if (cluster_node == NULL) {
/* Connection is recoverable but not currently running anywhere, so see
* if we can recover it first
*/
return remote_state_unknown;
} else if (cluster_node->details->unclean
|| !(cluster_node->details->online)) {
// Connection is running on a dead node, see if we can recover it first
return remote_state_resting;
} else if (pcmk__list_of_multiple(remote_rsc->running_on)
&& (remote_rsc->partial_migration_source != NULL)
&& (remote_rsc->partial_migration_target != NULL)) {
/* We're in the middle of migrating a connection resource, so wait until
* after the migration completes before performing any actions.
*/
return remote_state_resting;
}
return remote_state_alive;
}
-static int
-is_recurring_action(pe_action_t *action)
-{
- guint interval_ms;
-
- if (pcmk__guint_from_hash(action->meta,
- XML_LRM_ATTR_INTERVAL_MS, 0,
- &interval_ms) != pcmk_rc_ok) {
- return 0;
- }
- return (interval_ms > 0);
-}
-
/*!
* \internal
* \brief Order actions on remote node relative to actions for the connection
*/
static void
apply_remote_ordering(pe_action_t *action, pe_working_set_t *data_set)
{
pe_resource_t *remote_rsc = NULL;
enum action_tasks task = text2task(action->task);
enum remote_connection_state state = get_remote_node_state(action->node);
enum pe_ordering order_opts = pe_order_none;
if (action->rsc == NULL) {
return;
}
CRM_ASSERT(pe__is_guest_or_remote_node(action->node));
remote_rsc = action->node->details->remote_rsc;
CRM_ASSERT(remote_rsc != NULL);
crm_trace("Order %s action %s relative to %s%s (state: %s)",
action->task, action->uuid,
pcmk_is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
remote_rsc->id, state2text(state));
if (pcmk__strcase_any_of(action->task, CRMD_ACTION_MIGRATE,
CRMD_ACTION_MIGRATED, NULL)) {
/* Migration ops map to "no_action", but we need to apply the same
* ordering as for stop or demote (see get_router_node()).
*/
task = stop_rsc;
}
switch (task) {
case start_rsc:
case action_promote:
order_opts = pe_order_none;
if (state == remote_state_failed) {
/* Force recovery, by making this action required */
pe__set_order_flags(order_opts, pe_order_implies_then);
}
/* Ensure connection is up before running this action */
order_start_then_action(remote_rsc, action, order_opts, data_set);
break;
case stop_rsc:
if (state == remote_state_alive) {
order_action_then_stop(action, remote_rsc,
pe_order_implies_first, data_set);
} else if (state == remote_state_failed) {
/* The resource is active on the node, but since we don't have a
* valid connection, the only way to stop the resource is by
* fencing the node. There is no need to order the stop relative
* to the remote connection, since the stop will become implied
* by the fencing.
*/
pe_fence_node(data_set, action->node,
"resources are active but connection is unrecoverable",
FALSE);
} else if (remote_rsc->next_role == RSC_ROLE_STOPPED) {
/* State must be remote_state_unknown or remote_state_stopped.
* Since the connection is not coming back up in this
* transition, stop this resource first.
*/
order_action_then_stop(action, remote_rsc,
pe_order_implies_first, data_set);
} else {
/* The connection is going to be started somewhere else, so
* stop this resource after that completes.
*/
order_start_then_action(remote_rsc, action, pe_order_none,
data_set);
}
break;
case action_demote:
/* Only order this demote relative to the connection start if the
* connection isn't being torn down. Otherwise, the demote would be
* blocked because the connection start would not be allowed.
*/
if ((state == remote_state_resting)
|| (state == remote_state_unknown)) {
order_start_then_action(remote_rsc, action, pe_order_none,
data_set);
} /* Otherwise we can rely on the stop ordering */
break;
default:
/* Wait for the connection resource to be up */
- if (is_recurring_action(action)) {
+ if (pcmk__action_is_recurring(action)) {
/* In case we ever get the recovery logic wrong, force
* recurring monitors to be restarted, even if just
* the connection was re-established
*/
order_start_then_action(remote_rsc, action,
pe_order_implies_then, data_set);
} else {
pe_node_t *cluster_node = pe__current_node(remote_rsc);
if ((task == monitor_rsc) && (state == remote_state_failed)) {
/* We would only be here if we do not know the state of the
* resource on the remote node. Since we have no way to find
* out, it is necessary to fence the node.
*/
pe_fence_node(data_set, action->node,
"resources are in unknown state "
"and connection is unrecoverable", FALSE);
}
if ((cluster_node != NULL) && (state == remote_state_stopped)) {
/* The connection is currently up, but is going down
* permanently. Make sure we check services are actually
* stopped _before_ we let the connection get closed.
*/
order_action_then_stop(action, remote_rsc,
pe_order_runnable_left, data_set);
} else {
order_start_then_action(remote_rsc, action, pe_order_none,
data_set);
}
}
break;
}
}
static void
apply_container_ordering(pe_action_t *action, pe_working_set_t *data_set)
{
/* VMs are also classified as containers for these purposes... in
* that they both involve a 'thing' running on a real or remote
* cluster node.
*
* This allows us to be smarter about the type and extent of
* recovery actions required in various scenarios
*/
pe_resource_t *remote_rsc = NULL;
pe_resource_t *container = NULL;
enum action_tasks task = text2task(action->task);
CRM_ASSERT(action->rsc != NULL);
CRM_ASSERT(action->node != NULL);
CRM_ASSERT(pe__is_guest_or_remote_node(action->node));
remote_rsc = action->node->details->remote_rsc;
CRM_ASSERT(remote_rsc != NULL);
container = remote_rsc->container;
CRM_ASSERT(container != NULL);
if (pcmk_is_set(container->flags, pe_rsc_failed)) {
pe_fence_node(data_set, action->node, "container failed", FALSE);
}
crm_trace("Order %s action %s relative to %s%s for %s%s",
action->task, action->uuid,
pcmk_is_set(remote_rsc->flags, pe_rsc_failed)? "failed " : "",
remote_rsc->id,
pcmk_is_set(container->flags, pe_rsc_failed)? "failed " : "",
container->id);
if (pcmk__strcase_any_of(action->task, CRMD_ACTION_MIGRATE,
CRMD_ACTION_MIGRATED, NULL)) {
/* Migration ops map to "no_action", but we need to apply the same
* ordering as for stop or demote (see get_router_node()).
*/
task = stop_rsc;
}
switch (task) {
case start_rsc:
case action_promote:
// Force resource recovery if the container is recovered
order_start_then_action(container, action, pe_order_implies_then,
data_set);
// Wait for the connection resource to be up, too
order_start_then_action(remote_rsc, action, pe_order_none,
data_set);
break;
case stop_rsc:
case action_demote:
if (pcmk_is_set(container->flags, pe_rsc_failed)) {
/* When the container representing a guest node fails, any stop
* or demote actions for resources running on the guest node
* are implied by the container stopping. This is similar to
* how fencing operations work for cluster nodes and remote
* nodes.
*/
} else {
/* Ensure the operation happens before the connection is brought
* down.
*
* If we really wanted to, we could order these after the
* connection start, IFF the container's current role was
* stopped (otherwise we re-introduce an ordering loop when the
* connection is restarting).
*/
order_action_then_stop(action, remote_rsc, pe_order_none,
data_set);
}
break;
default:
/* Wait for the connection resource to be up */
- if (is_recurring_action(action)) {
+ if (pcmk__action_is_recurring(action)) {
/* In case we ever get the recovery logic wrong, force
* recurring monitors to be restarted, even if just
* the connection was re-established
*/
if(task != no_action) {
order_start_then_action(remote_rsc, action,
pe_order_implies_then, data_set);
}
} else {
order_start_then_action(remote_rsc, action, pe_order_none,
data_set);
}
break;
}
}
/*!
* \internal
* \brief Order all relevant actions relative to remote connection actions
*
* \param[in] data_set Cluster working set
*/
void
pcmk__order_remote_connection_actions(pe_working_set_t *data_set)
{
if (!pcmk_is_set(data_set->flags, pe_flag_have_remote_nodes)) {
return;
}
crm_trace("Creating remote connection orderings");
for (GList *gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
pe_action_t *action = (pe_action_t *) gIter->data;
pe_resource_t *remote = NULL;
// We are only interested in resource actions
if (action->rsc == NULL) {
continue;
}
/* Special case: If we are clearing the failcount of an actual
* remote connection resource, then make sure this happens before
* any start of the resource in this transition.
*/
if (action->rsc->is_remote_node &&
pcmk__str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT, pcmk__str_casei)) {
pcmk__new_ordering(action->rsc, NULL, action, action->rsc,
pcmk__op_key(action->rsc->id, RSC_START, 0),
NULL, pe_order_optional, data_set);
continue;
}
// We are only interested in actions allocated to a node
if (action->node == NULL) {
continue;
}
if (!pe__is_guest_or_remote_node(action->node)) {
continue;
}
/* We are only interested in real actions.
*
* @TODO This is probably wrong; pseudo-actions might be converted to
* real actions and vice versa later in update_actions() at the end of
* pcmk__apply_orderings().
*/
if (pcmk_is_set(action->flags, pe_action_pseudo)) {
continue;
}
remote = action->node->details->remote_rsc;
if (remote == NULL) {
// Orphaned
continue;
}
/* Another special case: if a resource is moving to a Pacemaker Remote
* node, order the stop on the original node after any start of the
* remote connection. This ensures that if the connection fails to
* start, we leave the resource running on the original node.
*/
if (pcmk__str_eq(action->task, RSC_START, pcmk__str_casei)) {
for (GList *item = action->rsc->actions; item != NULL;
item = item->next) {
pe_action_t *rsc_action = item->data;
if ((rsc_action->node->details != action->node->details)
&& pcmk__str_eq(rsc_action->task, RSC_STOP, pcmk__str_casei)) {
pcmk__new_ordering(remote, start_key(remote), NULL,
action->rsc, NULL, rsc_action,
pe_order_optional, data_set);
}
}
}
/* The action occurs across a remote connection, so create
* ordering constraints that guarantee the action occurs while the node
* is active (after start, before stop ... things like that).
*
* This is somewhat brittle in that we need to make sure the results of
* this ordering are compatible with the result of get_router_node().
* It would probably be better to add XML_LRM_ATTR_ROUTER_NODE as part
* of this logic rather than create_graph_action().
*/
if (remote->container) {
crm_trace("Container ordering for %s", action->uuid);
apply_container_ordering(action, data_set);
} else {
crm_trace("Remote ordering for %s", action->uuid);
apply_remote_ordering(action, data_set);
}
}
}
/*!
* \internal
* \brief Check whether a node is a failed remote node
*
* \param[in] node Node to check
*
* \return true if \p node is a failed remote node, false otherwise
*/
bool
pcmk__is_failed_remote_node(pe_node_t *node)
{
return pe__is_remote_node(node) && (node->details->remote_rsc != NULL)
&& (get_remote_node_state(node) == remote_state_failed);
}
/*!
* \internal
* \brief Check whether a given resource corresponds to a given node as guest
*
* \param[in] rsc Resource to check
* \param[in] node Node to check
*
* \return true if \p node is a guest node and \p rsc is its containing
* resource, otherwise false
*/
bool
pcmk__rsc_corresponds_to_guest(pe_resource_t *rsc, pe_node_t *node)
{
return (rsc != NULL) && (rsc->fillers != NULL) && (node != NULL)
&& (node->details->remote_rsc != NULL)
&& (node->details->remote_rsc->container == rsc);
}
/*!
* \internal
* \brief Get proper connection host that a remote action must be routed through
*
* A remote connection resource might be starting, stopping, or migrating in the
* same transition that an action needs to be executed on its Pacemaker Remote
* node. Determine the proper node that the remote action should be routed
* through.
*
* \param[in] action (Potentially remote) action to route
*
* \return Connection host that action should be routed through if remote,
* otherwise NULL
*/
pe_node_t *
pcmk__connection_host_for_action(pe_action_t *action)
{
pe_node_t *began_on = NULL;
pe_node_t *ended_on = NULL;
bool partial_migration = false;
const char *task = action->task;
if (pcmk__str_eq(task, CRM_OP_FENCE, pcmk__str_casei)
|| !pe__is_guest_or_remote_node(action->node)) {
return NULL;
}
CRM_ASSERT(action->node->details->remote_rsc != NULL);
began_on = pe__current_node(action->node->details->remote_rsc);
ended_on = action->node->details->remote_rsc->allocated_to;
if (action->node->details->remote_rsc
&& (action->node->details->remote_rsc->container == NULL)
&& action->node->details->remote_rsc->partial_migration_target) {
partial_migration = true;
}
if (began_on == NULL) {
crm_trace("Routing %s for %s through remote connection's "
"next node %s (starting)%s",
action->task, (action->rsc? action->rsc->id : "no resource"),
(ended_on? ended_on->details->uname : "none"),
partial_migration? " (partial migration)" : "");
return ended_on;
}
if (ended_on == NULL) {
crm_trace("Routing %s for %s through remote connection's "
"current node %s (stopping)%s",
action->task, (action->rsc? action->rsc->id : "no resource"),
(began_on? began_on->details->uname : "none"),
partial_migration? " (partial migration)" : "");
return began_on;
}
if (began_on->details == ended_on->details) {
crm_trace("Routing %s for %s through remote connection's "
"current node %s (not moving)%s",
action->task, (action->rsc? action->rsc->id : "no resource"),
(began_on? began_on->details->uname : "none"),
partial_migration? " (partial migration)" : "");
return began_on;
}
/* If we get here, the remote connection is moving during this transition.
* This means some actions for resources behind the connection will get
* routed through the cluster node the connection resource is currently on,
* and others are routed through the cluster node the connection will end up
* on.
*/
if (pcmk__str_eq(task, "notify", pcmk__str_casei)) {
task = g_hash_table_lookup(action->meta, "notify_operation");
}
/*
* Stop, demote, and migration actions must occur before the connection can
* move (these actions are required before the remote resource can stop). In
* this case, we know these actions have to be routed through the initial
* cluster node the connection resource lived on before the move takes
* place.
*
* The exception is a partial migration of a (non-guest) remote connection
* resource; in that case, all actions (even these) will be ordered after
* the connection's pseudo-start on the migration target, so the target is
* the router node.
*/
if (pcmk__strcase_any_of(task, "cancel", "stop", "demote", "migrate_from",
"migrate_to", NULL) && !partial_migration) {
crm_trace("Routing %s for %s through remote connection's "
"current node %s (moving)%s",
action->task, (action->rsc? action->rsc->id : "no resource"),
(began_on? began_on->details->uname : "none"),
partial_migration? " (partial migration)" : "");
return began_on;
}
/* Everything else (start, promote, monitor, probe, refresh,
* clear failcount, delete, ...) must occur after the connection starts on
* the node it is moving to.
*/
crm_trace("Routing %s for %s through remote connection's "
"next node %s (moving)%s",
action->task, (action->rsc? action->rsc->id : "no resource"),
(ended_on? ended_on->details->uname : "none"),
partial_migration? " (partial migration)" : "");
return ended_on;
}
/*!
* \internal
* \brief Replace remote connection's addr="#uname" with actual address
*
* REMOTE_CONTAINER_HACK: If a given resource is a remote connection resource
* with its "addr" parameter set to "#uname", pull the actual value from the
* parameters evaluated without a node (which was put there earlier in
* pcmk__create_graph() when the bundle's expand() method was called).
*
* \param[in] rsc Resource to check
* \param[in] params Resource parameters evaluated per node
*/
void
pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params)
{
const char *remote_addr = g_hash_table_lookup(params,
XML_RSC_ATTR_REMOTE_RA_ADDR);
if (pcmk__str_eq(remote_addr, "#uname", pcmk__str_none)) {
GHashTable *base = pe_rsc_params(rsc, NULL, rsc->cluster);
remote_addr = g_hash_table_lookup(base, XML_RSC_ATTR_REMOTE_RA_ADDR);
if (remote_addr != NULL) {
g_hash_table_insert(params, strdup(XML_RSC_ATTR_REMOTE_RA_ADDR),
strdup(remote_addr));
}
}
}
/*!
* \brief Add special bundle meta-attributes to XML
*
* If a given action will be executed on a guest node (including a bundle),
* add the special bundle meta-attribute "container-attribute-target" and
* environment variable "physical_host" as XML attributes (using meta-attribute
* naming).
*
* \param[in] args_xml XML to add attributes to
* \param[in] action Action to check
*/
void
pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action)
{
pe_node_t *host = NULL;
enum action_tasks task;
if (!pe__is_guest_node(action->node)) {
return;
}
task = text2task(action->task);
if ((task == action_notify) || (task == action_notified)) {
task = text2task(g_hash_table_lookup(action->meta, "notify_operation"));
}
switch (task) {
case stop_rsc:
case stopped_rsc:
case action_demote:
case action_demoted:
// "Down" actions take place on guest's current host
host = pe__current_node(action->node->details->remote_rsc->container);
break;
case start_rsc:
case started_rsc:
case monitor_rsc:
case action_promote:
case action_promoted:
// "Up" actions take place on guest's next host
host = action->node->details->remote_rsc->container->allocated_to;
break;
default:
break;
}
if (host != NULL) {
hash2metafield((gpointer) XML_RSC_ATTR_TARGET,
(gpointer) g_hash_table_lookup(action->rsc->meta,
XML_RSC_ATTR_TARGET),
(gpointer) args_xml);
hash2metafield((gpointer) PCMK__ENV_PHYSICAL_HOST,
(gpointer) host->details->uname,
(gpointer) args_xml);
}
}