Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/include/crm/common/actions.h b/include/crm/common/actions.h
index 2cd3f616ab..3fcb9f71fb 100644
--- a/include/crm/common/actions.h
+++ b/include/crm/common/actions.h
@@ -1,412 +1,365 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_ACTIONS__H
#define PCMK__CRM_COMMON_ACTIONS__H
#include <stdbool.h> // bool
#include <strings.h> // strcasecmp()
#include <glib.h> // gboolean, guint
#include <libxml/tree.h> // xmlNode
#include <crm/lrmd_events.h> // lrmd_event_data_t
#include <glib.h> // GList, GHashTable
#include <libxml/tree.h> // xmlNode
#include <crm/common/nodes.h>
#include <crm/common/resources.h> // enum rsc_start_requirement, etc.
#include <crm/common/scheduler_types.h> // pcmk_resource_t, pcmk_node_t
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \file
* \brief APIs related to actions
* \ingroup core
*/
//! Default timeout (in milliseconds) for non-metadata actions
#define PCMK_DEFAULT_ACTION_TIMEOUT_MS 20000
// @COMPAT We don't need a separate timeout for metadata, much less a longer one
//! \deprecated Default timeout (in milliseconds) for metadata actions
#define PCMK_DEFAULT_METADATA_TIMEOUT_MS 30000
// Action names as strings
#define PCMK_ACTION_CANCEL "cancel"
#define PCMK_ACTION_CLEAR_FAILCOUNT "clear_failcount"
#define PCMK_ACTION_CLONE_ONE_OR_MORE "clone-one-or-more"
#define PCMK_ACTION_DELETE "delete"
#define PCMK_ACTION_DEMOTE "demote"
#define PCMK_ACTION_DEMOTED "demoted"
#define PCMK_ACTION_DO_SHUTDOWN "do_shutdown"
#define PCMK_ACTION_LIST "list"
#define PCMK_ACTION_LRM_DELETE "lrm_delete"
#define PCMK_ACTION_LOAD_STOPPED "load_stopped"
#define PCMK_ACTION_MAINTENANCE_NODES "maintenance_nodes"
#define PCMK_ACTION_META_DATA "meta-data"
#define PCMK_ACTION_METADATA "metadata"
#define PCMK_ACTION_MIGRATE_FROM "migrate_from"
#define PCMK_ACTION_MIGRATE_TO "migrate_to"
#define PCMK_ACTION_MONITOR "monitor"
#define PCMK_ACTION_NOTIFIED "notified"
#define PCMK_ACTION_NOTIFY "notify"
#define PCMK_ACTION_OFF "off"
#define PCMK_ACTION_ON "on"
#define PCMK_ACTION_ONE_OR_MORE "one-or-more"
#define PCMK_ACTION_PROMOTE "promote"
#define PCMK_ACTION_PROMOTED "promoted"
#define PCMK_ACTION_REBOOT "reboot"
#define PCMK_ACTION_RELOAD "reload"
#define PCMK_ACTION_RELOAD_AGENT "reload-agent"
#define PCMK_ACTION_RUNNING "running"
#define PCMK_ACTION_START "start"
#define PCMK_ACTION_STATUS "status"
#define PCMK_ACTION_STONITH "stonith"
#define PCMK_ACTION_STOP "stop"
#define PCMK_ACTION_STOPPED "stopped"
#define PCMK_ACTION_VALIDATE_ALL "validate-all"
-// Possible actions (including some pseudo-actions)
-// @COMPAT Make this internal when we can break API backward compatibility
-//!@{
-//! \deprecated Do not use (public access will be removed in a future release)
-enum action_tasks {
- pcmk_action_unspecified = 0, // Unspecified or unknown action
- pcmk_action_monitor, // Monitor
-
- // Each "completed" action must be the regular action plus 1
-
- pcmk_action_stop, // Stop
- pcmk_action_stopped, // Stop completed
-
- pcmk_action_start, // Start
- pcmk_action_started, // Start completed
-
- pcmk_action_notify, // Notify
- pcmk_action_notified, // Notify completed
-
- pcmk_action_promote, // Promote
- pcmk_action_promoted, // Promoted
-
- pcmk_action_demote, // Demote
- pcmk_action_demoted, // Demoted
-
- pcmk_action_shutdown, // Shut down node
- pcmk_action_fence, // Fence node
-
-#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
- no_action = pcmk_action_unspecified,
- monitor_rsc = pcmk_action_monitor,
- stop_rsc = pcmk_action_stop,
- stopped_rsc = pcmk_action_stopped,
- start_rsc = pcmk_action_start,
- started_rsc = pcmk_action_started,
- action_notify = pcmk_action_notify,
- action_notified = pcmk_action_notified,
- action_promote = pcmk_action_promote,
- action_promoted = pcmk_action_promoted,
- action_demote = pcmk_action_demote,
- action_demoted = pcmk_action_demoted,
- shutdown_crm = pcmk_action_shutdown,
- stonith_node = pcmk_action_fence,
-#endif
-};
-//!@}
-
// Possible responses to a resource action failure
// @COMPAT Make this internal when we can break API backward compatibility
//!@{
//! \deprecated Do not use (public access will be removed in a future release)
enum action_fail_response {
/* The order is (partially) significant here; the values from
* pcmk_on_fail_ignore through pcmk_on_fail_fence_node are in order of
* increasing severity.
*
* @COMPAT The values should be ordered and numbered per the "TODO" comments
* below, so all values are in order of severity and there is room for
* future additions, but that would break API compatibility.
* @TODO For now, we just use a function to compare the values specially, but
* at the next compatibility break, we should arrange things
* properly so we can compare with less than and greater than.
*/
// @TODO Define as 10
pcmk_on_fail_ignore = 0, // Act as if failure didn't happen
// @TODO Define as 30
pcmk_on_fail_restart = 1, // Restart resource
// @TODO Define as 60
pcmk_on_fail_ban = 2, // Ban resource from current node
// @TODO Define as 70
pcmk_on_fail_block = 3, // Treat resource as unmanaged
// @TODO Define as 80
pcmk_on_fail_stop = 4, // Stop resource and leave stopped
// @TODO Define as 90
pcmk_on_fail_standby_node = 5, // Put resource's node in standby
// @TODO Define as 100
pcmk_on_fail_fence_node = 6, // Fence resource's node
// @COMPAT Values below here are out of desired order for API compatibility
// @TODO Define as 50
pcmk_on_fail_restart_container = 7, // Restart resource's container
// @TODO Define as 40
/*
* Fence the remote node created by the resource if fencing is enabled,
* otherwise attempt to restart the resource (used internally for some
* remote connection failures).
*/
pcmk_on_fail_reset_remote = 8,
// @TODO Define as 20
pcmk_on_fail_demote = 9, // Demote if promotable, else stop
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
action_fail_ignore = pcmk_on_fail_ignore,
action_fail_recover = pcmk_on_fail_restart,
action_fail_migrate = pcmk_on_fail_ban,
action_fail_block = pcmk_on_fail_block,
action_fail_stop = pcmk_on_fail_stop,
action_fail_standby = pcmk_on_fail_standby_node,
action_fail_fence = pcmk_on_fail_fence_node,
action_fail_restart_container = pcmk_on_fail_restart_container,
action_fail_reset_remote = pcmk_on_fail_reset_remote,
action_fail_demote = pcmk_on_fail_demote,
#endif
};
//!@}
// Action scheduling flags
// @COMPAT Make this internal when we can break API backward compatibility
//!@{
//! \deprecated Do not use (public access will be removed in a future release)
enum pe_action_flags {
// No action flags set (compare with equality rather than bit set)
pcmk_no_action_flags = 0,
// Whether action does not require invoking an agent
pcmk_action_pseudo = (1 << 0),
// Whether action is runnable
pcmk_action_runnable = (1 << 1),
// Whether action should not be executed
pcmk_action_optional = (1 << 2),
// Whether action should be added to transition graph even if optional
pcmk_action_always_in_graph = (1 << 3),
// Whether operation-specific instance attributes have been unpacked yet
pcmk_action_attrs_evaluated = (1 << 4),
// Whether action is allowed to be part of a live migration
pcmk_action_migratable = (1 << 7),
// Whether action has been added to transition graph
pcmk_action_added_to_graph = (1 << 8),
// Whether action is a stop to abort a dangling migration
pcmk_action_migration_abort = (1 << 11),
/*
* Whether action is an ordering point for minimum required instances
* (used to implement ordering after clones with \c PCMK_META_CLONE_MIN
* configured, and ordered sets with \c PCMK_XA_REQUIRE_ALL set to
* \c PCMK_VALUE_FALSE).
*/
pcmk_action_min_runnable = (1 << 12),
// Whether action is recurring monitor that must be rescheduled if active
pcmk_action_reschedule = (1 << 13),
// Whether action has already been processed by a recursive procedure
pcmk_action_detect_loop = (1 << 14),
// Whether action's inputs have been de-duplicated yet
pcmk_action_inputs_deduplicated = (1 << 15),
// Whether action can be executed on DC rather than own node
pcmk_action_on_dc = (1 << 16),
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
pe_action_pseudo = pcmk_action_pseudo,
pe_action_runnable = pcmk_action_runnable,
pe_action_optional = pcmk_action_optional,
pe_action_print_always = pcmk_action_always_in_graph,
pe_action_have_node_attrs = pcmk_action_attrs_evaluated,
pe_action_implied_by_stonith = (1 << 6),
pe_action_migrate_runnable = pcmk_action_migratable,
pe_action_dumped = pcmk_action_added_to_graph,
pe_action_processed = (1 << 9),
pe_action_clear = (1 << 10),
pe_action_dangle = pcmk_action_migration_abort,
pe_action_requires_any = pcmk_action_min_runnable,
pe_action_reschedule = pcmk_action_reschedule,
pe_action_tracking = pcmk_action_detect_loop,
pe_action_dedup = pcmk_action_inputs_deduplicated,
pe_action_dc = pcmk_action_on_dc,
#endif
};
//!@}
/* @COMPAT enum pe_link_state and enum pe_ordering are currently needed for
* struct pe_action_wrapper_s (which is public) but should be removed at an
* API compatibility break when that can be refactored and made internal
*/
//!@{
//! \deprecated Do not use
enum pe_link_state {
pe_link_not_dumped = 0,
pe_link_dumped = 1,
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
pe_link_dup = 2,
#endif
};
enum pe_ordering {
pe_order_none = 0x0,
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
pe_order_optional = 0x1,
pe_order_apply_first_non_migratable = 0x2,
pe_order_implies_first = 0x10,
pe_order_implies_then = 0x20,
pe_order_promoted_implies_first = 0x40,
pe_order_implies_first_migratable = 0x80,
pe_order_runnable_left = 0x100,
pe_order_pseudo_left = 0x200,
pe_order_implies_then_on_node = 0x400,
pe_order_probe = 0x800,
pe_order_restart = 0x1000,
pe_order_stonith_stop = 0x2000,
pe_order_serialize_only = 0x4000,
pe_order_same_node = 0x8000,
pe_order_implies_first_printed = 0x10000,
pe_order_implies_then_printed = 0x20000,
pe_order_asymmetrical = 0x100000,
pe_order_load = 0x200000,
pe_order_one_or_more = 0x400000,
pe_order_anti_colocation = 0x800000,
pe_order_preserve = 0x1000000,
pe_order_then_cancels_first = 0x2000000,
pe_order_trace = 0x4000000,
pe_order_implies_first_master = pe_order_promoted_implies_first,
#endif
};
// Action sequenced relative to another action
// @COMPAT This should be internal
struct pe_action_wrapper_s {
// @COMPAT This should be uint32_t
enum pe_ordering type; // Group of enum pcmk__action_relation_flags
// @COMPAT This should be a bool
enum pe_link_state state; // Whether action has been added to graph yet
pcmk_action_t *action; // Action to be sequenced
};
//!@}
// Implementation of pcmk_action_t
// @COMPAT Make this internal when we can break API backward compatibility
//!@{
//! \deprecated Do not use (public access will be removed in a future release)
struct pe_action_s {
int id; // Counter to identify action
/*
* When the controller aborts a transition graph, it sets an abort priority.
* If this priority is higher, the action will still be executed anyway.
* Pseudo-actions are always allowed, so this is irrelevant for them.
*/
int priority;
pcmk_resource_t *rsc; // Resource to apply action to, if any
pcmk_node_t *node; // Node to execute action on, if any
xmlNode *op_entry; // Action XML configuration, if any
char *task; // Action name
char *uuid; // Action key
char *cancel_task; // If task is "cancel", the action being cancelled
char *reason; // Readable description of why action is needed
//@ COMPAT Change to uint32_t at a compatibility break
enum pe_action_flags flags; // Group of enum pe_action_flags
enum rsc_start_requirement needs; // Prerequisite for recovery
enum action_fail_response on_fail; // Response to failure
enum rsc_role_e fail_role; // Resource role if action fails
GHashTable *meta; // Meta-attributes relevant to action
GHashTable *extra; // Action-specific instance attributes
/* Current count of runnable instance actions for "first" action in an
* ordering dependency with pcmk__ar_min_runnable set.
*/
int runnable_before; // For Pacemaker use only
/*
* Number of instance actions for "first" action in an ordering dependency
* with pcmk__ar_min_runnable set that must be runnable before this action
* can be runnable.
*/
int required_runnable_before;
// Actions in a relation with this one (as pcmk__related_action_t *)
GList *actions_before;
GList *actions_after;
/* This is intended to hold data that varies by the type of action, but is
* not currently used. Some of the above fields could be moved here except
* for API backward compatibility.
*/
void *action_details;
};
//!@}
// @COMPAT Make this internal when we can break API backward compatibility
//! \deprecated Do not use (public access will be removed in a future release)
const char *pcmk_on_fail_text(enum action_fail_response on_fail);
// For parsing various action-related string specifications
gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
guint *interval_ms);
gboolean decode_transition_key(const char *key, char **uuid, int *transition_id,
int *action_id, int *target_rc);
gboolean decode_transition_magic(const char *magic, char **uuid,
int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc);
// @COMPAT Either these shouldn't be in libcrmcommon or lrmd_event_data_t should
int rsc_op_expected_rc(const lrmd_event_data_t *event);
gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
bool crm_op_needs_metadata(const char *rsc_class, const char *op);
xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
const char *task, const char *interval_spec,
const char *timeout);
bool pcmk_is_probe(const char *task, guint interval);
bool pcmk_xe_is_probe(const xmlNode *xml_op);
bool pcmk_xe_mask_probe_failure(const xmlNode *xml_op);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_ACTIONS__H
diff --git a/include/crm/common/actions_internal.h b/include/crm/common/actions_internal.h
index 68973d2841..14de5285b7 100644
--- a/include/crm/common/actions_internal.h
+++ b/include/crm/common/actions_internal.h
@@ -1,129 +1,155 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_ACTIONS_INTERNAL__H
#define PCMK__CRM_COMMON_ACTIONS_INTERNAL__H
#include <stdbool.h> // bool
#include <glib.h> // guint
#include <libxml/tree.h> // xmlNode
#include <crm/common/actions.h> // PCMK_ACTION_MONITOR
#include <crm/common/strings_internal.h> // pcmk__str_eq()
#ifdef __cplusplus
extern "C" {
#endif
// Action names as strings
// @COMPAT Deprecated since 2.0.0
#define PCMK__ACTION_POWEROFF "poweroff"
//! printf-style format to create operation key from resource, action, interval
#define PCMK__OP_FMT "%s_%s_%u"
/*!
* \internal
* \brief Set action flags for an action
*
* \param[in,out] action Action to set flags for
* \param[in] flags_to_set Group of enum pe_action_flags to set
*/
#define pcmk__set_action_flags(action, flags_to_set) do { \
(action)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, \
"Action", (action)->uuid, \
(action)->flags, \
(flags_to_set), \
#flags_to_set); \
} while (0)
/*!
* \internal
* \brief Clear action flags for an action
*
* \param[in,out] action Action to clear flags for
* \param[in] flags_to_clear Group of enum pe_action_flags to clear
*/
#define pcmk__clear_action_flags(action, flags_to_clear) do { \
(action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, \
"Action", (action)->uuid, \
(action)->flags, \
(flags_to_clear), \
#flags_to_clear); \
} while (0)
/*!
* \internal
* \brief Set action flags for a flag group
*
* \param[in,out] action_flags Flag group to set flags for
* \param[in] action_name Name of action being modified (for logging)
* \param[in] to_set Group of enum pe_action_flags to set
*/
#define pcmk__set_raw_action_flags(action_flags, action_name, to_set) do { \
action_flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action", action_name, \
(action_flags), \
(to_set), #to_set); \
} while (0)
/*!
* \internal
* \brief Clear action flags for a flag group
*
* \param[in,out] action_flags Flag group to clear flags for
* \param[in] action_name Name of action being modified (for logging)
* \param[in] to_clear Group of enum pe_action_flags to clear
*/
#define pcmk__clear_raw_action_flags(action_flags, action_name, to_clear) \
do { \
action_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
"Action", action_name, \
(action_flags), \
(to_clear), #to_clear); \
} while (0)
+// Possible actions (including some pseudo-actions)
+enum pcmk__action_type {
+ pcmk__action_unspecified = 0, // Unspecified or unknown action
+ pcmk__action_monitor, // Monitor
+
+ // Each "completed" action must be the regular action plus 1
+
+ pcmk__action_stop, // Stop
+ pcmk__action_stopped, // Stop completed
+
+ pcmk__action_start, // Start
+ pcmk__action_started, // Start completed
+
+ pcmk__action_notify, // Notify
+ pcmk__action_notified, // Notify completed
+
+ pcmk__action_promote, // Promote
+ pcmk__action_promoted, // Promoted
+
+ pcmk__action_demote, // Demote
+ pcmk__action_demoted, // Demoted
+
+ pcmk__action_shutdown, // Shut down node
+ pcmk__action_fence, // Fence node
+};
+
char *pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms);
char *pcmk__notify_key(const char *rsc_id, const char *notify_type,
const char *op_type);
char *pcmk__transition_key(int transition_id, int action_id, int target_rc,
const char *node);
void pcmk__filter_op_for_digest(xmlNode *param_set);
bool pcmk__is_fencing_action(const char *action);
-enum action_tasks pcmk__parse_action(const char *action_name);
-const char *pcmk__action_text(enum action_tasks action);
+enum pcmk__action_type pcmk__parse_action(const char *action_name);
+const char *pcmk__action_text(enum pcmk__action_type action);
/*!
* \internal
* \brief Get a human-friendly action name
*
* \param[in] action_name Actual action name
* \param[in] interval_ms Action interval (in milliseconds)
*
* \return Action name suitable for display
*/
static inline const char *
pcmk__readable_action(const char *action_name, guint interval_ms) {
if ((interval_ms == 0)
&& pcmk__str_eq(action_name, PCMK_ACTION_MONITOR, pcmk__str_none)) {
return "probe";
}
return action_name;
}
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_ACTIONS_INTERNAL__H
diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h
index 1a1abfd3f7..d5b033a881 100644
--- a/include/crm/pengine/internal.h
+++ b/include/crm/pengine/internal.h
@@ -1,420 +1,420 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_PENGINE_INTERNAL__H
# define PCMK__CRM_PENGINE_INTERNAL__H
# include <stdbool.h>
# include <stdint.h>
# include <string.h>
# include <crm/common/xml.h>
# include <crm/pengine/status.h>
# include <crm/pengine/remote_internal.h>
# include <crm/common/internal.h>
# include <crm/common/options_internal.h>
# include <crm/common/output_internal.h>
# include <crm/common/scheduler_internal.h>
const char *pe__resource_description(const pcmk_resource_t *rsc,
uint32_t show_opts);
bool pe__clone_is_ordered(const pcmk_resource_t *clone);
int pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag);
bool pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags);
bool pe__group_flag_is_set(const pcmk_resource_t *group, uint32_t flags);
pcmk_resource_t *pe__last_group_member(const pcmk_resource_t *group);
const pcmk_resource_t *pe__const_top_resource(const pcmk_resource_t *rsc,
bool include_bundle);
int pe__clone_max(const pcmk_resource_t *clone);
int pe__clone_node_max(const pcmk_resource_t *clone);
int pe__clone_promoted_max(const pcmk_resource_t *clone);
int pe__clone_promoted_node_max(const pcmk_resource_t *clone);
void pe__create_clone_notifications(pcmk_resource_t *clone);
void pe__free_clone_notification_data(pcmk_resource_t *clone);
void pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
pcmk_action_t *start,
pcmk_action_t *started,
pcmk_action_t *stop,
pcmk_action_t *stopped);
pcmk_action_t *pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task,
bool optional, bool runnable);
void pe__create_promotable_pseudo_ops(pcmk_resource_t *clone,
bool any_promoting, bool any_demoting);
bool pe_can_fence(const pcmk_scheduler_t *scheduler, const pcmk_node_t *node);
char *native_parameter(pcmk_resource_t *rsc, pcmk_node_t *node, gboolean create,
const char *name, pcmk_scheduler_t *scheduler);
pcmk_node_t *native_location(const pcmk_resource_t *rsc, GList **list,
int current);
void native_add_running(pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_scheduler_t *scheduler, gboolean failed);
gboolean native_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler);
gboolean group_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler);
gboolean clone_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler);
gboolean pe__unpack_bundle(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler);
pcmk_resource_t *native_find_rsc(pcmk_resource_t *rsc, const char *id,
const pcmk_node_t *node, int flags);
gboolean native_active(pcmk_resource_t *rsc, gboolean all);
gboolean group_active(pcmk_resource_t *rsc, gboolean all);
gboolean clone_active(pcmk_resource_t *rsc, gboolean all);
gboolean pe__bundle_active(pcmk_resource_t *rsc, gboolean all);
gchar *pcmk__native_output_string(const pcmk_resource_t *rsc, const char *name,
const pcmk_node_t *node, uint32_t show_opts,
const char *target_role, bool show_nodes);
int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name,
...) G_GNUC_NULL_TERMINATED;
char *pe__node_display_name(pcmk_node_t *node, bool print_detail);
// Clone notifications (pe_notif.c)
void pe__order_notifs_after_fencing(const pcmk_action_t *action,
pcmk_resource_t *rsc,
pcmk_action_t *stonith_op);
// Resource output methods
int pe__clone_xml(pcmk__output_t *out, va_list args);
int pe__clone_default(pcmk__output_t *out, va_list args);
int pe__group_xml(pcmk__output_t *out, va_list args);
int pe__group_default(pcmk__output_t *out, va_list args);
int pe__bundle_xml(pcmk__output_t *out, va_list args);
int pe__bundle_html(pcmk__output_t *out, va_list args);
int pe__bundle_text(pcmk__output_t *out, va_list args);
int pe__node_html(pcmk__output_t *out, va_list args);
int pe__node_text(pcmk__output_t *out, va_list args);
int pe__node_xml(pcmk__output_t *out, va_list args);
int pe__resource_xml(pcmk__output_t *out, va_list args);
int pe__resource_html(pcmk__output_t *out, va_list args);
int pe__resource_text(pcmk__output_t *out, va_list args);
void native_free(pcmk_resource_t *rsc);
void group_free(pcmk_resource_t *rsc);
void clone_free(pcmk_resource_t *rsc);
void pe__free_bundle(pcmk_resource_t *rsc);
enum rsc_role_e native_resource_state(const pcmk_resource_t *rsc,
gboolean current);
enum rsc_role_e group_resource_state(const pcmk_resource_t *rsc,
gboolean current);
enum rsc_role_e clone_resource_state(const pcmk_resource_t *rsc,
gboolean current);
enum rsc_role_e pe__bundle_resource_state(const pcmk_resource_t *rsc,
gboolean current);
void pe__count_common(pcmk_resource_t *rsc);
void pe__count_bundle(pcmk_resource_t *rsc);
void common_free(pcmk_resource_t *rsc);
pcmk_node_t *pe__copy_node(const pcmk_node_t *this_node);
time_t get_effective_time(pcmk_scheduler_t *scheduler);
/* Failure handling utilities (from failcounts.c) */
int pe_get_failcount(const pcmk_node_t *node, pcmk_resource_t *rsc,
time_t *last_failure, uint32_t flags,
const xmlNode *xml_op);
pcmk_action_t *pe__clear_failcount(pcmk_resource_t *rsc,
const pcmk_node_t *node, const char *reason,
pcmk_scheduler_t *scheduler);
/* Functions for finding/counting a resource's active nodes */
bool pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_node_t **active, unsigned int *count_all,
unsigned int *count_clean);
pcmk_node_t *pe__find_active_requires(const pcmk_resource_t *rsc,
unsigned int *count);
/* Binary like operators for lists of nodes */
GHashTable *pe__node_list2table(const GList *list);
pcmk_action_t *get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler);
gboolean order_actions(pcmk_action_t *lh_action, pcmk_action_t *rh_action,
uint32_t flags);
void pe__show_node_scores_as(const char *file, const char *function,
int line, bool to_log, const pcmk_resource_t *rsc,
const char *comment, GHashTable *nodes,
pcmk_scheduler_t *scheduler);
#define pe__show_node_scores(level, rsc, text, nodes, scheduler) \
pe__show_node_scores_as(__FILE__, __func__, __LINE__, \
(level), (rsc), (text), (nodes), (scheduler))
GHashTable *pcmk__unpack_action_meta(pcmk_resource_t *rsc,
const pcmk_node_t *node,
const char *action_name, guint interval_ms,
const xmlNode *action_config);
GHashTable *pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
GHashTable *node_attrs,
pcmk_scheduler_t *data_set);
xmlNode *pcmk__find_action_config(const pcmk_resource_t *rsc,
const char *action_name, guint interval_ms,
bool include_disabled);
enum rsc_start_requirement pcmk__action_requires(const pcmk_resource_t *rsc,
const char *action_name);
enum action_fail_response pcmk__parse_on_fail(const pcmk_resource_t *rsc,
const char *action_name,
guint interval_ms,
const char *value);
enum rsc_role_e pcmk__role_after_failure(const pcmk_resource_t *rsc,
const char *action_name,
enum action_fail_response on_fail,
GHashTable *meta);
pcmk_action_t *custom_action(pcmk_resource_t *rsc, char *key, const char *task,
const pcmk_node_t *on_node, gboolean optional,
pcmk_scheduler_t *scheduler);
#define delete_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_DELETE, 0)
#define stop_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_STOP, 0)
#define reload_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_RELOAD_AGENT, 0)
#define start_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_START, 0)
#define promote_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_PROMOTE, 0)
#define demote_key(rsc) pcmk__op_key((rsc)->id, PCMK_ACTION_DEMOTE, 0)
#define delete_action(rsc, node, optional) \
custom_action((rsc), delete_key(rsc), PCMK_ACTION_DELETE, \
(node), (optional), (rsc)->private->scheduler)
#define stop_action(rsc, node, optional) \
custom_action((rsc), stop_key(rsc), PCMK_ACTION_STOP, \
(node), (optional), (rsc)->private->scheduler)
#define start_action(rsc, node, optional) \
custom_action((rsc), start_key(rsc), PCMK_ACTION_START, \
(node), (optional), (rsc)->private->scheduler)
#define promote_action(rsc, node, optional) \
custom_action((rsc), promote_key(rsc), PCMK_ACTION_PROMOTE, \
(node), (optional), (rsc)->private->scheduler)
#define demote_action(rsc, node, optional) \
custom_action((rsc), demote_key(rsc), PCMK_ACTION_DEMOTE, \
(node), (optional), (rsc)->private->scheduler)
pcmk_action_t *find_first_action(const GList *input, const char *uuid,
const char *task, const pcmk_node_t *on_node);
-enum action_tasks get_complex_task(const pcmk_resource_t *rsc,
- const char *name);
+enum pcmk__action_type get_complex_task(const pcmk_resource_t *rsc,
+ const char *name);
GList *find_actions(GList *input, const char *key, const pcmk_node_t *on_node);
GList *find_actions_exact(GList *input, const char *key,
const pcmk_node_t *on_node);
GList *pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *task, bool require_node);
extern void pe_free_action(pcmk_action_t *action);
void resource_location(pcmk_resource_t *rsc, const pcmk_node_t *node, int score,
const char *tag, pcmk_scheduler_t *scheduler);
extern int pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
bool same_node_default);
extern gint sort_op_by_callid(gconstpointer a, gconstpointer b);
gboolean get_target_role(const pcmk_resource_t *rsc, enum rsc_role_e *role);
void pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role,
const char *why);
pcmk_resource_t *find_clone_instance(const pcmk_resource_t *rsc,
const char *sub_id);
extern void destroy_ticket(gpointer data);
pcmk_ticket_t *ticket_new(const char *ticket_id, pcmk_scheduler_t *scheduler);
// Resources for manipulating resource names
const char *pe_base_name_end(const char *id);
char *clone_strip(const char *last_rsc_id);
char *clone_zero(const char *last_rsc_id);
static inline bool
pe_base_name_eq(const pcmk_resource_t *rsc, const char *id)
{
if (id && rsc && rsc->id) {
// Number of characters in rsc->id before any clone suffix
size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1;
return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len);
}
return false;
}
int pe__target_rc_from_xml(const xmlNode *xml_op);
gint pe__cmp_node_name(gconstpointer a, gconstpointer b);
bool is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any);
pcmk__op_digest_t *pe__calculate_digests(pcmk_resource_t *rsc, const char *task,
guint *interval_ms,
const pcmk_node_t *node,
const xmlNode *xml_op,
GHashTable *overrides,
bool calc_secure,
pcmk_scheduler_t *scheduler);
void pe__free_digests(gpointer ptr);
pcmk__op_digest_t *rsc_action_digest_cmp(pcmk_resource_t *rsc,
const xmlNode *xml_op,
pcmk_node_t *node,
pcmk_scheduler_t *scheduler);
pcmk_action_t *pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
const char *reason, bool priority_delay,
pcmk_scheduler_t *scheduler);
void trigger_unfencing(pcmk_resource_t *rsc, pcmk_node_t *node,
const char *reason, pcmk_action_t *dependency,
pcmk_scheduler_t *scheduler);
char *pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag);
void pe_action_set_reason(pcmk_action_t *action, const char *reason,
bool overwrite);
void pe__add_action_expected_result(pcmk_action_t *action, int expected_result);
void pe__set_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags);
void pe__clear_resource_flags_recursive(pcmk_resource_t *rsc, uint64_t flags);
void pe__clear_resource_flags_on_all(pcmk_scheduler_t *scheduler,
uint64_t flag);
gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref);
int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, unsigned int options);
void pe_fence_node(pcmk_scheduler_t *scheduler, pcmk_node_t *node,
const char *reason, bool priority_delay);
pcmk_node_t *pe_create_node(const char *id, const char *uname, const char *type,
const char *score, pcmk_scheduler_t *scheduler);
int pe__common_output_text(pcmk__output_t *out, const pcmk_resource_t *rsc,
const char *name, const pcmk_node_t *node,
unsigned int options);
int pe__common_output_html(pcmk__output_t *out, const pcmk_resource_t *rsc,
const char *name, const pcmk_node_t *node,
unsigned int options);
GList *pe__bundle_containers(const pcmk_resource_t *bundle);
int pe__bundle_max(const pcmk_resource_t *rsc);
bool pe__node_is_bundle_instance(const pcmk_resource_t *bundle,
const pcmk_node_t *node);
pcmk_resource_t *pe__bundled_resource(const pcmk_resource_t *rsc);
const pcmk_resource_t *pe__get_rsc_in_container(const pcmk_resource_t *instance);
pcmk_resource_t *pe__first_container(const pcmk_resource_t *bundle);
void pe__foreach_bundle_replica(pcmk_resource_t *bundle,
bool (*fn)(pcmk__bundle_replica_t *, void *),
void *user_data);
void pe__foreach_const_bundle_replica(const pcmk_resource_t *bundle,
bool (*fn)(const pcmk__bundle_replica_t *,
void *),
void *user_data);
pcmk_resource_t *pe__find_bundle_replica(const pcmk_resource_t *bundle,
const pcmk_node_t *node);
bool pe__bundle_needs_remote_name(pcmk_resource_t *rsc);
const char *pe__add_bundle_remote_name(pcmk_resource_t *rsc, xmlNode *xml,
const char *field);
bool pe__is_universal_clone(const pcmk_resource_t *rsc,
const pcmk_scheduler_t *scheduler);
void pe__add_param_check(const xmlNode *rsc_op, pcmk_resource_t *rsc,
pcmk_node_t *node, enum pcmk__check_parameters,
pcmk_scheduler_t *scheduler);
void pe__foreach_param_check(pcmk_scheduler_t *scheduler,
void (*cb)(pcmk_resource_t*, pcmk_node_t*,
const xmlNode*,
enum pcmk__check_parameters));
void pe__free_param_checks(pcmk_scheduler_t *scheduler);
bool pe__shutdown_requested(const pcmk_node_t *node);
void pe__update_recheck_time(time_t recheck, pcmk_scheduler_t *scheduler,
const char *reason);
/*!
* \internal
* \brief Register xml formatting message functions.
*
* \param[in,out] out Output object to register messages with
*/
void pe__register_messages(pcmk__output_t *out);
void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name,
const pe_rule_eval_data_t *rule_data,
GHashTable *hash, const char *always_first,
gboolean overwrite,
pcmk_scheduler_t *scheduler);
bool pe__resource_is_disabled(const pcmk_resource_t *rsc);
void pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node);
GList *pe__rscs_with_tag(pcmk_scheduler_t *scheduler, const char *tag_name);
GList *pe__unames_with_tag(pcmk_scheduler_t *scheduler, const char *tag_name);
bool pe__rsc_has_tag(pcmk_scheduler_t *scheduler, const char *rsc,
const char *tag);
bool pe__uname_has_tag(pcmk_scheduler_t *scheduler, const char *node,
const char *tag);
bool pe__rsc_running_on_only(const pcmk_resource_t *rsc,
const pcmk_node_t *node);
bool pe__rsc_running_on_any(pcmk_resource_t *rsc, GList *node_list);
GList *pe__filter_rsc_list(GList *rscs, GList *filter);
GList * pe__build_node_name_list(pcmk_scheduler_t *scheduler, const char *s);
GList * pe__build_rsc_list(pcmk_scheduler_t *scheduler, const char *s);
bool pcmk__rsc_filtered_by_node(pcmk_resource_t *rsc, GList *only_node);
gboolean pe__bundle_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent);
gboolean pe__clone_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent);
gboolean pe__group_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent);
gboolean pe__native_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc,
gboolean check_parent);
xmlNode *pe__failed_probe_for_rsc(const pcmk_resource_t *rsc, const char *name);
const char *pe__clone_child_id(const pcmk_resource_t *rsc);
int pe__sum_node_health_scores(const pcmk_node_t *node, int base_health);
int pe__node_health(pcmk_node_t *node);
static inline enum pcmk__health_strategy
pe__health_strategy(pcmk_scheduler_t *scheduler)
{
const char *strategy = pcmk__cluster_option(scheduler->config_hash,
PCMK_OPT_NODE_HEALTH_STRATEGY);
return pcmk__parse_health_strategy(strategy);
}
static inline int
pe__health_score(const char *option, pcmk_scheduler_t *scheduler)
{
const char *value = pcmk__cluster_option(scheduler->config_hash, option);
return char2score(value);
}
#endif
diff --git a/lib/common/actions.c b/lib/common/actions.c
index 2b87befc7a..782207671a 100644
--- a/lib/common/actions.c
+++ b/lib/common/actions.c
@@ -1,587 +1,587 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <ctype.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/common/util.h>
#include <crm/common/scheduler.h>
/*!
* \brief Get string equivalent of an action type
*
* \param[in] action Action type
*
* \return Static string describing \p action
*/
const char *
-pcmk__action_text(enum action_tasks action)
+pcmk__action_text(enum pcmk__action_type action)
{
switch (action) {
- case pcmk_action_stop:
+ case pcmk__action_stop:
return PCMK_ACTION_STOP;
- case pcmk_action_stopped:
+ case pcmk__action_stopped:
return PCMK_ACTION_STOPPED;
- case pcmk_action_start:
+ case pcmk__action_start:
return PCMK_ACTION_START;
- case pcmk_action_started:
+ case pcmk__action_started:
return PCMK_ACTION_RUNNING;
- case pcmk_action_shutdown:
+ case pcmk__action_shutdown:
return PCMK_ACTION_DO_SHUTDOWN;
- case pcmk_action_fence:
+ case pcmk__action_fence:
return PCMK_ACTION_STONITH;
- case pcmk_action_monitor:
+ case pcmk__action_monitor:
return PCMK_ACTION_MONITOR;
- case pcmk_action_notify:
+ case pcmk__action_notify:
return PCMK_ACTION_NOTIFY;
- case pcmk_action_notified:
+ case pcmk__action_notified:
return PCMK_ACTION_NOTIFIED;
- case pcmk_action_promote:
+ case pcmk__action_promote:
return PCMK_ACTION_PROMOTE;
- case pcmk_action_promoted:
+ case pcmk__action_promoted:
return PCMK_ACTION_PROMOTED;
- case pcmk_action_demote:
+ case pcmk__action_demote:
return PCMK_ACTION_DEMOTE;
- case pcmk_action_demoted:
+ case pcmk__action_demoted:
return PCMK_ACTION_DEMOTED;
- default: // pcmk_action_unspecified or invalid
+ default: // pcmk__action_unspecified or invalid
return "no_action";
}
}
/*!
* \brief Parse an action type from an action name
*
* \param[in] action_name Action name
*
* \return Action type corresponding to \p action_name
*/
-enum action_tasks
+enum pcmk__action_type
pcmk__parse_action(const char *action_name)
{
if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
- return pcmk_action_stop;
+ return pcmk__action_stop;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_STOPPED, pcmk__str_none)) {
- return pcmk_action_stopped;
+ return pcmk__action_stopped;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
- return pcmk_action_start;
+ return pcmk__action_start;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_RUNNING, pcmk__str_none)) {
- return pcmk_action_started;
+ return pcmk__action_started;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_DO_SHUTDOWN,
pcmk__str_none)) {
- return pcmk_action_shutdown;
+ return pcmk__action_shutdown;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_STONITH, pcmk__str_none)) {
- return pcmk_action_fence;
+ return pcmk__action_fence;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_MONITOR, pcmk__str_none)) {
- return pcmk_action_monitor;
+ return pcmk__action_monitor;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
- return pcmk_action_notify;
+ return pcmk__action_notify;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_NOTIFIED,
pcmk__str_none)) {
- return pcmk_action_notified;
+ return pcmk__action_notified;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
- return pcmk_action_promote;
+ return pcmk__action_promote;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
- return pcmk_action_demote;
+ return pcmk__action_demote;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTED,
pcmk__str_none)) {
- return pcmk_action_promoted;
+ return pcmk__action_promoted;
} else if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTED, pcmk__str_none)) {
- return pcmk_action_demoted;
+ return pcmk__action_demoted;
}
- return pcmk_action_unspecified;
+ return pcmk__action_unspecified;
}
/*!
* \brief Get string equivalent of a failure handling type
*
* \param[in] on_fail Failure handling type
*
* \return Static string describing \p on_fail
*/
const char *
pcmk_on_fail_text(enum action_fail_response on_fail)
{
switch (on_fail) {
case pcmk_on_fail_ignore:
return "ignore";
case pcmk_on_fail_demote:
return "demote";
case pcmk_on_fail_block:
return "block";
case pcmk_on_fail_restart:
return "recover";
case pcmk_on_fail_ban:
return "migrate";
case pcmk_on_fail_stop:
return "stop";
case pcmk_on_fail_fence_node:
return "fence";
case pcmk_on_fail_standby_node:
return "standby";
case pcmk_on_fail_restart_container:
return "restart-container";
case pcmk_on_fail_reset_remote:
return "reset-remote";
}
return "<unknown>";
}
/*!
* \brief Generate an operation key (RESOURCE_ACTION_INTERVAL)
*
* \param[in] rsc_id ID of resource being operated on
* \param[in] op_type Operation name
* \param[in] interval_ms Operation interval
*
* \return Newly allocated memory containing operation key as string
*
* \note This function asserts on errors, so it will never return NULL.
* The caller is responsible for freeing the result with free().
*/
char *
pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
{
CRM_ASSERT(rsc_id != NULL);
CRM_ASSERT(op_type != NULL);
return crm_strdup_printf(PCMK__OP_FMT, rsc_id, op_type, interval_ms);
}
static inline gboolean
convert_interval(const char *s, guint *interval_ms)
{
unsigned long l;
errno = 0;
l = strtoul(s, NULL, 10);
if (errno != 0) {
return FALSE;
}
*interval_ms = (guint) l;
return TRUE;
}
/*!
* \internal
* \brief Check for underbar-separated substring match
*
* \param[in] key Overall string being checked
* \param[in] position Match before underbar at this \p key index
* \param[in] matches Substrings to match (may contain underbars)
*
* \return \p key index of underbar before any matching substring,
* or 0 if none
*/
static size_t
match_before(const char *key, size_t position, const char **matches)
{
for (int i = 0; matches[i] != NULL; ++i) {
const size_t match_len = strlen(matches[i]);
// Must have at least X_MATCH before position
if (position > (match_len + 1)) {
const size_t possible = position - match_len - 1;
if ((key[possible] == '_')
&& (strncmp(key + possible + 1, matches[i], match_len) == 0)) {
return possible;
}
}
}
return 0;
}
gboolean
parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
{
guint local_interval_ms = 0;
const size_t key_len = (key == NULL)? 0 : strlen(key);
// Operation keys must be formatted as RSC_ACTION_INTERVAL
size_t action_underbar = 0; // Index in key of underbar before ACTION
size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
size_t possible = 0;
/* Underbar was a poor choice of separator since both RSC and ACTION can
* contain underbars. Here, list action names and name prefixes that can.
*/
const char *actions_with_underbars[] = {
PCMK_ACTION_MIGRATE_FROM,
PCMK_ACTION_MIGRATE_TO,
NULL
};
const char *action_prefixes_with_underbars[] = {
"pre_" PCMK_ACTION_NOTIFY,
"post_" PCMK_ACTION_NOTIFY,
"confirmed-pre_" PCMK_ACTION_NOTIFY,
"confirmed-post_" PCMK_ACTION_NOTIFY,
NULL,
};
// Initialize output variables in case of early return
if (rsc_id) {
*rsc_id = NULL;
}
if (op_type) {
*op_type = NULL;
}
if (interval_ms) {
*interval_ms = 0;
}
// RSC_ACTION_INTERVAL implies a minimum of 5 characters
if (key_len < 5) {
return FALSE;
}
// Find, parse, and validate interval
interval_underbar = key_len - 2;
while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
--interval_underbar;
}
if ((interval_underbar == 2)
|| !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
return FALSE;
}
// Find the base (OCF) action name, disregarding prefixes
action_underbar = match_before(key, interval_underbar,
actions_with_underbars);
if (action_underbar == 0) {
action_underbar = interval_underbar - 2;
while ((action_underbar > 0) && (key[action_underbar] != '_')) {
--action_underbar;
}
if (action_underbar == 0) {
return FALSE;
}
}
possible = match_before(key, action_underbar,
action_prefixes_with_underbars);
if (possible != 0) {
action_underbar = possible;
}
// Set output variables
if (rsc_id != NULL) {
*rsc_id = strndup(key, action_underbar);
pcmk__mem_assert(*rsc_id);
}
if (op_type != NULL) {
*op_type = strndup(key + action_underbar + 1,
interval_underbar - action_underbar - 1);
pcmk__mem_assert(*op_type);
}
if (interval_ms != NULL) {
*interval_ms = local_interval_ms;
}
return TRUE;
}
char *
pcmk__notify_key(const char *rsc_id, const char *notify_type,
const char *op_type)
{
CRM_CHECK(rsc_id != NULL, return NULL);
CRM_CHECK(op_type != NULL, return NULL);
CRM_CHECK(notify_type != NULL, return NULL);
return crm_strdup_printf("%s_%s_notify_%s_0",
rsc_id, notify_type, op_type);
}
/*!
* \brief Parse a transition magic string into its constituent parts
*
* \param[in] magic Magic string to parse (must be non-NULL)
* \param[out] uuid If non-NULL, where to store copy of parsed UUID
* \param[out] transition_id If non-NULL, where to store parsed transition ID
* \param[out] action_id If non-NULL, where to store parsed action ID
* \param[out] op_status If non-NULL, where to store parsed result status
* \param[out] op_rc If non-NULL, where to store parsed actual rc
* \param[out] target_rc If non-NULL, where to stored parsed target rc
*
* \return TRUE if key was valid, FALSE otherwise
* \note If uuid is supplied and this returns TRUE, the caller is responsible
* for freeing the memory for *uuid using free().
*/
gboolean
decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc)
{
int res = 0;
char *key = NULL;
gboolean result = TRUE;
int local_op_status = -1;
int local_op_rc = -1;
CRM_CHECK(magic != NULL, return FALSE);
#ifdef HAVE_SSCANF_M
res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
#else
// magic must have >=4 other characters
key = pcmk__assert_alloc(1, strlen(magic) - 3);
res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
#endif
if (res == EOF) {
crm_err("Could not decode transition information '%s': %s",
magic, pcmk_rc_str(errno));
result = FALSE;
} else if (res < 3) {
crm_warn("Transition information '%s' incomplete (%d of 3 expected items)",
magic, res);
result = FALSE;
} else {
if (op_status) {
*op_status = local_op_status;
}
if (op_rc) {
*op_rc = local_op_rc;
}
result = decode_transition_key(key, uuid, transition_id, action_id,
target_rc);
}
free(key);
return result;
}
char *
pcmk__transition_key(int transition_id, int action_id, int target_rc,
const char *node)
{
CRM_CHECK(node != NULL, return NULL);
return crm_strdup_printf("%d:%d:%d:%-*s",
action_id, transition_id, target_rc, 36, node);
}
/*!
* \brief Parse a transition key into its constituent parts
*
* \param[in] key Transition key to parse (must be non-NULL)
* \param[out] uuid If non-NULL, where to store copy of parsed UUID
* \param[out] transition_id If non-NULL, where to store parsed transition ID
* \param[out] action_id If non-NULL, where to store parsed action ID
* \param[out] target_rc If non-NULL, where to stored parsed target rc
*
* \return TRUE if key was valid, FALSE otherwise
* \note If uuid is supplied and this returns TRUE, the caller is responsible
* for freeing the memory for *uuid using free().
*/
gboolean
decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
int *target_rc)
{
int local_transition_id = -1;
int local_action_id = -1;
int local_target_rc = -1;
char local_uuid[37] = { '\0' };
// Initialize any supplied output arguments
if (uuid) {
*uuid = NULL;
}
if (transition_id) {
*transition_id = -1;
}
if (action_id) {
*action_id = -1;
}
if (target_rc) {
*target_rc = -1;
}
CRM_CHECK(key != NULL, return FALSE);
if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
&local_target_rc, local_uuid) != 4) {
crm_err("Invalid transition key '%s'", key);
return FALSE;
}
if (strlen(local_uuid) != 36) {
crm_warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
}
if (uuid) {
*uuid = pcmk__str_copy(local_uuid);
}
if (transition_id) {
*transition_id = local_transition_id;
}
if (action_id) {
*action_id = local_action_id;
}
if (target_rc) {
*target_rc = local_target_rc;
}
return TRUE;
}
int
rsc_op_expected_rc(const lrmd_event_data_t *op)
{
int rc = 0;
if (op && op->user_data) {
decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
}
return rc;
}
gboolean
did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
{
switch (op->op_status) {
case PCMK_EXEC_CANCELLED:
case PCMK_EXEC_PENDING:
return FALSE;
case PCMK_EXEC_NOT_SUPPORTED:
case PCMK_EXEC_TIMEOUT:
case PCMK_EXEC_ERROR:
case PCMK_EXEC_NOT_CONNECTED:
case PCMK_EXEC_NO_FENCE_DEVICE:
case PCMK_EXEC_NO_SECRETS:
case PCMK_EXEC_INVALID:
return TRUE;
default:
if (target_rc != op->rc) {
return TRUE;
}
}
return FALSE;
}
/*!
* \brief Create a CIB XML element for an operation
*
* \param[in,out] parent If not NULL, make new XML node a child of this
* \param[in] prefix Generate an ID using this prefix
* \param[in] task Operation task to set
* \param[in] interval_spec Operation interval to set
* \param[in] timeout If not NULL, operation timeout to set
*
* \return New XML object on success, NULL otherwise
*/
xmlNode *
crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
const char *interval_spec, const char *timeout)
{
xmlNode *xml_op;
CRM_CHECK(prefix && task && interval_spec, return NULL);
xml_op = pcmk__xe_create(parent, PCMK_XE_OP);
pcmk__xe_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
crm_xml_add(xml_op, PCMK_META_INTERVAL, interval_spec);
crm_xml_add(xml_op, PCMK_XA_NAME, task);
if (timeout) {
crm_xml_add(xml_op, PCMK_META_TIMEOUT, timeout);
}
return xml_op;
}
/*!
* \brief Check whether an operation requires resource agent meta-data
*
* \param[in] rsc_class Resource agent class (or NULL to skip class check)
* \param[in] op Operation action (or NULL to skip op check)
*
* \return true if operation needs meta-data, false otherwise
* \note At least one of rsc_class and op must be specified.
*/
bool
crm_op_needs_metadata(const char *rsc_class, const char *op)
{
/* Agent metadata is used to determine whether an agent reload is possible,
* so if this op is not relevant to that feature, we don't need metadata.
*/
CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
if ((rsc_class != NULL)
&& !pcmk_is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
// Metadata is needed only for resource classes that use parameters
return false;
}
if (op == NULL) {
return true;
}
// Metadata is needed only for these actions
return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR,
PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE,
PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM,
PCMK_ACTION_NOTIFY, NULL);
}
/*!
* \internal
* \brief Check whether an action name is for a fencing action
*
* \param[in] action Action name to check
*
* \return \c true if \p action is \c PCMK_ACTION_OFF, \c PCMK_ACTION_REBOOT,
* or \c PCMK__ACTION_POWEROFF, otherwise \c false
*/
bool
pcmk__is_fencing_action(const char *action)
{
return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT,
PCMK__ACTION_POWEROFF, NULL);
}
diff --git a/lib/pacemaker/pcmk_sched_actions.c b/lib/pacemaker/pcmk_sched_actions.c
index 91cd9da420..f704bf459a 100644
--- a/lib/pacemaker/pcmk_sched_actions.c
+++ b/lib/pacemaker/pcmk_sched_actions.c
@@ -1,1944 +1,1944 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/param.h>
#include <glib.h>
#include <crm/lrmd_internal.h>
#include <crm/common/scheduler_internal.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
/*!
* \internal
* \brief Get the action flags relevant to ordering constraints
*
* \param[in,out] 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 uint32_t
action_flags_for_ordering(pcmk_action_t *action, const pcmk_node_t *node)
{
bool runnable = false;
uint32_t 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->private->cmds->action_flags(action, NULL);
if ((node == NULL) || !pcmk__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, pcmk_action_runnable);
// Then recheck the resource method with the node
flags = action->rsc->private->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, pcmk_action_runnable)) {
pcmk__set_raw_action_flags(flags, action->rsc->id,
pcmk_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,
const pcmk_resource_t *first_rsc)
{
guint interval_ms = 0;
char *uuid = NULL;
char *rid = NULL;
char *first_task_str = NULL;
- enum action_tasks first_task = pcmk_action_unspecified;
- enum action_tasks remapped_task = pcmk_action_unspecified;
+ enum pcmk__action_type first_task = pcmk__action_unspecified;
+ enum pcmk__action_type remapped_task = pcmk__action_unspecified;
// Only non-notify actions for collective resources need remapping
if ((strstr(first_uuid, PCMK_ACTION_NOTIFY) != NULL)
|| (first_rsc->private->variant < pcmk__rsc_variant_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 = pcmk__parse_action(first_task_str);
switch (first_task) {
- case pcmk_action_stop:
- case pcmk_action_start:
- case pcmk_action_notify:
- case pcmk_action_promote:
- case pcmk_action_demote:
+ case pcmk__action_stop:
+ case pcmk__action_start:
+ case pcmk__action_notify:
+ case pcmk__action_promote:
+ case pcmk__action_demote:
remapped_task = first_task + 1;
break;
- case pcmk_action_stopped:
- case pcmk_action_started:
- case pcmk_action_notified:
- case pcmk_action_promoted:
- case pcmk_action_demoted:
+ case pcmk__action_stopped:
+ case pcmk__action_started:
+ case pcmk__action_notified:
+ case pcmk__action_promoted:
+ case pcmk__action_demoted:
remapped_task = first_task;
break;
- case pcmk_action_monitor:
- case pcmk_action_shutdown:
- case pcmk_action_fence:
+ case pcmk__action_monitor:
+ case pcmk__action_shutdown:
+ case pcmk__action_fence:
break;
default:
crm_err("Unknown action '%s' in ordering", first_task_str);
break;
}
- if (remapped_task != pcmk_action_unspecified) {
+ if (remapped_task != pcmk__action_unspecified) {
/* If a clone or bundle has notifications enabled, the ordering will be
* relative to when notifications have been sent for the remapped task.
*/
if (pcmk_is_set(first_rsc->flags, pcmk__rsc_notify)
&& (pcmk__is_clone(first_rsc) || pcmk__is_bundled(first_rsc))) {
uuid = pcmk__notify_key(rid, "confirmed-post",
pcmk__action_text(remapped_task));
} else {
uuid = pcmk__op_key(rid, pcmk__action_text(remapped_task), 0);
}
pcmk__rsc_trace(first_rsc,
"Remapped action UUID %s to %s for ordering purposes",
first_uuid, uuid);
}
done:
free(first_task_str);
free(rid);
return (uuid != NULL)? uuid : pcmk__str_copy(first_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 pcmk_action_t *
action_for_ordering(pcmk_action_t *action)
{
pcmk_action_t *result = action;
pcmk_resource_t *rsc = action->rsc;
if (rsc == NULL) {
return result;
}
if ((rsc->private->variant >= pcmk__rsc_variant_group)
&& (action->uuid != NULL)) {
char *uuid = action_uuid_for_ordering(action->uuid, rsc);
result = find_first_action(rsc->private->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 Wrapper for update_ordered_actions() method for readability
*
* \param[in,out] rsc Resource to call method for
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this
* node (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates
* (may include pcmk_action_optional to affect only
* mandatory actions, and pe_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
static inline uint32_t
update(pcmk_resource_t *rsc, pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags, uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler)
{
return rsc->private->cmds->update_ordered_actions(first, then, node, flags,
filter, type, scheduler);
}
/*!
* \internal
* \brief Update flags for ordering's actions appropriately for ordering's flags
*
* \param[in,out] first First action in an ordering
* \param[in,out] 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,out] order Action wrapper for \p first in ordering
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags
*/
static uint32_t
update_action_for_ordering_flags(pcmk_action_t *first, pcmk_action_t *then,
uint32_t first_flags, uint32_t then_flags,
pcmk__related_action_t *order,
pcmk_scheduler_t *scheduler)
{
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.
*/
pcmk_node_t *node = then->node;
if (pcmk_is_set(order->type, pcmk__ar_first_implies_same_node_then)) {
/* 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
* pcmk__ar_first_implies_then.
*/
pcmk__clear_relation_flags(order->type,
pcmk__ar_first_implies_same_node_then);
pcmk__set_relation_flags(order->type, pcmk__ar_first_implies_then);
node = first->node;
pcmk__rsc_trace(then->rsc,
"%s then %s: mapped "
"pcmk__ar_first_implies_same_node_then to "
"pcmk__ar_first_implies_then on %s",
first->uuid, then->uuid, pcmk__node_name(node));
}
if (pcmk_is_set(order->type, pcmk__ar_first_implies_then)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node,
first_flags & pcmk_action_optional,
pcmk_action_optional, pcmk__ar_first_implies_then,
scheduler);
} else if (!pcmk_is_set(first_flags, pcmk_action_optional)
&& pcmk_is_set(then->flags, pcmk_action_optional)) {
pcmk__clear_action_flags(then, pcmk_action_optional);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_first_implies_then",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_intermediate_stop)
&& (then->rsc != NULL)) {
enum pe_action_flags restart = pcmk_action_optional
|pcmk_action_runnable;
changed |= update(then->rsc, first, then, node, first_flags, restart,
pcmk__ar_intermediate_stop, scheduler);
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_intermediate_stop",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_then_implies_first)) {
if (first->rsc != NULL) {
changed |= update(first->rsc, first, then, node, first_flags,
pcmk_action_optional, pcmk__ar_then_implies_first,
scheduler);
} else if (!pcmk_is_set(first_flags, pcmk_action_optional)
&& pcmk_is_set(first->flags, pcmk_action_runnable)) {
pcmk__clear_action_flags(first, pcmk_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_then_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_promoted_then_implies_first)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node,
first_flags & pcmk_action_optional,
pcmk_action_optional,
pcmk__ar_promoted_then_implies_first, scheduler);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after "
"pcmk__ar_promoted_then_implies_first",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_min_runnable)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_runnable, pcmk__ar_min_runnable,
scheduler);
} else if (pcmk_is_set(first_flags, pcmk_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, pcmk_action_runnable)) {
pcmk__set_action_flags(then, pcmk_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
}
pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_min_runnable",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_nested_remote_probe)
&& (then->rsc != NULL)) {
if (!pcmk_is_set(first_flags, pcmk_action_runnable)
&& (first->rsc != NULL)
&& (first->rsc->private->active_nodes != NULL)) {
pcmk__rsc_trace(then->rsc,
"%s then %s: ignoring because first is stopping",
first->uuid, then->uuid);
order->type = (enum pe_ordering) pcmk__ar_none;
} else {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_runnable,
pcmk__ar_unrunnable_first_blocks, scheduler);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_nested_remote_probe",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_unrunnable_first_blocks)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_runnable,
pcmk__ar_unrunnable_first_blocks, scheduler);
} else if (!pcmk_is_set(first_flags, pcmk_action_runnable)
&& pcmk_is_set(then->flags, pcmk_action_runnable)) {
pcmk__clear_action_flags(then, pcmk_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_unrunnable_first_blocks",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_unmigratable_then_blocks)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_optional,
pcmk__ar_unmigratable_then_blocks, scheduler);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after "
"pcmk__ar_unmigratable_then_blocks",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_first_else_then)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_optional, pcmk__ar_first_else_then,
scheduler);
}
pcmk__rsc_trace(then->rsc,
"%s then %s: %s after pcmk__ar_first_else_then",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_ordered)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_runnable, pcmk__ar_ordered,
scheduler);
}
pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_ordered",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(order->type, pcmk__ar_asymmetric)) {
if (then->rsc != NULL) {
changed |= update(then->rsc, first, then, node, first_flags,
pcmk_action_runnable, pcmk__ar_asymmetric,
scheduler);
}
pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_asymmetric",
first->uuid, then->uuid,
(changed? "changed" : "unchanged"));
}
if (pcmk_is_set(first->flags, pcmk_action_runnable)
&& pcmk_is_set(order->type, pcmk__ar_first_implies_then_graphed)
&& !pcmk_is_set(first_flags, pcmk_action_optional)) {
pcmk__rsc_trace(then->rsc, "%s will be in graph because %s is required",
then->uuid, first->uuid);
pcmk__set_action_flags(then, pcmk_action_always_in_graph);
// Don't bother marking 'then' as changed just for this
}
if (pcmk_is_set(order->type, pcmk__ar_then_implies_first_graphed)
&& !pcmk_is_set(then_flags, pcmk_action_optional)) {
pcmk__rsc_trace(then->rsc, "%s will be in graph because %s is required",
first->uuid, then->uuid);
pcmk__set_action_flags(first, pcmk_action_always_in_graph);
// Don't bother marking 'first' as changed just for this
}
if (pcmk_any_flags_set(order->type, pcmk__ar_first_implies_then
|pcmk__ar_then_implies_first
|pcmk__ar_intermediate_stop)
&& (first->rsc != NULL)
&& !pcmk_is_set(first->rsc->flags, pcmk__rsc_managed)
&& pcmk_is_set(first->rsc->flags, pcmk__rsc_blocked)
&& !pcmk_is_set(first->flags, pcmk_action_runnable)
&& pcmk__str_eq(first->task, PCMK_ACTION_STOP, pcmk__str_none)) {
if (pcmk_is_set(then->flags, pcmk_action_runnable)) {
pcmk__clear_action_flags(then, pcmk_action_runnable);
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
pcmk__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), pcmk_action_pseudo)? "pseudo-action" : "action")
#define action_optional_str(flags) \
(pcmk_is_set((flags), pcmk_action_optional)? "optional" : "required")
#define action_runnable_str(flags) \
(pcmk_is_set((flags), pcmk_action_runnable)? "runnable" : "unrunnable")
#define action_node_str(a) \
(((a)->node == NULL)? "no node" : (a)->node->private->name)
/*!
* \internal
* \brief Update an action's flags for all orderings where it is "then"
*
* \param[in,out] then Action to update
* \param[in,out] scheduler Scheduler data
*/
void
pcmk__update_action_for_orderings(pcmk_action_t *then,
pcmk_scheduler_t *scheduler)
{
GList *lpc = NULL;
uint32_t changed = pcmk__updated_none;
int last_flags = then->flags;
pcmk__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, pcmk_action_min_runnable)) {
/* 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
* PCMK_XA_REQUIRE_ALL=PCMK_VALUE_FALSE attribute. Treat it like
* PCMK_META_CLONE_MIN=1.
*/
then->required_runnable_before = 1;
}
/* The pcmk__ar_min_runnable clause of
* update_action_for_ordering_flags() (called below)
* will reset runnable if appropriate.
*/
pcmk__clear_action_flags(then, pcmk_action_runnable);
}
for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) {
pcmk__related_action_t *other = lpc->data;
pcmk_action_t *first = other->action;
pcmk_node_t *then_node = then->node;
pcmk_node_t *first_node = first->node;
if ((first->rsc != NULL)
&& pcmk__is_group(first->rsc)
&& pcmk__str_eq(first->task, PCMK_ACTION_START, pcmk__str_none)) {
first_node = first->rsc->private->fns->location(first->rsc, NULL,
FALSE);
if (first_node != NULL) {
pcmk__rsc_trace(first->rsc, "Found %s for 'first' %s",
pcmk__node_name(first_node), first->uuid);
}
}
if (pcmk__is_group(then->rsc)
&& pcmk__str_eq(then->task, PCMK_ACTION_START, pcmk__str_none)) {
then_node = then->rsc->private->fns->location(then->rsc, NULL,
FALSE);
if (then_node != NULL) {
pcmk__rsc_trace(then->rsc, "Found %s for 'then' %s",
pcmk__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, pcmk__ar_if_on_same_node)
&& (first_node != NULL) && (then_node != NULL)
&& !pcmk__same_node(first_node, then_node)) {
pcmk__rsc_trace(then->rsc,
"Disabled ordering %s on %s then %s on %s: "
"not same node",
other->action->uuid, pcmk__node_name(first_node),
then->uuid, pcmk__node_name(then_node));
other->type = (enum pe_ordering) pcmk__ar_none;
continue;
}
pcmk__clear_updated_flags(changed, then, pcmk__updated_first);
if ((first->rsc != NULL)
&& pcmk_is_set(other->type, pcmk__ar_then_cancels_first)
&& !pcmk_is_set(then->flags, pcmk_action_optional)) {
/* 'then' is required, so we must abandon 'first'
* (e.g. a required stop cancels any agent reload).
*/
pcmk__set_action_flags(other->action, pcmk_action_optional);
if (!strcmp(first->task, PCMK_ACTION_RELOAD_AGENT)) {
pcmk__clear_rsc_flags(first->rsc, pcmk__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) {
pcmk__rsc_trace(then->rsc, "Ordering %s after %s instead of %s",
then->uuid, first->uuid, other->action->uuid);
}
pcmk__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.
*/
uint32_t 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, scheduler);
/* '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);
pcmk__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 = (enum pe_ordering) pcmk__ar_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) {
pcmk__related_action_t *other = lpc2->data;
pcmk__update_action_for_orderings(other->action, scheduler);
}
pcmk__update_action_for_orderings(first, scheduler);
}
}
if (pcmk_is_set(then->flags, pcmk_action_min_runnable)) {
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, pcmk_action_runnable)
&& !pcmk_is_set(then->flags, pcmk_action_runnable)) {
pcmk__block_colocation_dependents(then);
}
pcmk__update_action_for_orderings(then, scheduler);
for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) {
pcmk__related_action_t *other = lpc->data;
pcmk__update_action_for_orderings(other->action, scheduler);
}
}
}
static inline bool
is_primitive_action(const pcmk_action_t *action)
{
return (action != NULL) && pcmk__is_primitive(action->rsc);
}
/*!
* \internal
* \brief Clear a single action flag and set reason text
*
* \param[in,out] 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))) { \
pcmk__clear_action_flags(action, flag); \
if ((action)->rsc != (reason)->rsc) { \
char *reason_text = pe__action2reason((reason), (flag)); \
pe_action_set_reason((action), reason_text, false); \
free(reason_text); \
} \
} \
} while (0)
/*!
* \internal
* \brief Update actions in an asymmetric ordering
*
* If the "first" action in an asymmetric ordering is unrunnable, make the
* "second" action unrunnable as well, if appropriate.
*
* \param[in] first 'First' action in an asymmetric ordering
* \param[in,out] then 'Then' action in an asymmetric ordering
*/
static void
handle_asymmetric_ordering(const pcmk_action_t *first, pcmk_action_t *then)
{
/* Only resource actions after an unrunnable 'first' action need updates for
* asymmetric ordering.
*/
if ((then->rsc == NULL)
|| pcmk_is_set(first->flags, pcmk_action_runnable)) {
return;
}
// Certain optional 'then' actions are unaffected by unrunnable 'first'
if (pcmk_is_set(then->flags, pcmk_action_optional)) {
enum rsc_role_e then_rsc_role;
then_rsc_role = then->rsc->private->fns->state(then->rsc, TRUE);
if ((then_rsc_role == pcmk_role_stopped)
&& pcmk__str_eq(then->task, PCMK_ACTION_STOP, pcmk__str_none)) {
/* If 'then' should stop after 'first' but is already stopped, the
* ordering is irrelevant.
*/
return;
} else if ((then_rsc_role >= pcmk_role_started)
&& pcmk__str_eq(then->task, PCMK_ACTION_START, pcmk__str_none)
&& pe__rsc_running_on_only(then->rsc, then->node)) {
/* Similarly if 'then' should start after 'first' but is already
* started on a single node.
*/
return;
}
}
// 'First' can't run, so 'then' can't either
clear_action_flag_because(then, pcmk_action_optional, first);
clear_action_flag_because(then, pcmk_action_runnable, first);
}
/*!
* \internal
* \brief Set action bits appropriately when pcmk__ar_intermediate_stop is used
*
* \param[in,out] first 'First' action in ordering
* \param[in,out] then 'Then' action in ordering
* \param[in] filter What action flags to care about
*
* \note pcmk__ar_intermediate_stop is set for "stop resource before starting
* it" and "stop later group member before stopping earlier group member"
*/
static void
handle_restart_ordering(pcmk_action_t *first, pcmk_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, pcmk_action_optional)
&& !pcmk_is_set(then->flags, pcmk_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, pcmk_action_runnable)
&& !pcmk_is_set(then->flags, pcmk_action_runnable)
&& pcmk_is_set(then->rsc->flags, pcmk__rsc_managed)
&& (first->rsc == then->rsc)) {
reason = "stop";
}
if (reason == NULL) {
return;
}
pcmk__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, pcmk_action_runnable)) {
clear_action_flag_because(first, pcmk_action_optional, then);
}
// Make 'first' required if 'then' is required
if (!pcmk_is_set(then->flags, pcmk_action_optional)) {
clear_action_flag_because(first, pcmk_action_optional, then);
}
// Make 'first' unmigratable if 'then' is unmigratable
if (!pcmk_is_set(then->flags, pcmk_action_migratable)) {
clear_action_flag_because(first, pcmk_action_migratable, then);
}
// Make 'then' unrunnable if 'first' is required but unrunnable
if (!pcmk_is_set(first->flags, pcmk_action_optional)
&& !pcmk_is_set(first->flags, pcmk_action_runnable)) {
clear_action_flag_because(then, pcmk_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.
* Effects may cascade to other orderings involving the actions as well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (ignored)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk_action_optional to affect only
* mandatory actions, and pcmk_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler)
{
uint32_t changed = pcmk__updated_none;
uint32_t then_flags = 0U;
uint32_t first_flags = 0U;
CRM_ASSERT((first != NULL) && (then != NULL) && (scheduler != NULL));
then_flags = then->flags;
first_flags = first->flags;
if (pcmk_is_set(type, pcmk__ar_asymmetric)) {
handle_asymmetric_ordering(first, then);
}
if (pcmk_is_set(type, pcmk__ar_then_implies_first)
&& !pcmk_is_set(then_flags, pcmk_action_optional)) {
// Then is required, and implies first should be, too
if (pcmk_is_set(filter, pcmk_action_optional)
&& !pcmk_is_set(flags, pcmk_action_optional)
&& pcmk_is_set(first_flags, pcmk_action_optional)) {
clear_action_flag_because(first, pcmk_action_optional, then);
}
if (pcmk_is_set(flags, pcmk_action_migratable)
&& !pcmk_is_set(then->flags, pcmk_action_migratable)) {
clear_action_flag_because(first, pcmk_action_migratable, then);
}
}
if (pcmk_is_set(type, pcmk__ar_promoted_then_implies_first)
&& (then->rsc != NULL)
&& (then->rsc->private->orig_role == pcmk_role_promoted)
&& pcmk_is_set(filter, pcmk_action_optional)
&& !pcmk_is_set(then->flags, pcmk_action_optional)) {
clear_action_flag_because(first, pcmk_action_optional, then);
if (pcmk_is_set(first->flags, pcmk_action_migratable)
&& !pcmk_is_set(then->flags, pcmk_action_migratable)) {
clear_action_flag_because(first, pcmk_action_migratable, then);
}
}
if (pcmk_is_set(type, pcmk__ar_unmigratable_then_blocks)
&& pcmk_is_set(filter, pcmk_action_optional)) {
if (!pcmk_all_flags_set(then->flags, pcmk_action_migratable
|pcmk_action_runnable)) {
clear_action_flag_because(first, pcmk_action_runnable, then);
}
if (!pcmk_is_set(then->flags, pcmk_action_optional)) {
clear_action_flag_because(first, pcmk_action_optional, then);
}
}
if (pcmk_is_set(type, pcmk__ar_first_else_then)
&& pcmk_is_set(filter, pcmk_action_optional)
&& !pcmk_is_set(first->flags, pcmk_action_runnable)) {
clear_action_flag_because(then, pcmk_action_migratable, first);
pcmk__clear_action_flags(then, pcmk_action_pseudo);
}
if (pcmk_is_set(type, pcmk__ar_unrunnable_first_blocks)
&& pcmk_is_set(filter, pcmk_action_runnable)
&& pcmk_is_set(then->flags, pcmk_action_runnable)
&& !pcmk_is_set(flags, pcmk_action_runnable)) {
clear_action_flag_because(then, pcmk_action_runnable, first);
clear_action_flag_because(then, pcmk_action_migratable, first);
}
if (pcmk_is_set(type, pcmk__ar_first_implies_then)
&& pcmk_is_set(filter, pcmk_action_optional)
&& pcmk_is_set(then->flags, pcmk_action_optional)
&& !pcmk_is_set(flags, pcmk_action_optional)
&& !pcmk_is_set(first->flags, pcmk_action_migratable)) {
clear_action_flag_because(then, pcmk_action_optional, first);
}
if (pcmk_is_set(type, pcmk__ar_intermediate_stop)) {
handle_restart_ordering(first, then, filter);
}
if (then_flags != then->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
pcmk__rsc_trace(then->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'first' %s (%#.6x)",
then->uuid, pcmk__node_name(then->node),
then->flags, then_flags, first->uuid, first->flags);
if ((then->rsc != NULL) && (then->rsc->private->parent != NULL)) {
// Required to handle "X_stop then X_start" for cloned groups
pcmk__update_action_for_orderings(then, scheduler);
}
}
if (first_flags != first->flags) {
pcmk__set_updated_flags(changed, first, pcmk__updated_first);
pcmk__rsc_trace(first->rsc,
"%s on %s: flags are now %#.6x (was %#.6x) "
"because of 'then' %s (%#.6x)",
first->uuid, pcmk__node_name(first->node),
first->flags, first_flags, then->uuid, then->flags);
}
return changed;
}
/*!
* \internal
* \brief Trace-log an action (optionally with its dependent actions)
*
* \param[in] pre_text If not NULL, prefix the log with this plus ": "
* \param[in] action Action to log
* \param[in] details If true, recursively log dependent actions
*/
void
pcmk__log_action(const char *pre_text, const pcmk_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, pcmk_action_pseudo)) {
if (action->node != NULL) {
node_uname = action->node->private->name;
node_uuid = action->node->private->id;
} else {
node_uname = "<none>";
}
}
switch (pcmk__parse_action(action->task)) {
- case pcmk_action_fence:
- case pcmk_action_shutdown:
+ case pcmk__action_fence:
+ case pcmk__action_shutdown:
if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
desc = "Pseudo ";
} else if (pcmk_is_set(action->flags, pcmk_action_optional)) {
desc = "Optional ";
} else if (!pcmk_is_set(action->flags, pcmk_action_runnable)) {
desc = "!!Non-Startable!! ";
} 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, pcmk_action_optional)) {
desc = "Optional ";
} else if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
desc = "Pseudo ";
} else if (!pcmk_is_set(action->flags, pcmk_action_runnable)) {
desc = "!!Non-Startable!! ";
} 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 : "<none>"),
(node_uname? "\ton " : ""), (node_uname? node_uname : ""),
(node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""),
(node_uuid? ")" : ""));
break;
}
if (details) {
const GList *iter = NULL;
const pcmk__related_action_t *other = NULL;
crm_trace("\t\t====== Preceding Actions");
for (iter = action->actions_before; iter != NULL; iter = iter->next) {
other = (const pcmk__related_action_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) {
other = (const pcmk__related_action_t *) iter->data;
pcmk__log_action("\t\t", other->action, false);
}
crm_trace("\t\t====== End");
} else {
crm_trace("\t\t(before=%d, after=%d)",
g_list_length(action->actions_before),
g_list_length(action->actions_after));
}
}
/*!
* \internal
* \brief Create a new shutdown action for a node
*
* \param[in,out] node Node being shut down
*
* \return Newly created shutdown action for \p node
*/
pcmk_action_t *
pcmk__new_shutdown_action(pcmk_node_t *node)
{
char *shutdown_id = NULL;
pcmk_action_t *shutdown_op = NULL;
CRM_ASSERT(node != NULL);
shutdown_id = crm_strdup_printf("%s-%s", PCMK_ACTION_DO_SHUTDOWN,
node->private->name);
shutdown_op = custom_action(NULL, shutdown_id, PCMK_ACTION_DO_SHUTDOWN,
node, FALSE, node->private->scheduler);
pcmk__order_stops_before_shutdown(node, shutdown_op);
pcmk__insert_meta(shutdown_op, PCMK__META_OP_NO_WAIT, PCMK_VALUE_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,out] update XML to add digest to
*/
static void
add_op_digest_to_xml(const lrmd_event_data_t *op, xmlNode *update)
{
char *digest = NULL;
xmlNode *args_xml = NULL;
if (op->params == NULL) {
return;
}
args_xml = pcmk__xe_create(NULL, PCMK_XE_PARAMETERS);
g_hash_table_foreach(op->params, hash2field, args_xml);
pcmk__filter_op_for_digest(args_xml);
digest = pcmk__digest_operation(args_xml);
crm_xml_add(update, PCMK__XA_OP_DIGEST, digest);
pcmk__xml_free(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, PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT,
NULL)) {
if (op->op_status == PCMK_EXEC_DONE) {
task = PCMK_ACTION_START;
} else {
task = PCMK_ACTION_MONITOR;
}
}
key = pcmk__op_key(op->rsc_id, task, op->interval_ms);
if (pcmk__str_eq(task, PCMK_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, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, 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 PCMK_META_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_first_child(parent, PCMK__XE_LRM_RSC_OP, PCMK_XA_ID,
op_id);
if (xml_op == NULL) {
xml_op = pcmk__xe_create(parent, PCMK__XE_LRM_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, PCMK_XA_ID, op_id);
crm_xml_add(xml_op, PCMK__XA_OPERATION_KEY, key);
crm_xml_add(xml_op, PCMK_XA_OPERATION, task);
crm_xml_add(xml_op, PCMK_XA_CRM_DEBUG_ORIGIN, origin);
crm_xml_add(xml_op, PCMK_XA_CRM_FEATURE_SET, caller_version);
crm_xml_add(xml_op, PCMK__XA_TRANSITION_KEY, op->user_data);
crm_xml_add(xml_op, PCMK__XA_TRANSITION_MAGIC, magic);
crm_xml_add(xml_op, PCMK_XA_EXIT_REASON, pcmk__s(exit_reason, ""));
crm_xml_add(xml_op, PCMK__META_ON_NODE, node); // For context during triage
crm_xml_add_int(xml_op, PCMK__XA_CALL_ID, op->call_id);
crm_xml_add_int(xml_op, PCMK__XA_RC_CODE, op->rc);
crm_xml_add_int(xml_op, PCMK__XA_OP_STATUS, op->op_status);
crm_xml_add_ms(xml_op, PCMK_META_INTERVAL, op->interval_ms);
if ((op->t_run > 0) || (op->t_rcchange > 0) || (op->exec_time > 0)
|| (op->queue_time > 0)) {
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, PCMK_XA_LAST_RC_CHANGE,
(long long) op->t_rcchange);
} else {
crm_xml_add_ll(xml_op, PCMK_XA_LAST_RC_CHANGE,
(long long) op->t_run);
}
crm_xml_add_int(xml_op, PCMK_XA_EXEC_TIME, op->exec_time);
crm_xml_add_int(xml_op, PCMK_XA_QUEUE_TIME, op->queue_time);
}
if (pcmk__str_any_of(op->op_type, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
/* Record PCMK__META_MIGRATE_SOURCE and PCMK__META_MIGRATE_TARGET always
* for migrate ops.
*/
const char *name = PCMK__META_MIGRATE_SOURCE;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
name = PCMK__META_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 PCMK_OPT_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 pcmk_action_t *action)
{
// Only resource actions taking place on resource's lock node are locked
if ((action == NULL) || (action->rsc == NULL)
|| !pcmk__same_node(action->node, action->rsc->private->lock_node)) {
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, PCMK_ACTION_STOP) != 0)) {
return false;
}
return true;
}
/* lowest to highest */
static gint
sort_action_id(gconstpointer a, gconstpointer b)
{
const pcmk__related_action_t *action_wrapper2 = a;
const pcmk__related_action_t *action_wrapper1 = 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,out] action Action whose inputs should be checked
*/
void
pcmk__deduplicate_action_inputs(pcmk_action_t *action)
{
GList *item = NULL;
GList *next = NULL;
pcmk__related_action_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) {
pcmk__related_action_t *input = 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,out] scheduler Scheduler data
*/
void
pcmk__output_actions(pcmk_scheduler_t *scheduler)
{
pcmk__output_t *out = scheduler->priv;
// Output node (non-resource) actions
for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) {
char *node_name = NULL;
char *task = NULL;
pcmk_action_t *action = (pcmk_action_t *) iter->data;
if (action->rsc != NULL) {
continue; // Resource actions will be output later
} else if (pcmk_is_set(action->flags, pcmk_action_optional)) {
continue; // This action was not scheduled
}
if (pcmk__str_eq(action->task, PCMK_ACTION_DO_SHUTDOWN,
pcmk__str_none)) {
task = strdup("Shutdown");
} else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
pcmk__str_none)) {
const char *op = g_hash_table_lookup(action->meta,
PCMK__META_STONITH_ACTION);
task = crm_strdup_printf("Fence (%s)", op);
} else {
continue; // Don't display other node action types
}
if (pcmk__is_guest_or_bundle_node(action->node)) {
const pcmk_resource_t *remote = action->node->private->remote;
node_name = crm_strdup_printf("%s (resource: %s)",
pcmk__node_name(action->node),
remote->private->launcher->id);
} else if (action->node != NULL) {
node_name = crm_strdup_printf("%s", pcmk__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 = scheduler->resources; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
rsc->private->cmds->output_actions(rsc);
}
}
/*!
* \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, PCMK_ACTION_MONITOR, PCMK_ACTION_MIGRATE_FROM,
PCMK_ACTION_PROMOTE, NULL)) {
task = PCMK_ACTION_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] scheduler Scheduler data
*
* \return true if only sanitized parameters changed, otherwise false
*/
static bool
only_sanitized_changed(const xmlNode *xml_op,
const pcmk__op_digest_t *digest_data,
const pcmk_scheduler_t *scheduler)
{
const char *digest_secure = NULL;
if (!pcmk_is_set(scheduler->flags, pcmk_sched_sanitized)) {
// The scheduler is not being run as a simulation
return false;
}
digest_secure = crm_element_value(xml_op, PCMK__XA_OP_SECURE_DIGEST);
return (digest_data->rc != pcmk__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,out] 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,out] node Node where resource should be restarted
*/
static void
force_restart(pcmk_resource_t *rsc, const char *task, guint interval_ms,
pcmk_node_t *node)
{
char *key = pcmk__op_key(rsc->id, task, interval_ms);
pcmk_action_t *required = custom_action(rsc, key, task, NULL, FALSE,
rsc->private->scheduler);
pe_action_set_reason(required, "resource definition change", true);
trigger_unfencing(rsc, node, "Device parameters changed", NULL,
rsc->private->scheduler);
}
/*!
* \internal
* \brief Schedule a reload of a resource on a node
*
* \param[in,out] data Resource to reload
* \param[in] user_data Where resource should be reloaded
*/
static void
schedule_reload(gpointer data, gpointer user_data)
{
pcmk_resource_t *rsc = data;
const pcmk_node_t *node = user_data;
pcmk_action_t *reload = NULL;
// For collective resources, just call recursively for children
if (rsc->private->variant > pcmk__rsc_variant_primitive) {
g_list_foreach(rsc->private->children, schedule_reload, user_data);
return;
}
// Skip the reload in certain situations
if ((node == NULL)
|| !pcmk_is_set(rsc->flags, pcmk__rsc_managed)
|| pcmk_is_set(rsc->flags, pcmk__rsc_failed)) {
pcmk__rsc_trace(rsc, "Skip reload of %s:%s%s %s",
rsc->id,
pcmk_is_set(rsc->flags, pcmk__rsc_managed)? "" : " unmanaged",
pcmk_is_set(rsc->flags, pcmk__rsc_failed)? " failed" : "",
(node == NULL)? "inactive" : node->private->name);
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, pcmk__rsc_start_pending)) {
pcmk__rsc_trace(rsc,
"%s: preventing agent reload because start pending",
rsc->id);
custom_action(rsc, stop_key(rsc), PCMK_ACTION_STOP, node, FALSE,
rsc->private->scheduler);
return;
}
// Schedule the reload
pcmk__set_rsc_flags(rsc, pcmk__rsc_reload);
reload = custom_action(rsc, reload_key(rsc), PCMK_ACTION_RELOAD_AGENT, node,
FALSE, rsc->private->scheduler);
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,
pcmk__ar_ordered|pcmk__ar_then_cancels_first,
rsc->private->scheduler);
pcmk__new_ordering(NULL, NULL, reload, rsc, demote_key(rsc), NULL,
pcmk__ar_ordered|pcmk__ar_then_cancels_first,
rsc->private->scheduler);
}
/*!
* \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,out] rsc Resource that action is for
* \param[in,out] 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(pcmk_resource_t *rsc, pcmk_node_t *node,
const xmlNode *xml_op)
{
guint interval_ms = 0;
const char *task = NULL;
const pcmk__op_digest_t *digest_data = NULL;
CRM_CHECK((rsc != NULL) && (node != NULL) && (xml_op != NULL),
return false);
task = crm_element_value(xml_op, PCMK_XA_OPERATION);
CRM_CHECK(task != NULL, return false);
crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms);
// If this is a recurring action, check whether it has been orphaned
if (interval_ms > 0) {
if (pcmk__find_action_config(rsc, task, interval_ms, false) != NULL) {
pcmk__rsc_trace(rsc,
"%s-interval %s for %s on %s is in configuration",
pcmk__readable_interval(interval_ms), task, rsc->id,
pcmk__node_name(node));
} else if (pcmk_is_set(rsc->private->scheduler->flags,
pcmk_sched_cancel_removed_actions)) {
pcmk__schedule_cancel(rsc,
crm_element_value(xml_op, PCMK__XA_CALL_ID),
task, interval_ms, node, "orphan");
return true;
} else {
pcmk__rsc_debug(rsc, "%s-interval %s for %s on %s is orphaned",
pcmk__readable_interval(interval_ms), task, rsc->id,
pcmk__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,
pcmk__node_name(node));
task = task_for_digest(task, interval_ms);
digest_data = rsc_action_digest_cmp(rsc, xml_op, node,
rsc->private->scheduler);
if (only_sanitized_changed(xml_op, digest_data, rsc->private->scheduler)) {
if (!pcmk__is_daemon && (rsc->private->scheduler->priv != NULL)) {
pcmk__output_t *out = rsc->private->scheduler->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,
pcmk__node_name(node),
crm_element_value(xml_op, PCMK__XA_TRANSITION_MAGIC));
}
return false;
}
switch (digest_data->rc) {
case pcmk__digest_restart:
crm_log_xml_debug(digest_data->params_restart, "params:restart");
force_restart(rsc, task, interval_ms, node);
return true;
case pcmk__digest_unknown:
case pcmk__digest_mismatch:
// Changes that can potentially be handled by an agent reload
if (interval_ms > 0) {
/* Recurring actions aren't reloaded per se, they are just
* re-scheduled so the next run uses the new parameters.
* The old instance will be cancelled automatically.
*/
crm_log_xml_debug(digest_data->params_all, "params:reschedule");
pcmk__reschedule_recurring(rsc, task, interval_ms, node);
} else if (crm_element_value(xml_op,
PCMK__XA_OP_RESTART_DIGEST) != NULL) {
// Agent supports reload, so use it
trigger_unfencing(rsc, node,
"Device parameters changed (reload)", NULL,
rsc->private->scheduler);
crm_log_xml_debug(digest_data->params_all, "params:reload");
schedule_reload((gpointer) rsc, (gpointer) node);
} else {
pcmk__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_entry Resource's \c PCMK__XE_LRM_RSC_OP 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(const xmlNode *rsc_entry, int *start_index, int *stop_index)
{
GList *ops = NULL;
for (xmlNode *rsc_op = pcmk__xe_first_child(rsc_entry, PCMK__XE_LRM_RSC_OP,
NULL, NULL);
rsc_op != NULL; rsc_op = pcmk__xe_next_same(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 \c PCMK__XE_LRM_RSC_OP status XML
* \param[in,out] rsc Resource whose history is being processed
* \param[in,out] node Node whose history is being processed
*/
static void
process_rsc_history(const xmlNode *rsc_entry, pcmk_resource_t *rsc,
pcmk_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, pcmk__rsc_removed)) {
if (pcmk__is_anonymous_clone(pe__const_top_resource(rsc, false))) {
pcmk__rsc_trace(rsc,
"Skipping configuration check "
"for orphaned clone instance %s",
rsc->id);
} else {
pcmk__rsc_trace(rsc,
"Skipping configuration check and scheduling "
"clean-up for orphaned resource %s", rsc->id);
pcmk__schedule_cleanup(rsc, node, false);
}
return;
}
if (pe_find_node_id(rsc->private->active_nodes,
node->private->id) == NULL) {
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, false)) {
pcmk__schedule_cleanup(rsc, node, false);
}
pcmk__rsc_trace(rsc,
"Skipping configuration check for %s "
"because no longer active on %s",
rsc->id, pcmk__node_name(node));
return;
}
pcmk__rsc_trace(rsc, "Checking for configuration changes for %s on %s",
rsc->id, pcmk__node_name(node));
if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, true)) {
pcmk__schedule_cleanup(rsc, node, false);
}
sorted_op_list = rsc_history_as_list(rsc_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, PCMK_XA_OPERATION);
crm_element_value_ms(rsc_op, PCMK_META_INTERVAL, &interval_ms);
if ((interval_ms > 0)
&& (pcmk_is_set(rsc->flags, pcmk__rsc_maintenance)
|| node->details->maintenance)) {
// Maintenance mode cancels recurring operations
pcmk__schedule_cancel(rsc,
crm_element_value(rsc_op, PCMK__XA_CALL_ID),
task, interval_ms, node, "maintenance mode");
} else if ((interval_ms > 0)
|| pcmk__strcase_any_of(task, PCMK_ACTION_MONITOR,
PCMK_ACTION_START,
PCMK_ACTION_PROMOTE,
PCMK_ACTION_MIGRATE_FROM, 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)) {
/* We haven't assigned 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, pcmk__check_active,
rsc->private->scheduler);
} else if (pcmk__check_action_config(rsc, node, rsc_op)
&& (pe_get_failcount(node, rsc, NULL, pcmk__fc_effective,
NULL) != 0)) {
pe__clear_failcount(rsc, node, "action definition changed",
rsc->private->scheduler);
}
}
}
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,out] node Node whose history is being processed
* \param[in] lrm_rscs Node's \c PCMK__XE_LRM_RESOURCES from CIB status XML
*/
static void
process_node_history(pcmk_node_t *node, const xmlNode *lrm_rscs)
{
crm_trace("Processing node history for %s", pcmk__node_name(node));
for (const xmlNode *rsc_entry = pcmk__xe_first_child(lrm_rscs,
PCMK__XE_LRM_RESOURCE,
NULL, NULL);
rsc_entry != NULL; rsc_entry = pcmk__xe_next_same(rsc_entry)) {
if (rsc_entry->children != NULL) {
GList *result = pcmk__rscs_matching_id(pcmk__xe_id(rsc_entry),
node->private->scheduler);
for (GList *iter = result; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
if (pcmk__is_primitive(rsc)) {
process_rsc_history(rsc_entry, rsc, node);
}
}
g_list_free(result);
}
}
}
// XPath to find a node's resource history
#define XPATH_NODE_HISTORY "/" PCMK_XE_CIB "/" PCMK_XE_STATUS \
"/" PCMK__XE_NODE_STATE \
"[@" PCMK_XA_UNAME "='%s']" \
"/" PCMK__XE_LRM "/" PCMK__XE_LRM_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,out] scheduler Scheduler data
*/
void
pcmk__handle_rsc_config_changes(pcmk_scheduler_t *scheduler)
{
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 = scheduler->nodes; iter != NULL; iter = iter->next) {
pcmk_node_t *node = (pcmk_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->private->name);
history = get_xpath_object(xpath, scheduler->input, LOG_NEVER);
free(xpath);
process_node_history(node, history);
}
}
}
diff --git a/lib/pacemaker/pcmk_sched_bundle.c b/lib/pacemaker/pcmk_sched_bundle.c
index adba71e447..2a8c45527b 100644
--- a/lib/pacemaker/pcmk_sched_bundle.c
+++ b/lib/pacemaker/pcmk_sched_bundle.c
@@ -1,1066 +1,1066 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdbool.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
struct assign_data {
const pcmk_node_t *prefer;
bool stop_if_fail;
};
/*!
* \internal
* \brief Assign a single bundle replica's resources (other than container)
*
* \param[in,out] replica Replica to assign
* \param[in] user_data Preferred node, if any
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
assign_replica(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk_node_t *container_host = NULL;
struct assign_data *assign_data = user_data;
const pcmk_node_t *prefer = assign_data->prefer;
bool stop_if_fail = assign_data->stop_if_fail;
const pcmk_resource_t *bundle = pe__const_top_resource(replica->container,
true);
if (replica->ip != NULL) {
pcmk__rsc_trace(bundle, "Assigning bundle %s IP %s",
bundle->id, replica->ip->id);
replica->ip->private->cmds->assign(replica->ip, prefer, stop_if_fail);
}
container_host = replica->container->private->assigned_node;
if (replica->remote != NULL) {
if (pcmk__is_pacemaker_remote_node(container_host)) {
/* REMOTE_CONTAINER_HACK: "Nested" connection resources must be on
* the same host because Pacemaker Remote only supports a single
* active connection.
*/
pcmk__new_colocation("#replica-remote-with-host-remote", NULL,
PCMK_SCORE_INFINITY, replica->remote,
container_host->private->remote, NULL,
NULL, pcmk__coloc_influence);
}
pcmk__rsc_trace(bundle, "Assigning bundle %s connection %s",
bundle->id, replica->remote->id);
replica->remote->private->cmds->assign(replica->remote, prefer,
stop_if_fail);
}
if (replica->child != NULL) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, replica->child->private->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
if (!pcmk__same_node(node, replica->node)) {
node->assign->score = -PCMK_SCORE_INFINITY;
} else if (!pcmk__threshold_reached(replica->child, node, NULL)) {
node->assign->score = PCMK_SCORE_INFINITY;
}
}
pcmk__set_rsc_flags(replica->child->private->parent,
pcmk__rsc_assigning);
pcmk__rsc_trace(bundle, "Assigning bundle %s replica child %s",
bundle->id, replica->child->id);
replica->child->private->cmds->assign(replica->child, replica->node,
stop_if_fail);
pcmk__clear_rsc_flags(replica->child->private->parent,
pcmk__rsc_assigning);
}
return true;
}
/*!
* \internal
* \brief Assign a bundle resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and a primitive descendant of \p rsc
* can't be assigned to a node, set the
* descendant's next role to stopped and update
* existing actions
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__bundle_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
GList *containers = NULL;
pcmk_resource_t *bundled_resource = NULL;
struct assign_data assign_data = { prefer, stop_if_fail };
CRM_ASSERT(pcmk__is_bundle(rsc));
pcmk__rsc_trace(rsc, "Assigning bundle %s", rsc->id);
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
pe__show_node_scores(!pcmk_is_set(rsc->private->scheduler->flags,
pcmk_sched_output_scores),
rsc, __func__, rsc->private->allowed_nodes,
rsc->private->scheduler);
// Assign all containers first, so we know what nodes the bundle will be on
containers = g_list_sort(pe__bundle_containers(rsc), pcmk__cmp_instance);
pcmk__assign_instances(rsc, containers, pe__bundle_max(rsc),
rsc->private->fns->max_per_node(rsc));
g_list_free(containers);
// Then assign remaining replica resources
pe__foreach_bundle_replica(rsc, assign_replica, (void *) &assign_data);
// Finally, assign the bundled resources to each bundle node
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, bundled_resource->private->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) {
if (pe__node_is_bundle_instance(rsc, node)) {
node->assign->score = 0;
} else {
node->assign->score = -PCMK_SCORE_INFINITY;
}
}
bundled_resource->private->cmds->assign(bundled_resource, prefer,
stop_if_fail);
}
pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned);
return NULL;
}
/*!
* \internal
* \brief Create actions for a bundle replica's resources (other than child)
*
* \param[in,out] replica Replica to create actions for
* \param[in] user_data Unused
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
create_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
{
if (replica->ip != NULL) {
replica->ip->private->cmds->create_actions(replica->ip);
}
if (replica->container != NULL) {
replica->container->private->cmds->create_actions(replica->container);
}
if (replica->remote != NULL) {
replica->remote->private->cmds->create_actions(replica->remote);
}
return true;
}
/*!
* \internal
* \brief Create all actions needed for a given bundle resource
*
* \param[in,out] rsc Bundle resource to create actions for
*/
void
pcmk__bundle_create_actions(pcmk_resource_t *rsc)
{
pcmk_action_t *action = NULL;
GList *containers = NULL;
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, create_replica_actions, NULL);
containers = pe__bundle_containers(rsc);
pcmk__create_instance_actions(rsc, containers);
g_list_free(containers);
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
bundled_resource->private->cmds->create_actions(bundled_resource);
if (pcmk_is_set(bundled_resource->flags, pcmk__rsc_promotable)) {
pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTE, true, true);
action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_PROMOTED,
true, true);
action->priority = PCMK_SCORE_INFINITY;
pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTE, true, true);
action = pe__new_rsc_pseudo_action(rsc, PCMK_ACTION_DEMOTED,
true, true);
action->priority = PCMK_SCORE_INFINITY;
}
}
}
/*!
* \internal
* \brief Create internal constraints for a bundle replica's resources
*
* \param[in,out] replica Replica to create internal constraints for
* \param[in,out] user_data Replica's parent bundle
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
replica_internal_constraints(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk_resource_t *bundle = user_data;
replica->container->private->cmds->internal_constraints(replica->container);
// Start bundle -> start replica container
pcmk__order_starts(bundle, replica->container,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
// Stop bundle -> stop replica child and container
if (replica->child != NULL) {
pcmk__order_stops(bundle, replica->child,
pcmk__ar_then_implies_first_graphed);
}
pcmk__order_stops(bundle, replica->container,
pcmk__ar_then_implies_first_graphed);
// Start replica container -> bundle is started
pcmk__order_resource_actions(replica->container, PCMK_ACTION_START, bundle,
PCMK_ACTION_RUNNING,
pcmk__ar_first_implies_then_graphed);
// Stop replica container -> bundle is stopped
pcmk__order_resource_actions(replica->container, PCMK_ACTION_STOP, bundle,
PCMK_ACTION_STOPPED,
pcmk__ar_first_implies_then_graphed);
if (replica->ip != NULL) {
replica->ip->private->cmds->internal_constraints(replica->ip);
// Replica IP address -> replica container (symmetric)
pcmk__order_starts(replica->ip, replica->container,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_guest_allowed);
pcmk__order_stops(replica->container, replica->ip,
pcmk__ar_then_implies_first|pcmk__ar_guest_allowed);
pcmk__new_colocation("#ip-with-container", NULL, PCMK_SCORE_INFINITY,
replica->ip, replica->container, NULL, NULL,
pcmk__coloc_influence);
}
if (replica->remote != NULL) {
/* This handles ordering and colocating remote relative to container
* (via "#resource-with-container"). Since IP is also ordered and
* colocated relative to the container, we don't need to do anything
* explicit here with IP.
*/
replica->remote->private->cmds->internal_constraints(replica->remote);
}
if (replica->child != NULL) {
CRM_ASSERT(replica->remote != NULL);
// "Start remote then child" is implicit in scheduler's remote logic
}
return true;
}
/*!
* \internal
* \brief Create implicit constraints needed for a bundle resource
*
* \param[in,out] rsc Bundle resource to create implicit constraints for
*/
void
pcmk__bundle_internal_constraints(pcmk_resource_t *rsc)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, replica_internal_constraints, rsc);
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource == NULL) {
return;
}
// Start bundle -> start bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_START, bundled_resource,
PCMK_ACTION_START,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is started -> bundle is started
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_RUNNING,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_first_implies_then_graphed);
// Stop bundle -> stop bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP, bundled_resource,
PCMK_ACTION_STOP,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is stopped -> bundle is stopped
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_STOPPED,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_first_implies_then_graphed);
bundled_resource->private->cmds->internal_constraints(bundled_resource);
if (!pcmk_is_set(bundled_resource->flags, pcmk__rsc_promotable)) {
return;
}
pcmk__promotable_restart_ordering(rsc);
// Demote bundle -> demote bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_DEMOTE, bundled_resource,
PCMK_ACTION_DEMOTE,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is demoted -> bundle is demoted
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_DEMOTED,
rsc, PCMK_ACTION_DEMOTED,
pcmk__ar_first_implies_then_graphed);
// Promote bundle -> promote bundled clone
pcmk__order_resource_actions(rsc, PCMK_ACTION_PROMOTE,
bundled_resource, PCMK_ACTION_PROMOTE,
pcmk__ar_then_implies_first_graphed);
// Bundled clone is promoted -> bundle is promoted
pcmk__order_resource_actions(bundled_resource, PCMK_ACTION_PROMOTED,
rsc, PCMK_ACTION_PROMOTED,
pcmk__ar_first_implies_then_graphed);
}
struct match_data {
const pcmk_node_t *node; // Node to compare against replica
pcmk_resource_t *container; // Replica container corresponding to node
};
/*!
* \internal
* \brief Check whether a replica container is assigned to a given node
*
* \param[in] replica Replica to check
* \param[in,out] user_data struct match_data with node to compare against
*
* \return true if the replica does not match (to indicate further replicas
* should be processed), otherwise false
*/
static bool
match_replica_container(const pcmk__bundle_replica_t *replica, void *user_data)
{
struct match_data *match_data = user_data;
if (pcmk__instance_matches(replica->container, match_data->node,
pcmk_role_unknown, false)) {
match_data->container = replica->container;
return false; // Match found, don't bother searching further replicas
}
return true; // No match, keep searching
}
/*!
* \internal
* \brief Get the host to which a bundle node is assigned
*
* \param[in] node Possible bundle node to check
*
* \return Node to which the container for \p node is assigned if \p node is a
* bundle node, otherwise \p node itself
*/
static const pcmk_node_t *
get_bundle_node_host(const pcmk_node_t *node)
{
if (pcmk__is_bundle_node(node)) {
const pcmk_resource_t *container = NULL;
container = node->private->remote->private->launcher;
return container->private->fns->location(container, NULL, 0);
}
return node;
}
/*!
* \internal
* \brief Find a bundle container compatible with a dependent resource
*
* \param[in] dependent Dependent resource in colocation with bundle
* \param[in] bundle Bundle that \p dependent is colocated with
*
* \return A container from \p bundle assigned to the same node as \p dependent
* if assigned, otherwise assigned to any of dependent's allowed nodes,
* otherwise NULL.
*/
static pcmk_resource_t *
compatible_container(const pcmk_resource_t *dependent,
const pcmk_resource_t *bundle)
{
GList *scratch = NULL;
struct match_data match_data = { NULL, NULL };
// If dependent is assigned, only check there
match_data.node = dependent->private->fns->location(dependent, NULL, 0);
match_data.node = get_bundle_node_host(match_data.node);
if (match_data.node != NULL) {
pe__foreach_const_bundle_replica(bundle, match_replica_container,
&match_data);
return match_data.container;
}
// Otherwise, check for any of the dependent's allowed nodes
scratch = g_hash_table_get_values(dependent->private->allowed_nodes);
scratch = pcmk__sort_nodes(scratch, NULL);
for (const GList *iter = scratch; iter != NULL; iter = iter->next) {
match_data.node = iter->data;
match_data.node = get_bundle_node_host(match_data.node);
if (match_data.node == NULL) {
continue;
}
pe__foreach_const_bundle_replica(bundle, match_replica_container,
&match_data);
if (match_data.container != NULL) {
break;
}
}
g_list_free(scratch);
return match_data.container;
}
struct coloc_data {
const pcmk__colocation_t *colocation;
pcmk_resource_t *dependent;
GList *container_hosts;
};
/*!
* \internal
* \brief Apply a colocation score to replica node scores or resource priority
*
* \param[in] replica Replica of primary bundle resource in colocation
* \param[in,out] user_data struct coloc_data for colocation being applied
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
replica_apply_coloc_score(const pcmk__bundle_replica_t *replica,
void *user_data)
{
struct coloc_data *coloc_data = user_data;
pcmk_node_t *chosen = NULL;
pcmk_resource_t *container = replica->container;
if (coloc_data->colocation->score < PCMK_SCORE_INFINITY) {
container->private->cmds->apply_coloc_score(coloc_data->dependent,
container,
coloc_data->colocation,
false);
return true;
}
chosen = container->private->fns->location(container, NULL, 0);
if ((chosen == NULL)
|| is_set_recursive(container, pcmk__rsc_blocked, true)) {
return true;
}
if ((coloc_data->colocation->primary_role >= pcmk_role_promoted)
&& ((replica->child == NULL)
|| (replica->child->private->next_role < pcmk_role_promoted))) {
return true;
}
pcmk__rsc_trace(pe__const_top_resource(container, true),
"Allowing mandatory colocation %s using %s @%d",
coloc_data->colocation->id, pcmk__node_name(chosen),
chosen->assign->score);
coloc_data->container_hosts = g_list_prepend(coloc_data->container_hosts,
chosen);
return true;
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
*/
void
pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
struct coloc_data coloc_data = { colocation, dependent, NULL };
/* This should never be called for the bundle itself as a dependent.
* Instead, we add its colocation constraints to its containers and bundled
* primitive and call the apply_coloc_score() method for them as dependents.
*/
CRM_ASSERT(pcmk__is_bundle(primary) && pcmk__is_primitive(dependent)
&& (colocation != NULL) && !for_dependent);
if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
pcmk__rsc_trace(primary,
"Skipping applying colocation %s "
"because %s is still provisional",
colocation->id, primary->id);
return;
}
pcmk__rsc_trace(primary, "Applying colocation %s (%s with %s at %s)",
colocation->id, dependent->id, primary->id,
pcmk_readable_score(colocation->score));
/* If the constraint dependent is a clone or bundle, "dependent" here is one
* of its instances. Look for a compatible instance of this bundle.
*/
if (colocation->dependent->private->variant > pcmk__rsc_variant_group) {
const pcmk_resource_t *primary_container = NULL;
primary_container = compatible_container(dependent, primary);
if (primary_container != NULL) { // Success, we found one
pcmk__rsc_debug(primary, "Pairing %s with %s",
dependent->id, primary_container->id);
dependent->private->cmds->apply_coloc_score(dependent,
primary_container,
colocation, true);
} else if (colocation->score >= PCMK_SCORE_INFINITY) {
// Failure, and it's fatal
crm_notice("%s cannot run because there is no compatible "
"instance of %s to colocate with",
dependent->id, primary->id);
pcmk__assign_resource(dependent, NULL, true, true);
} else { // Failure, but we can ignore it
pcmk__rsc_debug(primary,
"%s cannot be colocated with any instance of %s",
dependent->id, primary->id);
}
return;
}
pe__foreach_const_bundle_replica(primary, replica_apply_coloc_score,
&coloc_data);
if (colocation->score >= PCMK_SCORE_INFINITY) {
pcmk__colocation_intersect_nodes(dependent, primary, colocation,
coloc_data.container_hosts, false);
}
g_list_free(coloc_data.container_hosts);
}
// Bundle implementation of pcmk__assignment_methods_t:with_this_colocations()
void
pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *bundled_rsc = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc) && (orig_rsc != NULL) && (list != NULL));
// The bundle itself and its containers always get its colocations
if ((orig_rsc == rsc)
|| pcmk_is_set(orig_rsc->flags, pcmk__rsc_replica_container)) {
pcmk__add_with_this_list(list, rsc->private->with_this_colocations,
orig_rsc);
return;
}
/* The bundled resource gets the colocations if it's promotable and we've
* begun choosing roles
*/
bundled_rsc = pe__bundled_resource(rsc);
if ((bundled_rsc == NULL)
|| !pcmk_is_set(bundled_rsc->flags, pcmk__rsc_promotable)
|| (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
return;
}
if (orig_rsc == bundled_rsc) {
if (pe__clone_flag_is_set(orig_rsc,
pcmk__clone_promotion_constrained)) {
/* orig_rsc is the clone and we're setting roles (or have already
* done so)
*/
pcmk__add_with_this_list(list, rsc->private->with_this_colocations,
orig_rsc);
}
} else if (!pcmk_is_set(orig_rsc->flags, pcmk__rsc_unassigned)) {
/* orig_rsc is an instance and is already assigned. If something
* requests colocations for orig_rsc now, it's for setting roles.
*/
pcmk__add_with_this_list(list, rsc->private->with_this_colocations,
orig_rsc);
}
}
// Bundle implementation of pcmk__assignment_methods_t:this_with_colocations()
void
pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *bundled_rsc = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc) && (orig_rsc != NULL) && (list != NULL));
// The bundle itself and its containers always get its colocations
if ((orig_rsc == rsc)
|| pcmk_is_set(orig_rsc->flags, pcmk__rsc_replica_container)) {
pcmk__add_this_with_list(list, rsc->private->this_with_colocations,
orig_rsc);
return;
}
/* The bundled resource gets the colocations if it's promotable and we've
* begun choosing roles
*/
bundled_rsc = pe__bundled_resource(rsc);
if ((bundled_rsc == NULL)
|| !pcmk_is_set(bundled_rsc->flags, pcmk__rsc_promotable)
|| (pe__const_top_resource(orig_rsc, false) != bundled_rsc)) {
return;
}
if (orig_rsc == bundled_rsc) {
if (pe__clone_flag_is_set(orig_rsc,
pcmk__clone_promotion_constrained)) {
/* orig_rsc is the clone and we're setting roles (or have already
* done so)
*/
pcmk__add_this_with_list(list, rsc->private->this_with_colocations,
orig_rsc);
}
} else if (!pcmk_is_set(orig_rsc->flags, pcmk__rsc_unassigned)) {
/* orig_rsc is an instance and is already assigned. If something
* requests colocations for orig_rsc now, it's for setting roles.
*/
pcmk__add_this_with_list(list, rsc->private->this_with_colocations,
orig_rsc);
}
}
/*!
* \internal
* \brief Return action flags for a given bundle resource action
*
* \param[in,out] action Bundle resource action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__bundle_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
GList *containers = NULL;
uint32_t flags = 0;
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT((action != NULL) && pcmk__is_bundle(action->rsc));
bundled_resource = pe__bundled_resource(action->rsc);
if (bundled_resource != NULL) {
GList *children = bundled_resource->private->children;
// Clone actions are done on the bundled clone resource, not container
switch (get_complex_task(bundled_resource, action->task)) {
- case pcmk_action_unspecified:
- case pcmk_action_notify:
- case pcmk_action_notified:
- case pcmk_action_promote:
- case pcmk_action_promoted:
- case pcmk_action_demote:
- case pcmk_action_demoted:
+ case pcmk__action_unspecified:
+ case pcmk__action_notify:
+ case pcmk__action_notified:
+ case pcmk__action_promote:
+ case pcmk__action_promoted:
+ case pcmk__action_demote:
+ case pcmk__action_demoted:
return pcmk__collective_action_flags(action, children, node);
default:
break;
}
}
containers = pe__bundle_containers(action->rsc);
flags = pcmk__collective_action_flags(action, containers, node);
g_list_free(containers);
return flags;
}
/*!
* \internal
* \brief Apply a location constraint to a bundle replica
*
* \param[in,out] replica Replica to apply constraint to
* \param[in,out] user_data Location constraint to apply
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
apply_location_to_replica(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk__location_t *location = user_data;
replica->container->private->cmds->apply_location(replica->container,
location);
if (replica->ip != NULL) {
replica->ip->private->cmds->apply_location(replica->ip, location);
}
return true;
}
/*!
* \internal
* \brief Apply a location constraint to a bundle resource's allowed node scores
*
* \param[in,out] rsc Bundle resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void
pcmk__bundle_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT((location != NULL) && pcmk__is_bundle(rsc));
pcmk__apply_location(rsc, location);
pe__foreach_bundle_replica(rsc, apply_location_to_replica, location);
bundled_resource = pe__bundled_resource(rsc);
if ((bundled_resource != NULL)
&& ((location->role_filter == pcmk_role_unpromoted)
|| (location->role_filter == pcmk_role_promoted))) {
bundled_resource->private->cmds->apply_location(bundled_resource,
location);
bundled_resource->private->location_constraints =
g_list_prepend(bundled_resource->private->location_constraints,
location);
}
}
#define XPATH_REMOTE "//nvpair[@name='" PCMK_REMOTE_RA_ADDR "']"
/*!
* \internal
* \brief Add a bundle replica's actions to transition graph
*
* \param[in,out] replica Replica to add to graph
* \param[in] user_data Bundle that replica belongs to (for logging only)
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
add_replica_actions_to_graph(pcmk__bundle_replica_t *replica, void *user_data)
{
if ((replica->remote != NULL)
&& pe__bundle_needs_remote_name(replica->remote)) {
/* REMOTE_CONTAINER_HACK: Allow remote nodes to run containers that
* run pacemaker-remoted inside, without needing a separate IP for
* the container. This is done by configuring the inner remote's
* connection host as the magic string "#uname", then
* replacing it with the underlying host when needed.
*/
xmlNode *nvpair = get_xpath_object(XPATH_REMOTE,
replica->remote->private->xml,
LOG_ERR);
const char *calculated_addr = NULL;
// Replace the value in replica->remote->xml (if appropriate)
calculated_addr = pe__add_bundle_remote_name(replica->remote, nvpair,
PCMK_XA_VALUE);
if (calculated_addr != NULL) {
/* Since this is for the bundle as a resource, and not any
* particular action, replace the value in the default
* parameters (not evaluated for node). create_graph_action()
* will grab it from there to replace it in node-evaluated
* parameters.
*/
GHashTable *params = NULL;
params = pe_rsc_params(replica->remote, NULL,
replica->remote->private->scheduler);
pcmk__insert_dup(params, PCMK_REMOTE_RA_ADDR, calculated_addr);
} else {
pcmk_resource_t *bundle = user_data;
/* The only way to get here is if the remote connection is
* neither currently running nor scheduled to run. That means we
* won't be doing any operations that require addr (only start
* requires it; we additionally use it to compare digests when
* unpacking status, promote, and migrate_from history, but
* that's already happened by this point).
*/
pcmk__rsc_info(bundle,
"Unable to determine address for bundle %s "
"remote connection", bundle->id);
}
}
if (replica->ip != NULL) {
replica->ip->private->cmds->add_actions_to_graph(replica->ip);
}
replica->container->private->cmds->add_actions_to_graph(replica->container);
if (replica->remote != NULL) {
replica->remote->private->cmds->add_actions_to_graph(replica->remote);
}
return true;
}
/*!
* \internal
* \brief Add a bundle resource's actions to the transition graph
*
* \param[in,out] rsc Bundle resource whose actions should be added
*/
void
pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc)
{
pcmk_resource_t *bundled_resource = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
bundled_resource = pe__bundled_resource(rsc);
if (bundled_resource != NULL) {
bundled_resource->private->cmds->add_actions_to_graph(bundled_resource);
}
pe__foreach_bundle_replica(rsc, add_replica_actions_to_graph, rsc);
}
struct probe_data {
pcmk_resource_t *bundle; // Bundle being probed
pcmk_node_t *node; // Node to create probes on
bool any_created; // Whether any probes have been created
};
/*!
* \internal
* \brief Order a bundle replica's start after another replica's probe
*
* \param[in,out] replica Replica to order start for
* \param[in,out] user_data Replica with probe to order after
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
order_replica_start_after(pcmk__bundle_replica_t *replica, void *user_data)
{
pcmk__bundle_replica_t *probed_replica = user_data;
if ((replica == probed_replica) || (replica->container == NULL)) {
return true;
}
pcmk__new_ordering(probed_replica->container,
pcmk__op_key(probed_replica->container->id,
PCMK_ACTION_MONITOR, 0),
NULL, replica->container,
pcmk__op_key(replica->container->id, PCMK_ACTION_START,
0),
NULL, pcmk__ar_ordered|pcmk__ar_if_on_same_node,
replica->container->private->scheduler);
return true;
}
/*!
* \internal
* \brief Create probes for a bundle replica's resources
*
* \param[in,out] replica Replica to create probes for
* \param[in,out] user_data struct probe_data
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
create_replica_probes(pcmk__bundle_replica_t *replica, void *user_data)
{
struct probe_data *probe_data = user_data;
pcmk_resource_t *bundle = probe_data->bundle;
if ((replica->ip != NULL)
&& replica->ip->private->cmds->create_probe(replica->ip,
probe_data->node)) {
probe_data->any_created = true;
}
if ((replica->child != NULL)
&& pcmk__same_node(probe_data->node, replica->node)
&& replica->child->private->cmds->create_probe(replica->child,
probe_data->node)) {
probe_data->any_created = true;
}
if (replica->container->private->cmds->create_probe(replica->container,
probe_data->node)) {
probe_data->any_created = true;
/* If we're limited to one replica per host (due to
* the lack of an IP range probably), then we don't
* want any of our peer containers starting until
* we've established that no other copies are already
* running.
*
* Partly this is to ensure that the maximum replicas per host is
* observed, but also to ensure that the containers
* don't fail to start because the necessary port
* mappings (which won't include an IP for uniqueness)
* are already taken
*/
if (bundle->private->fns->max_per_node(bundle) == 1) {
pe__foreach_bundle_replica(bundle, order_replica_start_after,
replica);
}
}
if ((replica->remote != NULL)
&& replica->remote->private->cmds->create_probe(replica->remote,
probe_data->node)) {
/* Do not probe the remote resource until we know where the container is
* running. This is required for REMOTE_CONTAINER_HACK to correctly
* probe remote resources.
*/
char *probe_uuid = pcmk__op_key(replica->remote->id,
PCMK_ACTION_MONITOR, 0);
pcmk_action_t *probe = NULL;
probe = find_first_action(replica->remote->private->actions, probe_uuid,
NULL, probe_data->node);
free(probe_uuid);
if (probe != NULL) {
probe_data->any_created = true;
pcmk__rsc_trace(bundle, "Ordering %s probe on %s",
replica->remote->id,
pcmk__node_name(probe_data->node));
pcmk__new_ordering(replica->container,
pcmk__op_key(replica->container->id,
PCMK_ACTION_START, 0),
NULL, replica->remote, NULL, probe,
pcmk__ar_nested_remote_probe,
bundle->private->scheduler);
}
}
return true;
}
/*!
* \internal
*
* \brief Schedule any probes needed for a bundle resource on a node
*
* \param[in,out] rsc Bundle resource to create probes for
* \param[in,out] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool
pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node)
{
struct probe_data probe_data = { rsc, node, false };
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, create_replica_probes, &probe_data);
return probe_data.any_created;
}
/*!
* \internal
* \brief Output actions for one bundle replica
*
* \param[in,out] replica Replica to output actions for
* \param[in] user_data Unused
*
* \return true (to indicate that any further replicas should be processed)
*/
static bool
output_replica_actions(pcmk__bundle_replica_t *replica, void *user_data)
{
if (replica->ip != NULL) {
replica->ip->private->cmds->output_actions(replica->ip);
}
replica->container->private->cmds->output_actions(replica->container);
if (replica->remote != NULL) {
replica->remote->private->cmds->output_actions(replica->remote);
}
if (replica->child != NULL) {
replica->child->private->cmds->output_actions(replica->child);
}
return true;
}
/*!
* \internal
* \brief Output a summary of scheduled actions for a bundle resource
*
* \param[in,out] rsc Bundle resource to output actions for
*/
void
pcmk__output_bundle_actions(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_bundle(rsc));
pe__foreach_bundle_replica(rsc, output_replica_actions, NULL);
}
// Bundle implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
pcmk_resource_t *container = NULL;
CRM_ASSERT(pcmk__is_bundle(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
/* All bundle replicas are identical, so using the utilization of the first
* is sufficient for any. Only the implicit container resource can have
* utilization values.
*/
container = pe__first_container(rsc);
if (container != NULL) {
container->private->cmds->add_utilization(container, orig_rsc, all_rscs,
utilization);
}
}
// Bundle implementation of pcmk__assignment_methods_t:shutdown_lock()
void
pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_bundle(rsc));
// Bundles currently don't support shutdown locks
}
diff --git a/lib/pacemaker/pcmk_sched_group.c b/lib/pacemaker/pcmk_sched_group.c
index 7ff8518322..ba3720a854 100644
--- a/lib/pacemaker/pcmk_sched_group.c
+++ b/lib/pacemaker/pcmk_sched_group.c
@@ -1,1000 +1,1001 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdbool.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
/*!
* \internal
* \brief Assign a group resource to a node
*
* \param[in,out] rsc Group resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and a child of \p rsc can't be
* assigned to a node, set the child's next role to
* stopped and update existing actions
*
* \return Node that \p rsc is assigned to, if assigned entirely to one node
*
* \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
* completely undo the assignment. A successful assignment can be either
* undone or left alone as final. A failed assignment has the same effect
* as calling pcmk__unassign_resource(); there are no side effects on
* roles or actions.
*/
pcmk_node_t *
pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail)
{
pcmk_node_t *first_assigned_node = NULL;
pcmk_resource_t *first_member = NULL;
CRM_ASSERT(pcmk__is_group(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return rsc->private->assigned_node; // Assignment already done
}
if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s",
rsc->id);
return NULL;
}
if (rsc->private->children == NULL) {
// No members to assign
pcmk__clear_rsc_flags(rsc, pcmk__rsc_unassigned);
return NULL;
}
pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning);
first_member = (pcmk_resource_t *) rsc->private->children->data;
rsc->private->orig_role = first_member->private->orig_role;
pe__show_node_scores(!pcmk_is_set(rsc->private->scheduler->flags,
pcmk_sched_output_scores),
rsc, __func__, rsc->private->allowed_nodes,
rsc->private->scheduler);
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
pcmk_node_t *node = NULL;
pcmk__rsc_trace(rsc, "Assigning group %s member %s",
rsc->id, member->id);
node = member->private->cmds->assign(member, prefer, stop_if_fail);
if (first_assigned_node == NULL) {
first_assigned_node = node;
}
}
pe__set_next_role(rsc, first_member->private->next_role,
"first group member");
pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned);
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
return NULL;
}
return first_assigned_node;
}
/*!
* \internal
* \brief Create a pseudo-operation for a group as an ordering point
*
* \param[in,out] group Group resource to create action for
* \param[in] action Action name
*
* \return Newly created pseudo-operation
*/
static pcmk_action_t *
create_group_pseudo_op(pcmk_resource_t *group, const char *action)
{
pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
action, NULL, TRUE,
group->private->scheduler);
pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
return op;
}
/*!
* \internal
* \brief Create all actions needed for a given group resource
*
* \param[in,out] rsc Group resource to create actions for
*/
void
pcmk__group_create_actions(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_group(rsc));
pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id);
// Create actions for individual group members
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
member->private->cmds->create_actions(member);
}
// Create pseudo-actions for group itself to serve as ordering points
create_group_pseudo_op(rsc, PCMK_ACTION_START);
create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING);
create_group_pseudo_op(rsc, PCMK_ACTION_STOP);
create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED);
if (crm_is_true(g_hash_table_lookup(rsc->private->meta,
PCMK_META_PROMOTABLE))) {
create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE);
create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED);
create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE);
create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED);
}
}
// User data for member_internal_constraints()
struct member_data {
// These could be derived from member but this avoids some function calls
bool ordered;
bool colocated;
bool promotable;
pcmk_resource_t *last_active;
pcmk_resource_t *previous_member;
};
/*!
* \internal
* \brief Create implicit constraints needed for a group member
*
* \param[in,out] data Group member to create implicit constraints for
* \param[in,out] user_data Member data (struct member_data *)
*/
static void
member_internal_constraints(gpointer data, gpointer user_data)
{
pcmk_resource_t *member = (pcmk_resource_t *) data;
struct member_data *member_data = (struct member_data *) user_data;
// For ordering demote vs demote or stop vs stop
uint32_t down_flags = pcmk__ar_then_implies_first_graphed;
// For ordering demote vs demoted or stop vs stopped
uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed;
// Create the individual member's implicit constraints
member->private->cmds->internal_constraints(member);
if (member_data->previous_member == NULL) {
// This is first member
if (member_data->ordered) {
pcmk__set_relation_flags(down_flags, pcmk__ar_ordered);
post_down_flags = pcmk__ar_first_implies_then;
}
} else if (member_data->colocated) {
uint32_t flags = pcmk__coloc_none;
if (pcmk_is_set(member->flags, pcmk__rsc_critical)) {
flags |= pcmk__coloc_influence;
}
// Colocate this member with the previous one
pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY,
member, member_data->previous_member, NULL, NULL,
flags);
}
if (member_data->promotable) {
// Demote group -> demote member -> group is demoted
pcmk__order_resource_actions(member->private->parent,
PCMK_ACTION_DEMOTE,
member, PCMK_ACTION_DEMOTE, down_flags);
pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
member->private->parent,
PCMK_ACTION_DEMOTED, post_down_flags);
// Promote group -> promote member -> group is promoted
pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE,
member->private->parent,
PCMK_ACTION_PROMOTED,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_first_implies_then
|pcmk__ar_first_implies_then_graphed);
pcmk__order_resource_actions(member->private->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_then_implies_first_graphed);
}
// Stop group -> stop member -> group is stopped
pcmk__order_stops(member->private->parent, member, down_flags);
pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
member->private->parent, PCMK_ACTION_STOPPED,
post_down_flags);
// Start group -> start member -> group is started
pcmk__order_starts(member->private->parent, member,
pcmk__ar_then_implies_first_graphed);
pcmk__order_resource_actions(member, PCMK_ACTION_START,
member->private->parent, PCMK_ACTION_RUNNING,
pcmk__ar_unrunnable_first_blocks
|pcmk__ar_first_implies_then
|pcmk__ar_first_implies_then_graphed);
if (!member_data->ordered) {
pcmk__order_starts(member->private->parent, member,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
if (member_data->promotable) {
pcmk__order_resource_actions(member->private->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks
|pcmk__ar_then_implies_first_graphed);
}
} else if (member_data->previous_member == NULL) {
pcmk__order_starts(member->private->parent, member, pcmk__ar_none);
if (member_data->promotable) {
pcmk__order_resource_actions(member->private->parent,
PCMK_ACTION_PROMOTE,
member, PCMK_ACTION_PROMOTE,
pcmk__ar_none);
}
} else {
// Order this member relative to the previous one
pcmk__order_starts(member_data->previous_member, member,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks);
pcmk__order_stops(member, member_data->previous_member,
pcmk__ar_ordered|pcmk__ar_intermediate_stop);
/* In unusual circumstances (such as adding a new member to the middle
* of a group with unmanaged later members), this member may be active
* while the previous (new) member is inactive. In this situation, the
* usual restart orderings will be irrelevant, so we need to order this
* member's stop before the previous member's start.
*/
if ((member->private->active_nodes != NULL)
&& (member_data->previous_member->private->active_nodes == NULL)) {
pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
member_data->previous_member,
PCMK_ACTION_START,
pcmk__ar_then_implies_first
|pcmk__ar_unrunnable_first_blocks);
}
if (member_data->promotable) {
pcmk__order_resource_actions(member_data->previous_member,
PCMK_ACTION_PROMOTE, member,
PCMK_ACTION_PROMOTE,
pcmk__ar_first_implies_then
|pcmk__ar_unrunnable_first_blocks);
pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
member_data->previous_member,
PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
}
}
// Make sure partially active groups shut down in sequence
if (member->private->active_nodes != NULL) {
if (member_data->ordered && (member_data->previous_member != NULL)
&& (member_data->previous_member->private->active_nodes == NULL)
&& (member_data->last_active != NULL)
&& (member_data->last_active->private->active_nodes != NULL)) {
pcmk__order_stops(member, member_data->last_active,
pcmk__ar_ordered);
}
member_data->last_active = member;
}
member_data->previous_member = member;
}
/*!
* \internal
* \brief Create implicit constraints needed for a group resource
*
* \param[in,out] rsc Group resource to create implicit constraints for
*/
void
pcmk__group_internal_constraints(pcmk_resource_t *rsc)
{
struct member_data member_data = { false, };
const pcmk_resource_t *top = NULL;
CRM_ASSERT(pcmk__is_group(rsc));
/* Order group pseudo-actions relative to each other for restarting:
* stop group -> group is stopped -> start group -> group is started
*/
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
rsc, PCMK_ACTION_STOPPED,
pcmk__ar_unrunnable_first_blocks);
pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
rsc, PCMK_ACTION_START,
pcmk__ar_ordered);
pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
rsc, PCMK_ACTION_RUNNING,
pcmk__ar_unrunnable_first_blocks);
top = pe__const_top_resource(rsc, false);
member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered);
member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated);
member_data.promotable = pcmk_is_set(top->flags, pcmk__rsc_promotable);
g_list_foreach(rsc->private->children, member_internal_constraints,
&member_data);
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint for a group with some other resource, apply the
* score to the dependent's allowed node scores (if we are still placing
* resources) or priority (if we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent group resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
*/
static void
colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
pcmk_resource_t *member = NULL;
if (dependent->private->children == NULL) {
return;
}
pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
colocation->id, dependent->id, primary->id);
if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) {
// Colocate first member (internal colocations will handle the rest)
member = (pcmk_resource_t *) dependent->private->children->data;
member->private->cmds->apply_coloc_score(member, primary, colocation,
true);
return;
}
if (colocation->score >= PCMK_SCORE_INFINITY) {
pcmk__config_err("%s: Cannot perform mandatory colocation between "
"non-colocated group and %s",
dependent->id, primary->id);
return;
}
// Colocate each member individually
for (GList *iter = dependent->private->children;
iter != NULL; iter = iter->next) {
member = (pcmk_resource_t *) iter->data;
member->private->cmds->apply_coloc_score(member, primary, colocation,
true);
}
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint for some other resource with a group, apply the
* score to the dependent's allowed node scores (if we are still placing
* resources) or priority (if we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary group resource in colocation
* \param[in] colocation Colocation constraint to apply
*/
static void
colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation)
{
const pcmk_resource_t *member = NULL;
pcmk__rsc_trace(primary,
"Processing colocation %s (%s with group %s) for primary",
colocation->id, dependent->id, primary->id);
if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) {
return;
}
if (pe__group_flag_is_set(primary, pcmk__group_colocated)) {
if (colocation->score >= PCMK_SCORE_INFINITY) {
/* For mandatory colocations, the entire group must be assignable
* (and in the specified role if any), so apply the colocation based
* on the last member.
*/
member = pe__last_group_member(primary);
} else if (primary->private->children != NULL) {
/* For optional colocations, whether the group is partially or fully
* up doesn't matter, so apply the colocation based on the first
* member.
*/
member = (pcmk_resource_t *) primary->private->children->data;
}
if (member == NULL) {
return; // Nothing to colocate with
}
member->private->cmds->apply_coloc_score(dependent, member, colocation,
false);
return;
}
if (colocation->score >= PCMK_SCORE_INFINITY) {
pcmk__config_err("%s: Cannot perform mandatory colocation with"
" non-colocated group %s",
dependent->id, primary->id);
return;
}
// Colocate dependent with each member individually
for (const GList *iter = primary->private->children;
iter != NULL; iter = iter->next) {
member = iter->data;
member->private->cmds->apply_coloc_score(dependent, member, colocation,
false);
}
}
/*!
* \internal
* \brief Apply a colocation's score to node scores or resource priority
*
* Given a colocation constraint, apply its score to the dependent's
* allowed node scores (if we are still placing resources) or priority (if
* we are choosing promotable clone instance roles).
*
* \param[in,out] dependent Dependent resource in colocation
* \param[in] primary Primary resource in colocation
* \param[in] colocation Colocation constraint to apply
* \param[in] for_dependent true if called on behalf of dependent
*/
void
pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent)
{
CRM_ASSERT((dependent != NULL) && (primary != NULL)
&& (colocation != NULL));
if (for_dependent) {
colocate_group_with(dependent, primary, colocation);
} else {
// Method should only be called for primitive dependents
CRM_ASSERT(pcmk__is_primitive(dependent));
colocate_with_group(dependent, primary, colocation);
}
}
/*!
* \internal
* \brief Return action flags for a given group resource action
*
* \param[in,out] action Group action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
{
// Default flags for a group action
uint32_t flags = pcmk_action_optional
|pcmk_action_runnable
|pcmk_action_pseudo;
CRM_ASSERT(action != NULL);
// Update flags considering each member's own flags for same action
for (GList *iter = action->rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
// Check whether member has the same action
- enum action_tasks task = get_complex_task(member, action->task);
+ enum pcmk__action_type task = get_complex_task(member, action->task);
const char *task_s = pcmk__action_text(task);
pcmk_action_t *member_action = NULL;
member_action = find_first_action(member->private->actions, NULL,
task_s, node);
if (member_action != NULL) {
uint32_t member_flags = 0U;
member_flags = member->private->cmds->action_flags(member_action,
node);
// Group action is mandatory if any member action is
if (pcmk_is_set(flags, pcmk_action_optional)
&& !pcmk_is_set(member_flags, pcmk_action_optional)) {
pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is",
action->uuid, member_action->uuid);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk_action_optional);
pcmk__clear_action_flags(action, pcmk_action_optional);
}
// Group action is unrunnable if any member action is
if (!pcmk__str_eq(task_s, action->task, pcmk__str_none)
&& pcmk_is_set(flags, pcmk_action_runnable)
&& !pcmk_is_set(member_flags, pcmk_action_runnable)) {
pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is",
action->uuid, member_action->uuid);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk_action_runnable);
pcmk__clear_action_flags(action, pcmk_action_runnable);
}
/* Group (pseudo-)actions other than stop or demote are unrunnable
* unless every member will do it.
*/
- } else if ((task != pcmk_action_stop) && (task != pcmk_action_demote)) {
+ } else if ((task != pcmk__action_stop)
+ && (task != pcmk__action_demote)) {
pcmk__rsc_trace(action->rsc,
"%s is not runnable because %s will not %s",
action->uuid, member->id, task_s);
pcmk__clear_raw_action_flags(flags, "group action",
pcmk_action_runnable);
}
}
return flags;
}
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions' flags
* (and runnable_before members if appropriate) as appropriate for the ordering.
* Effects may cascade to other orderings involving the actions as well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk_action_optional to affect only
* mandatory actions, and pcmk_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler)
{
uint32_t changed = pcmk__updated_none;
// Group method can be called only on behalf of "then" action
CRM_ASSERT((first != NULL) && (then != NULL) && (then->rsc != NULL)
&& (scheduler != NULL));
// Update the actions for the group itself
changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
type, scheduler);
// Update the actions for each group member
for (GList *iter = then->rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
pcmk_action_t *member_action = NULL;
member_action = find_first_action(member->private->actions, NULL,
then->task, node);
if (member_action == NULL) {
continue;
}
changed |= member->private->cmds->update_ordered_actions(first,
member_action,
node, flags,
filter, type,
scheduler);
}
return changed;
}
/*!
* \internal
* \brief Apply a location constraint to a group's allowed node scores
*
* \param[in,out] rsc Group resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void
pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
{
GList *node_list_orig = NULL;
GList *node_list_copy = NULL;
bool reset_scores = true;
CRM_ASSERT(pcmk__is_group(rsc) && (location != NULL));
node_list_orig = location->nodes;
node_list_copy = pcmk__copy_node_list(node_list_orig, true);
reset_scores = pe__group_flag_is_set(rsc, pcmk__group_colocated);
// Apply the constraint for the group itself (updates node scores)
pcmk__apply_location(rsc, location);
// Apply the constraint for each member
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
member->private->cmds->apply_location(member, location);
if (reset_scores) {
/* The first member of colocated groups needs to use the original
* node scores, but subsequent members should work on a copy, since
* the first member's scores already incorporate theirs.
*/
reset_scores = false;
location->nodes = node_list_copy;
}
}
location->nodes = node_list_orig;
g_list_free_full(node_list_copy, free);
}
// Group implementation of pcmk__assignment_methods_t:colocated_resources()
GList *
pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs)
{
CRM_ASSERT(pcmk__is_group(rsc));
if (orig_rsc == NULL) {
orig_rsc = rsc;
}
if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
|| pcmk__is_clone(rsc->private->parent)) {
/* This group has colocated members and/or is cloned -- either way,
* add every child's colocated resources to the list. The first and last
* members will include the group's own colocations.
*/
colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
for (const GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *member = iter->data;
colocated_rscs = member->private->cmds->colocated_resources(member,
orig_rsc,
colocated_rscs);
}
} else if (rsc->private->children != NULL) {
/* This group's members are not colocated, and the group is not cloned,
* so just add the group's own colocations to the list.
*/
colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc,
colocated_rscs);
}
return colocated_rscs;
}
// Group implementation of pcmk__assignment_methods_t:with_this_colocations()
void
pcmk__with_group_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
parent = rsc->private->parent;
// Ignore empty groups
if (rsc->private->children == NULL) {
return;
}
/* "With this" colocations are needed only for the group itself and for its
* last member. (Previous members will chain via the group internal
* colocations.)
*/
if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) {
return;
}
pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s",
rsc->id, orig_rsc->id);
// Add the group's own colocations
pcmk__add_with_this_list(list, rsc->private->with_this_colocations,
orig_rsc);
// If cloned, add any relevant colocations with the clone
if (parent != NULL) {
parent->private->cmds->with_this_colocations(parent, orig_rsc, list);
}
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
// @COMPAT Non-colocated groups are deprecated
return;
}
// Add explicit colocations with the group's (other) children
for (const GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
const pcmk_resource_t *member = iter->data;
if (member == orig_rsc) {
continue;
}
member->private->cmds->with_this_colocations(member, orig_rsc, list);
}
}
// Group implementation of pcmk__assignment_methods_t:this_with_colocations()
void
pcmk__group_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList **list)
{
const pcmk_resource_t *parent = NULL;
const pcmk_resource_t *member = NULL;
CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc));
parent = rsc->private->parent;
// Ignore empty groups
if (rsc->private->children == NULL) {
return;
}
/* "This with" colocations are normally needed only for the group itself and
* for its first member.
*/
if ((rsc == orig_rsc) || (orig_rsc == rsc->private->children->data)) {
pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s",
rsc->id, orig_rsc->id);
// Add the group's own colocations
pcmk__add_this_with_list(list, rsc->private->this_with_colocations,
orig_rsc);
// If cloned, add any relevant colocations involving the clone
if (parent != NULL) {
parent->private->cmds->this_with_colocations(parent, orig_rsc,
list);
}
if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
// @COMPAT Non-colocated groups are deprecated
return;
}
// Add explicit colocations involving the group's (other) children
for (const GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
member = iter->data;
if (member == orig_rsc) {
continue;
}
member->private->cmds->this_with_colocations(member, orig_rsc,
list);
}
return;
}
/* Later group members honor the group's colocations indirectly, due to the
* internal group colocations that chain everything from the first member.
* However, if an earlier group member is unmanaged, this chaining will not
* happen, so the group's mandatory colocations must be explicitly added.
*/
for (const GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
member = iter->data;
if (orig_rsc == member) {
break; // We've seen all earlier members, and none are unmanaged
}
if (!pcmk_is_set(member->flags, pcmk__rsc_managed)) {
crm_trace("Adding mandatory '%s with' colocations to list for "
"member %s because earlier member %s is unmanaged",
rsc->id, orig_rsc->id, member->id);
for (const GList *cons_iter = rsc->private->this_with_colocations;
cons_iter != NULL; cons_iter = cons_iter->next) {
const pcmk__colocation_t *colocation = NULL;
colocation = (const pcmk__colocation_t *) cons_iter->data;
if (colocation->score == PCMK_SCORE_INFINITY) {
pcmk__add_this_with(list, colocation, orig_rsc);
}
}
// @TODO Add mandatory (or all?) clone constraints if cloned
break;
}
}
}
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Group resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to \c NULL
* to copy allowed nodes from \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* <tt>source_rsc</tt>'s own matching node scores will
* not be added, and \p *nodes must be \c NULL as
* well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
* the \c pcmk__coloc_select_this_with flag are used together (and only by
* \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
* \note This is the group implementation of
* \c pcmk__assignment_methods_t:add_colocated_node_scores().
*/
void
pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id, GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags)
{
pcmk_resource_t *member = NULL;
CRM_ASSERT(pcmk__is_group(source_rsc) && (nodes != NULL)
&& ((colocation != NULL)
|| ((target_rsc == NULL) && (*nodes == NULL))));
if (log_id == NULL) {
log_id = source_rsc->id;
}
// Avoid infinite recursion
if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) {
pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
log_id, source_rsc->id);
return;
}
pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
// Ignore empty groups (only possible with schema validation disabled)
if (source_rsc->private->children == NULL) {
return;
}
/* Refer the operation to the first or last member as appropriate.
*
* cmp_resources() is the only caller that passes a NULL nodes table,
* and is also the only caller using pcmk__coloc_select_this_with.
* For "this with" colocations, the last member will recursively incorporate
* all the other members' "this with" colocations via the internal group
* colocations (and via the first member, the group's own colocations).
*
* For "with this" colocations, the first member works similarly.
*/
if (*nodes == NULL) {
member = pe__last_group_member(source_rsc);
} else {
member = source_rsc->private->children->data;
}
pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s "
"(at %.6f)", log_id, source_rsc->id, member->id, factor);
member->private->cmds->add_colocated_node_scores(member, target_rsc, log_id,
nodes, colocation, factor,
flags);
pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes);
}
// Group implementation of pcmk__assignment_methods_t:add_utilization()
void
pcmk__group_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization)
{
pcmk_resource_t *member = NULL;
CRM_ASSERT((orig_rsc != NULL) && (utilization != NULL)
&& pcmk__is_group(rsc));
if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) {
return;
}
pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
orig_rsc->id, rsc->id);
if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
|| pcmk__is_clone(rsc->private->parent)) {
// Every group member will be on same node, so sum all members
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
member = (pcmk_resource_t *) iter->data;
if (pcmk_is_set(member->flags, pcmk__rsc_unassigned)
&& (g_list_find(all_rscs, member) == NULL)) {
member->private->cmds->add_utilization(member, orig_rsc,
all_rscs, utilization);
}
}
} else if (rsc->private->children != NULL) {
// Just add first member's utilization
member = (pcmk_resource_t *) rsc->private->children->data;
if ((member != NULL)
&& pcmk_is_set(member->flags, pcmk__rsc_unassigned)
&& (g_list_find(all_rscs, member) == NULL)) {
member->private->cmds->add_utilization(member, orig_rsc, all_rscs,
utilization);
}
}
}
void
pcmk__group_shutdown_lock(pcmk_resource_t *rsc)
{
CRM_ASSERT(pcmk__is_group(rsc));
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
member->private->cmds->shutdown_lock(member);
}
}
diff --git a/lib/pacemaker/pcmk_sched_instances.c b/lib/pacemaker/pcmk_sched_instances.c
index 4b1e4bbf89..033d1c57a1 100644
--- a/lib/pacemaker/pcmk_sched_instances.c
+++ b/lib/pacemaker/pcmk_sched_instances.c
@@ -1,1709 +1,1709 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
/* This file is intended for code usable with both clone instances and bundle
* replica containers.
*/
#include <crm_internal.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
/*!
* \internal
* \brief Check whether a node is allowed to run an instance
*
* \param[in] instance Clone instance or bundle container to check
* \param[in] node Node to check
* \param[in] max_per_node Maximum number of instances allowed to run on a node
*
* \return true if \p node is allowed to run \p instance, otherwise false
*/
static bool
can_run_instance(const pcmk_resource_t *instance, const pcmk_node_t *node,
int max_per_node)
{
pcmk_node_t *allowed_node = NULL;
if (pcmk_is_set(instance->flags, pcmk__rsc_removed)) {
pcmk__rsc_trace(instance, "%s cannot run on %s: orphaned",
instance->id, pcmk__node_name(node));
return false;
}
if (!pcmk__node_available(node, false, false)) {
pcmk__rsc_trace(instance,
"%s cannot run on %s: node cannot run resources",
instance->id, pcmk__node_name(node));
return false;
}
allowed_node = pcmk__top_allowed_node(instance, node);
if (allowed_node == NULL) {
crm_warn("%s cannot run on %s: node not allowed",
instance->id, pcmk__node_name(node));
return false;
}
if (allowed_node->assign->score < 0) {
pcmk__rsc_trace(instance,
"%s cannot run on %s: parent score is %s there",
instance->id, pcmk__node_name(node),
pcmk_readable_score(allowed_node->assign->score));
return false;
}
if (allowed_node->assign->count >= max_per_node) {
pcmk__rsc_trace(instance,
"%s cannot run on %s: node already has %d instance%s",
instance->id, pcmk__node_name(node), max_per_node,
pcmk__plural_s(max_per_node));
return false;
}
pcmk__rsc_trace(instance, "%s can run on %s (%d already running)",
instance->id, pcmk__node_name(node),
allowed_node->assign->count);
return true;
}
/*!
* \internal
* \brief Ban a clone instance or bundle replica from unavailable allowed nodes
*
* \param[in,out] instance Clone instance or bundle replica to ban
* \param[in] max_per_node Maximum instances allowed to run on a node
*/
static void
ban_unavailable_allowed_nodes(pcmk_resource_t *instance, int max_per_node)
{
if (instance->private->allowed_nodes != NULL) {
GHashTableIter iter;
pcmk_node_t *node = NULL;
g_hash_table_iter_init(&iter, instance->private->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (!can_run_instance(instance, node, max_per_node)) {
pcmk__rsc_trace(instance, "Banning %s from unavailable node %s",
instance->id, pcmk__node_name(node));
node->assign->score = -PCMK_SCORE_INFINITY;
for (GList *child_iter = instance->private->children;
child_iter != NULL; child_iter = child_iter->next) {
pcmk_resource_t *child = child_iter->data;
pcmk_node_t *child_node = NULL;
child_node =
g_hash_table_lookup(child->private->allowed_nodes,
node->private->id);
if (child_node != NULL) {
pcmk__rsc_trace(instance,
"Banning %s child %s "
"from unavailable node %s",
instance->id, child->id,
pcmk__node_name(node));
child_node->assign->score = -PCMK_SCORE_INFINITY;
}
}
}
}
}
}
/*!
* \internal
* \brief Create a hash table with a single node in it
*
* \param[in] node Node to copy into new table
*
* \return Newly created hash table containing a copy of \p node
* \note The caller is responsible for freeing the result with
* g_hash_table_destroy().
*/
static GHashTable *
new_node_table(pcmk_node_t *node)
{
GHashTable *table = pcmk__strkey_table(NULL, free);
node = pe__copy_node(node);
g_hash_table_insert(table, (gpointer) node->private->id, node);
return table;
}
/*!
* \internal
* \brief Apply a resource's parent's colocation scores to a node table
*
* \param[in] rsc Resource whose colocations should be applied
* \param[in,out] nodes Node table to apply colocations to
*/
static void
apply_parent_colocations(const pcmk_resource_t *rsc, GHashTable **nodes)
{
GList *colocations = pcmk__this_with_colocations(rsc);
for (const GList *iter = colocations; iter != NULL; iter = iter->next) {
const pcmk__colocation_t *colocation = iter->data;
pcmk_resource_t *other = colocation->primary;
float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
other->private->cmds->add_colocated_node_scores(other, rsc, rsc->id,
nodes, colocation,
factor,
pcmk__coloc_select_default);
}
g_list_free(colocations);
colocations = pcmk__with_this_colocations(rsc);
for (const GList *iter = colocations; iter != NULL; iter = iter->next) {
const pcmk__colocation_t *colocation = iter->data;
pcmk_resource_t *other = colocation->dependent;
float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
if (!pcmk__colocation_has_influence(colocation, rsc)) {
continue;
}
other->private->cmds->add_colocated_node_scores(other, rsc, rsc->id,
nodes, colocation,
factor,
pcmk__coloc_select_nonnegative);
}
g_list_free(colocations);
}
/*!
* \internal
* \brief Compare clone or bundle instances based on colocation scores
*
* Determine the relative order in which two clone or bundle instances should be
* assigned to nodes, considering the scores of colocation constraints directly
* or indirectly involving them.
*
* \param[in] instance1 First instance to compare
* \param[in] instance2 Second instance to compare
*
* \return A negative number if \p instance1 should be assigned first,
* a positive number if \p instance2 should be assigned first,
* or 0 if assignment order doesn't matter
*/
static int
cmp_instance_by_colocation(const pcmk_resource_t *instance1,
const pcmk_resource_t *instance2)
{
int rc = 0;
pcmk_node_t *node1 = NULL;
pcmk_node_t *node2 = NULL;
pcmk_node_t *current_node1 = pcmk__current_node(instance1);
pcmk_node_t *current_node2 = pcmk__current_node(instance2);
GHashTable *colocated_scores1 = NULL;
GHashTable *colocated_scores2 = NULL;
CRM_ASSERT((instance1 != NULL) && (instance1->private->parent != NULL)
&& (instance2 != NULL) && (instance2->private->parent != NULL)
&& (current_node1 != NULL) && (current_node2 != NULL));
// Create node tables initialized with each node
colocated_scores1 = new_node_table(current_node1);
colocated_scores2 = new_node_table(current_node2);
// Apply parental colocations
apply_parent_colocations(instance1, &colocated_scores1);
apply_parent_colocations(instance2, &colocated_scores2);
// Find original nodes again, with scores updated for colocations
node1 = g_hash_table_lookup(colocated_scores1, current_node1->private->id);
node2 = g_hash_table_lookup(colocated_scores2, current_node2->private->id);
// Compare nodes by updated scores
if (node1->assign->score < node2->assign->score) {
crm_trace("Assign %s (%d on %s) after %s (%d on %s)",
instance1->id, node1->assign->score, pcmk__node_name(node1),
instance2->id, node2->assign->score, pcmk__node_name(node2));
rc = 1;
} else if (node1->assign->score > node2->assign->score) {
crm_trace("Assign %s (%d on %s) before %s (%d on %s)",
instance1->id, node1->assign->score, pcmk__node_name(node1),
instance2->id, node2->assign->score, pcmk__node_name(node2));
rc = -1;
}
g_hash_table_destroy(colocated_scores1);
g_hash_table_destroy(colocated_scores2);
return rc;
}
/*!
* \internal
* \brief Check whether a resource or any of its children are failed
*
* \param[in] rsc Resource to check
*
* \return true if \p rsc or any of its children are failed, otherwise false
*/
static bool
did_fail(const pcmk_resource_t *rsc)
{
if (pcmk_is_set(rsc->flags, pcmk__rsc_failed)) {
return true;
}
for (GList *iter = rsc->private->children;
iter != NULL; iter = iter->next) {
if (did_fail((const pcmk_resource_t *) iter->data)) {
return true;
}
}
return false;
}
/*!
* \internal
* \brief Check whether a node is allowed to run a resource
*
* \param[in] rsc Resource to check
* \param[in,out] node Node to check (will be set NULL if not allowed)
*
* \return true if *node is either NULL or allowed for \p rsc, otherwise false
*/
static bool
node_is_allowed(const pcmk_resource_t *rsc, pcmk_node_t **node)
{
if (*node != NULL) {
pcmk_node_t *allowed = g_hash_table_lookup(rsc->private->allowed_nodes,
(*node)->private->id);
if ((allowed == NULL) || (allowed->assign->score < 0)) {
pcmk__rsc_trace(rsc, "%s: current location (%s) is unavailable",
rsc->id, pcmk__node_name(*node));
*node = NULL;
return false;
}
}
return true;
}
/*!
* \internal
* \brief Compare two clone or bundle instances' instance numbers
*
* \param[in] a First instance to compare
* \param[in] b Second instance to compare
*
* \return A negative number if \p a's instance number is lower,
* a positive number if \p b's instance number is lower,
* or 0 if their instance numbers are the same
*/
gint
pcmk__cmp_instance_number(gconstpointer a, gconstpointer b)
{
const pcmk_resource_t *instance1 = (const pcmk_resource_t *) a;
const pcmk_resource_t *instance2 = (const pcmk_resource_t *) b;
char *div1 = NULL;
char *div2 = NULL;
CRM_ASSERT((instance1 != NULL) && (instance2 != NULL));
// Clone numbers are after a colon, bundle numbers after a dash
div1 = strrchr(instance1->id, ':');
if (div1 == NULL) {
div1 = strrchr(instance1->id, '-');
}
div2 = strrchr(instance2->id, ':');
if (div2 == NULL) {
div2 = strrchr(instance2->id, '-');
}
CRM_ASSERT((div1 != NULL) && (div2 != NULL));
return (gint) (strtol(div1 + 1, NULL, 10) - strtol(div2 + 1, NULL, 10));
}
/*!
* \internal
* \brief Compare clone or bundle instances according to assignment order
*
* Compare two clone or bundle instances according to the order they should be
* assigned to nodes, preferring (in order):
*
* - Active instance that is less multiply active
* - Instance that is not active on a disallowed node
* - Instance with higher configured priority
* - Active instance whose current node can run resources
* - Active instance whose parent is allowed on current node
* - Active instance whose current node has fewer other instances
* - Active instance
* - Instance that isn't failed
* - Instance whose colocations result in higher score on current node
* - Instance with lower ID in lexicographic order
*
* \param[in] a First instance to compare
* \param[in] b Second instance to compare
*
* \return A negative number if \p a should be assigned first,
* a positive number if \p b should be assigned first,
* or 0 if assignment order doesn't matter
*/
gint
pcmk__cmp_instance(gconstpointer a, gconstpointer b)
{
int rc = 0;
pcmk_node_t *node1 = NULL;
pcmk_node_t *node2 = NULL;
unsigned int nnodes1 = 0;
unsigned int nnodes2 = 0;
bool can1 = true;
bool can2 = true;
const pcmk_resource_t *instance1 = (const pcmk_resource_t *) a;
const pcmk_resource_t *instance2 = (const pcmk_resource_t *) b;
CRM_ASSERT((instance1 != NULL) && (instance2 != NULL));
node1 = instance1->private->fns->active_node(instance1, &nnodes1, NULL);
node2 = instance2->private->fns->active_node(instance2, &nnodes2, NULL);
/* If both instances are running and at least one is multiply
* active, prefer instance that's running on fewer nodes.
*/
if ((nnodes1 > 0) && (nnodes2 > 0)) {
if (nnodes1 < nnodes2) {
crm_trace("Assign %s (active on %d) before %s (active on %d): "
"less multiply active",
instance1->id, nnodes1, instance2->id, nnodes2);
return -1;
} else if (nnodes1 > nnodes2) {
crm_trace("Assign %s (active on %d) after %s (active on %d): "
"more multiply active",
instance1->id, nnodes1, instance2->id, nnodes2);
return 1;
}
}
/* An instance that is either inactive or active on an allowed node is
* preferred over an instance that is active on a no-longer-allowed node.
*/
can1 = node_is_allowed(instance1, &node1);
can2 = node_is_allowed(instance2, &node2);
if (can1 && !can2) {
crm_trace("Assign %s before %s: not active on a disallowed node",
instance1->id, instance2->id);
return -1;
} else if (!can1 && can2) {
crm_trace("Assign %s after %s: active on a disallowed node",
instance1->id, instance2->id);
return 1;
}
// Prefer instance with higher configured priority
if (instance1->private->priority > instance2->private->priority) {
crm_trace("Assign %s before %s: priority (%d > %d)",
instance1->id, instance2->id,
instance1->private->priority, instance2->private->priority);
return -1;
} else if (instance1->private->priority < instance2->private->priority) {
crm_trace("Assign %s after %s: priority (%d < %d)",
instance1->id, instance2->id,
instance1->private->priority, instance2->private->priority);
return 1;
}
// Prefer active instance
if ((node1 == NULL) && (node2 == NULL)) {
crm_trace("No assignment preference for %s vs. %s: inactive",
instance1->id, instance2->id);
return 0;
} else if (node1 == NULL) {
crm_trace("Assign %s after %s: active", instance1->id, instance2->id);
return 1;
} else if (node2 == NULL) {
crm_trace("Assign %s before %s: active", instance1->id, instance2->id);
return -1;
}
// Prefer instance whose current node can run resources
can1 = pcmk__node_available(node1, false, false);
can2 = pcmk__node_available(node2, false, false);
if (can1 && !can2) {
crm_trace("Assign %s before %s: current node can run resources",
instance1->id, instance2->id);
return -1;
} else if (!can1 && can2) {
crm_trace("Assign %s after %s: current node can't run resources",
instance1->id, instance2->id);
return 1;
}
// Prefer instance whose parent is allowed to run on instance's current node
node1 = pcmk__top_allowed_node(instance1, node1);
node2 = pcmk__top_allowed_node(instance2, node2);
if ((node1 == NULL) && (node2 == NULL)) {
crm_trace("No assignment preference for %s vs. %s: "
"parent not allowed on either instance's current node",
instance1->id, instance2->id);
return 0;
} else if (node1 == NULL) {
crm_trace("Assign %s after %s: parent not allowed on current node",
instance1->id, instance2->id);
return 1;
} else if (node2 == NULL) {
crm_trace("Assign %s before %s: parent allowed on current node",
instance1->id, instance2->id);
return -1;
}
// Prefer instance whose current node is running fewer other instances
if (node1->assign->count < node2->assign->count) {
crm_trace("Assign %s before %s: fewer active instances on current node",
instance1->id, instance2->id);
return -1;
} else if (node1->assign->count > node2->assign->count) {
crm_trace("Assign %s after %s: more active instances on current node",
instance1->id, instance2->id);
return 1;
}
// Prefer instance that isn't failed
can1 = did_fail(instance1);
can2 = did_fail(instance2);
if (!can1 && can2) {
crm_trace("Assign %s before %s: not failed",
instance1->id, instance2->id);
return -1;
} else if (can1 && !can2) {
crm_trace("Assign %s after %s: failed",
instance1->id, instance2->id);
return 1;
}
// Prefer instance with higher cumulative colocation score on current node
rc = cmp_instance_by_colocation(instance1, instance2);
if (rc != 0) {
return rc;
}
// Prefer instance with lower instance number
rc = pcmk__cmp_instance_number(instance1, instance2);
if (rc < 0) {
crm_trace("Assign %s before %s: instance number",
instance1->id, instance2->id);
} else if (rc > 0) {
crm_trace("Assign %s after %s: instance number",
instance1->id, instance2->id);
} else {
crm_trace("No assignment preference for %s vs. %s",
instance1->id, instance2->id);
}
return rc;
}
/*!
* \internal
* \brief Increment the parent's instance count after assigning an instance
*
* An instance's parent tracks how many instances have been assigned to each
* node via its pcmk_node_t:count member. After assigning an instance to a node,
* find the corresponding node in the parent's allowed table and increment it.
*
* \param[in,out] instance Instance whose parent to update
* \param[in] assigned_to Node to which the instance was assigned
*/
static void
increment_parent_count(pcmk_resource_t *instance,
const pcmk_node_t *assigned_to)
{
pcmk_node_t *allowed = NULL;
if (assigned_to == NULL) {
return;
}
allowed = pcmk__top_allowed_node(instance, assigned_to);
if (allowed == NULL) {
/* The instance is allowed on the node, but its parent isn't. This
* shouldn't be possible if the resource is managed, and we won't be
* able to limit the number of instances assigned to the node.
*/
CRM_LOG_ASSERT(!pcmk_is_set(instance->flags, pcmk__rsc_managed));
} else {
allowed->assign->count++;
}
}
/*!
* \internal
* \brief Assign an instance to a node
*
* \param[in,out] instance Clone instance or bundle replica container
* \param[in] prefer If not NULL, attempt early assignment to this
* node, if still the best choice; otherwise,
* perform final assignment
* \param[in] max_per_node Assign at most this many instances to one node
*
* \return Node to which \p instance is assigned
*/
static const pcmk_node_t *
assign_instance(pcmk_resource_t *instance, const pcmk_node_t *prefer,
int max_per_node)
{
pcmk_node_t *chosen = NULL;
pcmk__rsc_trace(instance, "Assigning %s (preferring %s)", instance->id,
((prefer == NULL)? "no node" : prefer->private->name));
if (pcmk_is_set(instance->flags, pcmk__rsc_assigning)) {
pcmk__rsc_debug(instance,
"Assignment loop detected involving %s colocations",
instance->id);
return NULL;
}
ban_unavailable_allowed_nodes(instance, max_per_node);
// Failed early assignments are reversible (stop_if_fail=false)
chosen = instance->private->cmds->assign(instance, prefer,
(prefer == NULL));
increment_parent_count(instance, chosen);
return chosen;
}
/*!
* \internal
* \brief Try to assign an instance to its current node early
*
* \param[in] rsc Clone or bundle being assigned (for logs only)
* \param[in] instance Clone instance or bundle replica container
* \param[in] current Instance's current node
* \param[in] max_per_node Maximum number of instances per node
* \param[in] available Number of instances still available for assignment
*
* \return \c true if \p instance was successfully assigned to its current node,
* or \c false otherwise
*/
static bool
assign_instance_early(const pcmk_resource_t *rsc, pcmk_resource_t *instance,
const pcmk_node_t *current, int max_per_node,
int available)
{
const pcmk_node_t *chosen = NULL;
int reserved = 0;
pcmk_resource_t *parent = instance->private->parent;
GHashTable *allowed_orig = NULL;
GHashTable *allowed_orig_parent = parent->private->allowed_nodes;
const pcmk_node_t *allowed_node = NULL;
pcmk__rsc_trace(instance, "Trying to assign %s to its current node %s",
instance->id, pcmk__node_name(current));
allowed_node = g_hash_table_lookup(instance->private->allowed_nodes,
current->private->id);
if (!pcmk__node_available(allowed_node, true, false)) {
pcmk__rsc_info(instance,
"Not assigning %s to current node %s: unavailable",
instance->id, pcmk__node_name(current));
return false;
}
/* On each iteration, if instance gets assigned to a node other than its
* current one, we reserve one instance for the chosen node, unassign
* instance, restore instance's original node tables, and try again. This
* way, instances are proportionally assigned to nodes based on preferences,
* but shuffling of specific instances is minimized. If a node will be
* assigned instances at all, it preferentially receives instances that are
* currently active there.
*
* parent->private->allowed_nodes tracks the number of instances assigned to
* each node. If a node already has max_per_node instances assigned,
* ban_unavailable_allowed_nodes() marks it as unavailable.
*
* In the end, we restore the original parent->private->allowed_nodes to
* undo the changes to counts during tentative assignments. If we
* successfully assigned an instance to its current node, we increment that
* node's counter.
*/
// Back up the allowed node tables of instance and its children recursively
pcmk__copy_node_tables(instance, &allowed_orig);
// Update instances-per-node counts in a scratch table
parent->private->allowed_nodes = pcmk__copy_node_table(allowed_orig_parent);
while (reserved < available) {
chosen = assign_instance(instance, current, max_per_node);
if (pcmk__same_node(chosen, current)) {
// Successfully assigned to current node
break;
}
// Assignment updates scores, so restore to original state
pcmk__rsc_debug(instance, "Rolling back node scores for %s",
instance->id);
pcmk__restore_node_tables(instance, allowed_orig);
if (chosen == NULL) {
// Assignment failed, so give up
pcmk__rsc_info(instance,
"Not assigning %s to current node %s: unavailable",
instance->id, pcmk__node_name(current));
pcmk__set_rsc_flags(instance, pcmk__rsc_unassigned);
break;
}
// We prefer more strongly to assign an instance to the chosen node
pcmk__rsc_debug(instance,
"Not assigning %s to current node %s: %s is better",
instance->id, pcmk__node_name(current),
pcmk__node_name(chosen));
// Reserve one instance for the chosen node and try again
if (++reserved >= available) {
pcmk__rsc_info(instance,
"Not assigning %s to current node %s: "
"other assignments are more important",
instance->id, pcmk__node_name(current));
} else {
pcmk__rsc_debug(instance,
"Reserved an instance of %s for %s. Retrying "
"assignment of %s to %s",
rsc->id, pcmk__node_name(chosen), instance->id,
pcmk__node_name(current));
}
// Clear this assignment (frees chosen); leave instance counts in parent
pcmk__unassign_resource(instance);
chosen = NULL;
}
g_hash_table_destroy(allowed_orig);
// Restore original instances-per-node counts
g_hash_table_destroy(parent->private->allowed_nodes);
parent->private->allowed_nodes = allowed_orig_parent;
if (chosen == NULL) {
// Couldn't assign instance to current node
return false;
}
pcmk__rsc_trace(instance, "Assigned %s to current node %s",
instance->id, pcmk__node_name(current));
increment_parent_count(instance, chosen);
return true;
}
/*!
* \internal
* \brief Reset the node counts of a resource's allowed nodes to zero
*
* \param[in,out] rsc Resource to reset
*
* \return Number of nodes that are available to run resources
*/
static unsigned int
reset_allowed_node_counts(pcmk_resource_t *rsc)
{
unsigned int available_nodes = 0;
pcmk_node_t *node = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, rsc->private->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &node)) {
node->assign->count = 0;
if (pcmk__node_available(node, false, false)) {
available_nodes++;
}
}
return available_nodes;
}
/*!
* \internal
* \brief Check whether an instance has a preferred node
*
* \param[in] instance Clone instance or bundle replica container
* \param[in] optimal_per_node Optimal number of instances per node
*
* \return Instance's current node if still available, otherwise NULL
*/
static const pcmk_node_t *
preferred_node(const pcmk_resource_t *instance, int optimal_per_node)
{
const pcmk_node_t *node = NULL;
const pcmk_node_t *parent_node = NULL;
// Check whether instance is active, healthy, and not yet assigned
if ((instance->private->active_nodes == NULL)
|| !pcmk_is_set(instance->flags, pcmk__rsc_unassigned)
|| pcmk_is_set(instance->flags, pcmk__rsc_failed)) {
return NULL;
}
// Check whether instance's current node can run resources
node = pcmk__current_node(instance);
if (!pcmk__node_available(node, true, false)) {
pcmk__rsc_trace(instance, "Not assigning %s to %s early (unavailable)",
instance->id, pcmk__node_name(node));
return NULL;
}
// Check whether node already has optimal number of instances assigned
parent_node = pcmk__top_allowed_node(instance, node);
if ((parent_node != NULL)
&& (parent_node->assign->count >= optimal_per_node)) {
pcmk__rsc_trace(instance,
"Not assigning %s to %s early "
"(optimal instances already assigned)",
instance->id, pcmk__node_name(node));
return NULL;
}
return node;
}
/*!
* \internal
* \brief Assign collective instances to nodes
*
* \param[in,out] collective Clone or bundle resource being assigned
* \param[in,out] instances List of clone instances or bundle containers
* \param[in] max_total Maximum instances to assign in total
* \param[in] max_per_node Maximum instances to assign to any one node
*/
void
pcmk__assign_instances(pcmk_resource_t *collective, GList *instances,
int max_total, int max_per_node)
{
// Reuse node count to track number of assigned instances
unsigned int available_nodes = reset_allowed_node_counts(collective);
int optimal_per_node = 0;
int assigned = 0;
GList *iter = NULL;
pcmk_resource_t *instance = NULL;
const pcmk_node_t *current = NULL;
if (available_nodes > 0) {
optimal_per_node = max_total / available_nodes;
}
if (optimal_per_node < 1) {
optimal_per_node = 1;
}
pcmk__rsc_debug(collective,
"Assigning up to %d %s instance%s to up to %u node%s "
"(at most %d per host, %d optimal)",
max_total, collective->id, pcmk__plural_s(max_total),
available_nodes, pcmk__plural_s(available_nodes),
max_per_node, optimal_per_node);
// Assign as many instances as possible to their current location
for (iter = instances; (iter != NULL) && (assigned < max_total);
iter = iter->next) {
int available = max_total - assigned;
instance = iter->data;
if (!pcmk_is_set(instance->flags, pcmk__rsc_unassigned)) {
continue; // Already assigned
}
current = preferred_node(instance, optimal_per_node);
if ((current != NULL)
&& assign_instance_early(collective, instance, current,
max_per_node, available)) {
assigned++;
}
}
pcmk__rsc_trace(collective, "Assigned %d of %d instance%s to current node",
assigned, max_total, pcmk__plural_s(max_total));
for (iter = instances; iter != NULL; iter = iter->next) {
instance = (pcmk_resource_t *) iter->data;
if (!pcmk_is_set(instance->flags, pcmk__rsc_unassigned)) {
continue; // Already assigned
}
if (instance->private->active_nodes != NULL) {
current = pcmk__current_node(instance);
if (pcmk__top_allowed_node(instance, current) == NULL) {
const char *unmanaged = "";
if (!pcmk_is_set(instance->flags, pcmk__rsc_managed)) {
unmanaged = "Unmanaged resource ";
}
crm_notice("%s%s is running on %s which is no longer allowed",
unmanaged, instance->id, pcmk__node_name(current));
}
}
if (assigned >= max_total) {
pcmk__rsc_debug(collective,
"Not assigning %s because maximum %d instances "
"already assigned",
instance->id, max_total);
resource_location(instance, NULL, -PCMK_SCORE_INFINITY,
"collective_limit_reached",
collective->private->scheduler);
} else if (assign_instance(instance, NULL, max_per_node) != NULL) {
assigned++;
}
}
pcmk__rsc_debug(collective, "Assigned %d of %d possible instance%s of %s",
assigned, max_total, pcmk__plural_s(max_total),
collective->id);
}
enum instance_state {
instance_starting = (1 << 0),
instance_stopping = (1 << 1),
/* This indicates that some instance is restarting. It's not the same as
* instance_starting|instance_stopping, which would indicate that some
* instance is starting, and some instance (not necessarily the same one) is
* stopping.
*/
instance_restarting = (1 << 2),
instance_active = (1 << 3),
instance_all = instance_starting|instance_stopping
|instance_restarting|instance_active,
};
/*!
* \internal
* \brief Check whether an instance is active, starting, and/or stopping
*
* \param[in] instance Clone instance or bundle replica container
* \param[in,out] state Whether any instance is starting, stopping, etc.
*/
static void
check_instance_state(const pcmk_resource_t *instance, uint32_t *state)
{
const GList *iter = NULL;
uint32_t instance_state = 0; // State of just this instance
// No need to check further if all conditions have already been detected
if (pcmk_all_flags_set(*state, instance_all)) {
return;
}
// If instance is a collective (a cloned group), check its children instead
if (instance->private->variant > pcmk__rsc_variant_primitive) {
for (iter = instance->private->children;
(iter != NULL) && !pcmk_all_flags_set(*state, instance_all);
iter = iter->next) {
check_instance_state((const pcmk_resource_t *) iter->data, state);
}
return;
}
// If we get here, instance is a primitive
if (instance->private->active_nodes != NULL) {
instance_state |= instance_active;
}
// Check each of the instance's actions for runnable start or stop
for (iter = instance->private->actions;
(iter != NULL) && !pcmk_all_flags_set(instance_state,
instance_starting
|instance_stopping);
iter = iter->next) {
const pcmk_action_t *action = (const pcmk_action_t *) iter->data;
const bool optional = pcmk_is_set(action->flags, pcmk_action_optional);
if (pcmk__str_eq(PCMK_ACTION_START, action->task, pcmk__str_none)) {
if (!optional
&& pcmk_is_set(action->flags, pcmk_action_runnable)) {
pcmk__rsc_trace(instance, "Instance is starting due to %s",
action->uuid);
instance_state |= instance_starting;
} else {
pcmk__rsc_trace(instance, "%s doesn't affect %s state (%s)",
action->uuid, instance->id,
(optional? "optional" : "unrunnable"));
}
} else if (pcmk__str_eq(PCMK_ACTION_STOP, action->task,
pcmk__str_none)) {
/* Only stop actions can be pseudo-actions for primitives. That
* indicates that the node they are on is being fenced, so the stop
* is implied rather than actually executed.
*/
if (!optional
&& pcmk_any_flags_set(action->flags, pcmk_action_pseudo
|pcmk_action_runnable)) {
pcmk__rsc_trace(instance, "Instance is stopping due to %s",
action->uuid);
instance_state |= instance_stopping;
} else {
pcmk__rsc_trace(instance, "%s doesn't affect %s state (%s)",
action->uuid, instance->id,
(optional? "optional" : "unrunnable"));
}
}
}
if (pcmk_all_flags_set(instance_state,
instance_starting|instance_stopping)) {
instance_state |= instance_restarting;
}
*state |= instance_state;
}
/*!
* \internal
* \brief Create actions for collective resource instances
*
* \param[in,out] collective Clone or bundle resource to create actions for
* \param[in,out] instances List of clone instances or bundle containers
*/
void
pcmk__create_instance_actions(pcmk_resource_t *collective, GList *instances)
{
uint32_t state = 0;
pcmk_action_t *stop = NULL;
pcmk_action_t *stopped = NULL;
pcmk_action_t *start = NULL;
pcmk_action_t *started = NULL;
pcmk__rsc_trace(collective, "Creating collective instance actions for %s",
collective->id);
// Create actions for each instance appropriate to its variant
for (GList *iter = instances; iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
instance->private->cmds->create_actions(instance);
check_instance_state(instance, &state);
}
// Create pseudo-actions for rsc start and started
start = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_START,
!pcmk_is_set(state, instance_starting),
true);
started = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_RUNNING,
!pcmk_is_set(state, instance_starting),
false);
started->priority = PCMK_SCORE_INFINITY;
if (pcmk_any_flags_set(state, instance_active|instance_starting)) {
pcmk__set_action_flags(started, pcmk_action_runnable);
}
// Create pseudo-actions for rsc stop and stopped
stop = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_STOP,
!pcmk_is_set(state, instance_stopping),
true);
stopped = pe__new_rsc_pseudo_action(collective, PCMK_ACTION_STOPPED,
!pcmk_is_set(state, instance_stopping),
true);
stopped->priority = PCMK_SCORE_INFINITY;
if (!pcmk_is_set(state, instance_restarting)) {
pcmk__set_action_flags(stop, pcmk_action_migratable);
}
if (pcmk__is_clone(collective)) {
pe__create_clone_notif_pseudo_ops(collective, start, started, stop,
stopped);
}
}
/*!
* \internal
* \brief Get a list of clone instances or bundle replica containers
*
* \param[in] rsc Clone or bundle resource
*
* \return Clone instances if \p rsc is a clone, or a newly created list of
* \p rsc's replica containers if \p rsc is a bundle
* \note The caller must call free_instance_list() on the result when the list
* is no longer needed.
*/
static inline GList *
get_instance_list(const pcmk_resource_t *rsc)
{
if (pcmk__is_bundle(rsc)) {
return pe__bundle_containers(rsc);
} else {
return rsc->private->children;
}
}
/*!
* \internal
* \brief Free any memory created by get_instance_list()
*
* \param[in] rsc Clone or bundle resource passed to get_instance_list()
* \param[in,out] list Return value of get_instance_list() for \p rsc
*/
static inline void
free_instance_list(const pcmk_resource_t *rsc, GList *list)
{
if (list != rsc->private->children) {
g_list_free(list);
}
}
/*!
* \internal
* \brief Check whether an instance is compatible with a role and node
*
* \param[in] instance Clone instance or bundle replica container
* \param[in] node Instance must match this node
* \param[in] role If not pcmk_role_unknown, instance must match this role
* \param[in] current If true, compare instance's original node and role,
* otherwise compare assigned next node and role
*
* \return true if \p instance is compatible with \p node and \p role,
* otherwise false
*/
bool
pcmk__instance_matches(const pcmk_resource_t *instance, const pcmk_node_t *node,
enum rsc_role_e role, bool current)
{
pcmk_node_t *instance_node = NULL;
CRM_CHECK((instance != NULL) && (node != NULL), return false);
if ((role != pcmk_role_unknown)
&& (role != instance->private->fns->state(instance, current))) {
pcmk__rsc_trace(instance,
"%s is not a compatible instance (role is not %s)",
instance->id, pcmk_role_text(role));
return false;
}
if (!is_set_recursive(instance, pcmk__rsc_blocked, true)) {
// We only want instances that haven't failed
instance_node = instance->private->fns->location(instance, NULL,
current);
}
if (instance_node == NULL) {
pcmk__rsc_trace(instance,
"%s is not a compatible instance "
"(not assigned to a node)",
instance->id);
return false;
}
if (!pcmk__same_node(instance_node, node)) {
pcmk__rsc_trace(instance,
"%s is not a compatible instance "
"(assigned to %s not %s)",
instance->id, pcmk__node_name(instance_node),
pcmk__node_name(node));
return false;
}
return true;
}
#define display_role(r) \
(((r) == pcmk_role_unknown)? "matching" : pcmk_role_text(r))
/*!
* \internal
* \brief Find an instance that matches a given resource by node and role
*
* \param[in] match_rsc Resource that instance must match (for logging only)
* \param[in] rsc Clone or bundle resource to check for matching instance
* \param[in] node Instance must match this node
* \param[in] role If not pcmk_role_unknown, instance must match this role
* \param[in] current If true, compare instance's original node and role,
* otherwise compare assigned next node and role
*
* \return \p rsc instance matching \p node and \p role if any, otherwise NULL
*/
static pcmk_resource_t *
find_compatible_instance_on_node(const pcmk_resource_t *match_rsc,
const pcmk_resource_t *rsc,
const pcmk_node_t *node, enum rsc_role_e role,
bool current)
{
GList *instances = NULL;
instances = get_instance_list(rsc);
for (GList *iter = instances; iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = (pcmk_resource_t *) iter->data;
if (pcmk__instance_matches(instance, node, role, current)) {
pcmk__rsc_trace(match_rsc,
"Found %s %s instance %s compatible with %s on %s",
display_role(role), rsc->id, instance->id,
match_rsc->id, pcmk__node_name(node));
free_instance_list(rsc, instances); // Only frees list, not contents
return instance;
}
}
free_instance_list(rsc, instances);
pcmk__rsc_trace(match_rsc,
"No %s %s instance found compatible with %s on %s",
display_role(role), rsc->id, match_rsc->id,
pcmk__node_name(node));
return NULL;
}
/*!
* \internal
* \brief Find a clone instance or bundle container compatible with a resource
*
* \param[in] match_rsc Resource that instance must match
* \param[in] rsc Clone or bundle resource to check for matching instance
* \param[in] role If not pcmk_role_unknown, instance must match this role
* \param[in] current If true, compare instance's original node and role,
* otherwise compare assigned next node and role
*
* \return Compatible (by \p role and \p match_rsc location) instance of \p rsc
* if any, otherwise NULL
*/
pcmk_resource_t *
pcmk__find_compatible_instance(const pcmk_resource_t *match_rsc,
const pcmk_resource_t *rsc, enum rsc_role_e role,
bool current)
{
pcmk_resource_t *instance = NULL;
GList *nodes = NULL;
const pcmk_node_t *node = NULL;
GHashTable *allowed_nodes = match_rsc->private->allowed_nodes;
// If match_rsc has a node, check only that node
node = match_rsc->private->fns->location(match_rsc, NULL, current);
if (node != NULL) {
return find_compatible_instance_on_node(match_rsc, rsc, node, role,
current);
}
// Otherwise check for an instance matching any of match_rsc's allowed nodes
nodes = pcmk__sort_nodes(g_hash_table_get_values(allowed_nodes), NULL);
for (GList *iter = nodes; (iter != NULL) && (instance == NULL);
iter = iter->next) {
instance = find_compatible_instance_on_node(match_rsc, rsc,
(pcmk_node_t *) iter->data,
role, current);
}
if (instance == NULL) {
pcmk__rsc_debug(rsc, "No %s instance found compatible with %s",
rsc->id, match_rsc->id);
}
g_list_free(nodes);
return instance;
}
/*!
* \internal
* \brief Unassign an instance if mandatory ordering has no interleave match
*
* \param[in] first 'First' action in an ordering
* \param[in] then 'Then' action in an ordering
* \param[in,out] then_instance 'Then' instance that has no interleave match
* \param[in] type Group of enum pcmk__action_relation_flags
* \param[in] current If true, "then" action is stopped or demoted
*
* \return true if \p then_instance was unassigned, otherwise false
*/
static bool
unassign_if_mandatory(const pcmk_action_t *first, const pcmk_action_t *then,
pcmk_resource_t *then_instance, uint32_t type,
bool current)
{
// Allow "then" instance to go down even without an interleave match
if (current) {
pcmk__rsc_trace(then->rsc,
"%s has no instance to order before stopping "
"or demoting %s",
first->rsc->id, then_instance->id);
/* If the "first" action must be runnable, but there is no "first"
* instance, the "then" instance must not be allowed to come up.
*/
} else if (pcmk_any_flags_set(type, pcmk__ar_unrunnable_first_blocks
|pcmk__ar_first_implies_then)) {
pcmk__rsc_info(then->rsc,
"Inhibiting %s from being active "
"because there is no %s instance to interleave",
then_instance->id, first->rsc->id);
return pcmk__assign_resource(then_instance, NULL, true, true);
}
return false;
}
/*!
* \internal
* \brief Find first matching action for a clone instance or bundle container
*
* \param[in] action Action in an interleaved ordering
* \param[in] instance Clone instance or bundle container being interleaved
* \param[in] action_name Action to look for
* \param[in] node If not NULL, require action to be on this node
* \param[in] for_first If true, \p instance is the 'first' resource in the
* ordering, otherwise it is the 'then' resource
*
* \return First action for \p instance (or in some cases if \p instance is a
* bundle container, its containerized resource) that matches
* \p action_name and \p node if any, otherwise NULL
*/
static pcmk_action_t *
find_instance_action(const pcmk_action_t *action, const pcmk_resource_t *instance,
const char *action_name, const pcmk_node_t *node,
bool for_first)
{
const pcmk_resource_t *rsc = NULL;
pcmk_action_t *matching_action = NULL;
/* If instance is a bundle container, sometimes we should interleave the
* action for the container itself, and sometimes for the containerized
* resource.
*
* For example, given "start bundle A then bundle B", B likely requires the
* service inside A's container to be active, rather than just the
* container, so we should interleave the action for A's containerized
* resource. On the other hand, it's possible B's container itself requires
* something from A, so we should interleave the action for B's container.
*
* Essentially, for 'first', we should use the containerized resource for
* everything except stop, and for 'then', we should use the container for
* everything except promote and demote (which can only be performed on the
* containerized resource).
*/
if ((for_first && !pcmk__str_any_of(action->task, PCMK_ACTION_STOP,
PCMK_ACTION_STOPPED, NULL))
|| (!for_first && pcmk__str_any_of(action->task, PCMK_ACTION_PROMOTE,
PCMK_ACTION_PROMOTED,
PCMK_ACTION_DEMOTE,
PCMK_ACTION_DEMOTED, NULL))) {
rsc = pe__get_rsc_in_container(instance);
}
if (rsc == NULL) {
rsc = instance; // No containerized resource, use instance itself
} else {
node = NULL; // Containerized actions are on bundle-created guest
}
matching_action = find_first_action(rsc->private->actions, NULL,
action_name, node);
if (matching_action != NULL) {
return matching_action;
}
if (pcmk_is_set(instance->flags, pcmk__rsc_removed)
|| pcmk__str_any_of(action_name, PCMK_ACTION_STOP, PCMK_ACTION_DEMOTE,
NULL)) {
crm_trace("No %s action found for %s%s",
action_name,
pcmk_is_set(instance->flags, pcmk__rsc_removed)? "orphan " : "",
instance->id);
} else {
crm_err("No %s action found for %s to interleave (bug?)",
action_name, instance->id);
}
return NULL;
}
/*!
* \internal
* \brief Get the original action name of a bundle or clone action
*
* Given an action for a bundle or clone, get the original action name,
* mapping notify to the action being notified, and if the instances are
* primitives, mapping completion actions to the action that was completed
* (for example, stopped to stop).
*
* \param[in] action Clone or bundle action to check
*
* \return Original action name for \p action
*/
static const char *
orig_action_name(const pcmk_action_t *action)
{
// Any instance will do
const pcmk_resource_t *instance = action->rsc->private->children->data;
char *action_type = NULL;
const char *action_name = action->task;
- enum action_tasks orig_task = pcmk_action_unspecified;
+ enum pcmk__action_type orig_task = pcmk__action_unspecified;
if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
PCMK_ACTION_NOTIFIED, NULL)) {
// action->uuid is RSC_(confirmed-){pre,post}_notify_ACTION_INTERVAL
CRM_CHECK(parse_op_key(action->uuid, NULL, &action_type, NULL),
- return pcmk__action_text(pcmk_action_unspecified));
+ return pcmk__action_text(pcmk__action_unspecified));
action_name = strstr(action_type, "_notify_");
CRM_CHECK(action_name != NULL,
- return pcmk__action_text(pcmk_action_unspecified));
+ return pcmk__action_text(pcmk__action_unspecified));
action_name += strlen("_notify_");
}
orig_task = get_complex_task(instance, action_name);
free(action_type);
return pcmk__action_text(orig_task);
}
/*!
* \internal
* \brief Update two interleaved actions according to an ordering between them
*
* Given information about an ordering of two interleaved actions, update the
* actions' flags (and runnable_before members if appropriate) as appropriate
* for the ordering. Effects may cascade to other orderings involving the
* actions as well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk_action_optional to affect only
* mandatory actions, and pcmk_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
static uint32_t
update_interleaved_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t filter,
uint32_t type)
{
GList *instances = NULL;
uint32_t changed = pcmk__updated_none;
const char *orig_first_task = orig_action_name(first);
// Stops and demotes must be interleaved with instance on current node
bool current = pcmk__ends_with(first->uuid, "_" PCMK_ACTION_STOPPED "_0")
|| pcmk__ends_with(first->uuid,
"_" PCMK_ACTION_DEMOTED "_0");
// Update the specified actions for each "then" instance individually
instances = get_instance_list(then->rsc);
for (GList *iter = instances; iter != NULL; iter = iter->next) {
pcmk_resource_t *first_instance = NULL;
pcmk_resource_t *then_instance = iter->data;
pcmk_action_t *first_action = NULL;
pcmk_action_t *then_action = NULL;
// Find a "first" instance to interleave with this "then" instance
first_instance = pcmk__find_compatible_instance(then_instance,
first->rsc,
pcmk_role_unknown,
current);
if (first_instance == NULL) { // No instance can be interleaved
if (unassign_if_mandatory(first, then, then_instance, type,
current)) {
pcmk__set_updated_flags(changed, first, pcmk__updated_then);
}
continue;
}
first_action = find_instance_action(first, first_instance,
orig_first_task, node, true);
if (first_action == NULL) {
continue;
}
then_action = find_instance_action(then, then_instance, then->task,
node, false);
if (then_action == NULL) {
continue;
}
if (order_actions(first_action, then_action, type)) {
pcmk__set_updated_flags(changed, first,
pcmk__updated_first|pcmk__updated_then);
}
changed |= then_instance->private->cmds->update_ordered_actions(
first_action, then_action, node,
first_instance->private->cmds->action_flags(first_action, node),
filter, type, then->rsc->private->scheduler);
}
free_instance_list(then->rsc, instances);
return changed;
}
/*!
* \internal
* \brief Check whether two actions in an ordering can be interleaved
*
* \param[in] first 'First' action in the ordering
* \param[in] then 'Then' action in the ordering
*
* \return true if \p first and \p then can be interleaved, otherwise false
*/
static bool
can_interleave_actions(const pcmk_action_t *first, const pcmk_action_t *then)
{
bool interleave = false;
pcmk_resource_t *rsc = NULL;
if ((first->rsc == NULL) || (then->rsc == NULL)) {
crm_trace("Not interleaving %s with %s: not resource actions",
first->uuid, then->uuid);
return false;
}
if (first->rsc == then->rsc) {
crm_trace("Not interleaving %s with %s: same resource",
first->uuid, then->uuid);
return false;
}
if ((first->rsc->private->variant < pcmk__rsc_variant_clone)
|| (then->rsc->private->variant < pcmk__rsc_variant_clone)) {
crm_trace("Not interleaving %s with %s: not clones or bundles",
first->uuid, then->uuid);
return false;
}
if (pcmk__ends_with(then->uuid, "_stop_0")
|| pcmk__ends_with(then->uuid, "_demote_0")) {
rsc = first->rsc;
} else {
rsc = then->rsc;
}
interleave = crm_is_true(g_hash_table_lookup(rsc->private->meta,
PCMK_META_INTERLEAVE));
pcmk__rsc_trace(rsc, "'%s then %s' will %sbe interleaved (based on %s)",
first->uuid, then->uuid, (interleave? "" : "not "),
rsc->id);
return interleave;
}
/*!
* \internal
* \brief Update non-interleaved instance actions according to an ordering
*
* Given information about an ordering of two non-interleaved actions, update
* the actions' flags (and runnable_before members if appropriate) as
* appropriate for the ordering. Effects may cascade to other orderings
* involving the actions as well.
*
* \param[in,out] instance Clone instance or bundle container
* \param[in,out] first "First" action in ordering
* \param[in] then "Then" action in ordering (for \p instance's parent)
* \param[in] node If not NULL, limit scope of ordering to this node
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk_action_optional to affect only
* mandatory actions, and pcmk_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
static uint32_t
update_noninterleaved_actions(pcmk_resource_t *instance, pcmk_action_t *first,
const pcmk_action_t *then, const pcmk_node_t *node,
uint32_t flags, uint32_t filter, uint32_t type)
{
pcmk_action_t *instance_action = NULL;
pcmk_scheduler_t *scheduler = instance->private->scheduler;
uint32_t instance_flags = 0;
uint32_t changed = pcmk__updated_none;
// Check whether instance has an equivalent of "then" action
instance_action = find_first_action(instance->private->actions, NULL,
then->task, node);
if (instance_action == NULL) {
return changed;
}
// Check whether action is runnable
instance_flags = instance->private->cmds->action_flags(instance_action,
node);
if (!pcmk_is_set(instance_flags, pcmk_action_runnable)) {
return changed;
}
// If so, update actions for the instance
changed = instance->private->cmds->update_ordered_actions(first,
instance_action,
node, flags,
filter, type,
scheduler);
// Propagate any changes to later actions
if (pcmk_is_set(changed, pcmk__updated_then)) {
for (GList *after_iter = instance_action->actions_after;
after_iter != NULL; after_iter = after_iter->next) {
pcmk__related_action_t *after = after_iter->data;
pcmk__update_action_for_orderings(after->action, scheduler);
}
}
return changed;
}
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two clone or bundle actions, update
* the actions' flags (and runnable_before members if appropriate) as
* appropriate for the ordering. Effects may cascade to other orderings
* involving the actions as well.
*
* \param[in,out] first 'First' action in an ordering
* \param[in,out] then 'Then' action in an ordering
* \param[in] node If not NULL, limit scope of ordering to this node
* (only used when interleaving instances)
* \param[in] flags Action flags for \p first for ordering purposes
* \param[in] filter Action flags to limit scope of certain updates (may
* include pcmk_action_optional to affect only
* mandatory actions, and pcmk_action_runnable to
* affect only runnable actions)
* \param[in] type Group of enum pcmk__action_relation_flags to apply
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t
pcmk__instance_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
const pcmk_node_t *node, uint32_t flags,
uint32_t filter, uint32_t type,
pcmk_scheduler_t *scheduler)
{
CRM_ASSERT((first != NULL) && (then != NULL) && (scheduler != NULL));
if (then->rsc == NULL) {
return pcmk__updated_none;
} else if (can_interleave_actions(first, then)) {
return update_interleaved_actions(first, then, node, filter, type);
} else {
uint32_t changed = pcmk__updated_none;
GList *instances = get_instance_list(then->rsc);
// Update actions for the clone or bundle resource itself
changed |= pcmk__update_ordered_actions(first, then, node, flags,
filter, type, scheduler);
// Update the 'then' clone instances or bundle containers individually
for (GList *iter = instances; iter != NULL; iter = iter->next) {
pcmk_resource_t *instance = iter->data;
changed |= update_noninterleaved_actions(instance, first, then,
node, flags, filter, type);
}
free_instance_list(then->rsc, instances);
return changed;
}
}
#define pe__clear_action_summary_flags(flags, action, flag) do { \
flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
"Action summary", action->rsc->id, \
flags, flag, #flag); \
} while (0)
/*!
* \internal
* \brief Return action flags for a given clone or bundle action
*
* \param[in,out] action Action for a clone or bundle
* \param[in] instances Clone instances or bundle containers
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
*/
uint32_t
pcmk__collective_action_flags(pcmk_action_t *action, const GList *instances,
const pcmk_node_t *node)
{
bool any_runnable = false;
const char *action_name = orig_action_name(action);
// Set original assumptions (optional and runnable may be cleared below)
uint32_t flags = pcmk_action_optional
|pcmk_action_runnable
|pcmk_action_pseudo;
for (const GList *iter = instances; iter != NULL; iter = iter->next) {
const pcmk_resource_t *instance = iter->data;
const pcmk_node_t *instance_node = NULL;
pcmk_action_t *instance_action = NULL;
uint32_t instance_flags;
// Node is relevant only to primitive instances
if (pcmk__is_primitive(instance)) {
instance_node = node;
}
instance_action = find_first_action(instance->private->actions, NULL,
action_name, instance_node);
if (instance_action == NULL) {
pcmk__rsc_trace(action->rsc, "%s has no %s action on %s",
instance->id, action_name, pcmk__node_name(node));
continue;
}
pcmk__rsc_trace(action->rsc, "%s has %s for %s on %s",
instance->id, instance_action->uuid, action_name,
pcmk__node_name(node));
instance_flags = instance->private->cmds->action_flags(instance_action,
node);
// If any instance action is mandatory, so is the collective action
if (pcmk_is_set(flags, pcmk_action_optional)
&& !pcmk_is_set(instance_flags, pcmk_action_optional)) {
pcmk__rsc_trace(instance, "%s is mandatory because %s is",
action->uuid, instance_action->uuid);
pe__clear_action_summary_flags(flags, action,
pcmk_action_optional);
pcmk__clear_action_flags(action, pcmk_action_optional);
}
// If any instance action is runnable, so is the collective action
if (pcmk_is_set(instance_flags, pcmk_action_runnable)) {
any_runnable = true;
}
}
if (!any_runnable) {
pcmk__rsc_trace(action->rsc,
"%s is not runnable because no instance can run %s",
action->uuid, action_name);
pe__clear_action_summary_flags(flags, action, pcmk_action_runnable);
if (node == NULL) {
pcmk__clear_action_flags(action, pcmk_action_runnable);
}
}
return flags;
}
diff --git a/lib/pacemaker/pcmk_sched_remote.c b/lib/pacemaker/pcmk_sched_remote.c
index 131b6b2453..8a28fcd52e 100644
--- a/lib/pacemaker/pcmk_sched_remote.c
+++ b/lib/pacemaker/pcmk_sched_remote.c
@@ -1,732 +1,733 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <glib.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#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 pcmk__ar_guest_allowed with these convenience functions to
* exempt internally generated constraints from the prohibition of user
* constraints involving remote connection resources.
*
* The start ordering additionally uses pcmk__ar_unrunnable_first_blocks so that
* the specified action is not runnable if the start is not runnable.
*/
static inline void
order_start_then_action(pcmk_resource_t *first_rsc, pcmk_action_t *then_action,
uint32_t extra)
{
if ((first_rsc != NULL) && (then_action != NULL)) {
pcmk__new_ordering(first_rsc, start_key(first_rsc), NULL,
then_action->rsc, NULL, then_action,
pcmk__ar_guest_allowed
|pcmk__ar_unrunnable_first_blocks
|extra,
first_rsc->private->scheduler);
}
}
static inline void
order_action_then_stop(pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
uint32_t extra)
{
if ((first_action != NULL) && (then_rsc != NULL)) {
pcmk__new_ordering(first_action->rsc, NULL, first_action,
then_rsc, stop_key(then_rsc), NULL,
pcmk__ar_guest_allowed|extra,
then_rsc->private->scheduler);
}
}
static enum remote_connection_state
get_remote_node_state(const pcmk_node_t *node)
{
const pcmk_resource_t *remote_rsc = NULL;
const pcmk_node_t *cluster_node = NULL;
CRM_ASSERT(node != NULL);
remote_rsc = node->private->remote;
CRM_ASSERT(remote_rsc != NULL);
cluster_node = pcmk__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->private->next_role == pcmk_role_stopped)
|| (remote_rsc->private->assigned_node == 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, pcmk__rsc_failed)) {
/* Connection resource is cleanly stopped */
return remote_state_stopped;
}
/* Connection resource is failed */
if ((remote_rsc->private->next_role == pcmk_role_stopped)
&& (remote_rsc->private->remote_reconnect_ms > 0U)
&& pcmk_is_set(node->private->flags, pcmk__node_remote_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->private->active_nodes)
&& (remote_rsc->private->partial_migration_source != NULL)
&& (remote_rsc->private->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;
}
/*!
* \internal
* \brief Order actions on remote node relative to actions for the connection
*
* \param[in,out] action An action scheduled on a Pacemaker Remote node
*/
static void
apply_remote_ordering(pcmk_action_t *action)
{
pcmk_resource_t *remote_rsc = NULL;
- enum action_tasks task = pcmk__parse_action(action->task);
+ enum pcmk__action_type task = pcmk__parse_action(action->task);
enum remote_connection_state state = get_remote_node_state(action->node);
uint32_t order_opts = pcmk__ar_none;
if (action->rsc == NULL) {
return;
}
CRM_ASSERT(pcmk__is_pacemaker_remote_node(action->node));
remote_rsc = action->node->private->remote;
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, pcmk__rsc_failed)? "failed " : "",
remote_rsc->id, state2text(state));
if (pcmk__strcase_any_of(action->task, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
- /* Migration ops map to pcmk_action_unspecified, but we need to apply
+ /* Migration ops map to pcmk__action_unspecified, but we need to apply
* the same ordering as for stop or demote (see get_router_node()).
*/
- task = pcmk_action_stop;
+ task = pcmk__action_stop;
}
switch (task) {
- case pcmk_action_start:
- case pcmk_action_promote:
+ case pcmk__action_start:
+ case pcmk__action_promote:
order_opts = pcmk__ar_none;
if (state == remote_state_failed) {
/* Force recovery, by making this action required */
pcmk__set_relation_flags(order_opts,
pcmk__ar_first_implies_then);
}
/* Ensure connection is up before running this action */
order_start_then_action(remote_rsc, action, order_opts);
break;
- case pcmk_action_stop:
+ case pcmk__action_stop:
if (state == remote_state_alive) {
order_action_then_stop(action, remote_rsc,
pcmk__ar_then_implies_first);
} 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(remote_rsc->private->scheduler, action->node,
"resources are active but "
"connection is unrecoverable",
FALSE);
} else if (remote_rsc->private->next_role == pcmk_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,
pcmk__ar_then_implies_first);
} else {
/* The connection is going to be started somewhere else, so
* stop this resource after that completes.
*/
order_start_then_action(remote_rsc, action, pcmk__ar_none);
}
break;
- case pcmk_action_demote:
+ case pcmk__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, pcmk__ar_none);
} /* Otherwise we can rely on the stop ordering */
break;
default:
/* Wait for the connection resource to be up */
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,
pcmk__ar_first_implies_then);
} else {
pcmk_node_t *cluster_node = pcmk__current_node(remote_rsc);
- if ((task == pcmk_action_monitor) && (state == remote_state_failed)) {
+ if ((task == pcmk__action_monitor)
+ && (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(remote_rsc->private->scheduler, 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,
pcmk__ar_unrunnable_first_blocks);
} else {
order_start_then_action(remote_rsc, action, pcmk__ar_none);
}
}
break;
}
}
static void
apply_launcher_ordering(pcmk_action_t *action)
{
pcmk_resource_t *remote_rsc = NULL;
pcmk_resource_t *launcher = NULL;
- enum action_tasks task = pcmk__parse_action(action->task);
+ enum pcmk__action_type task = pcmk__parse_action(action->task);
CRM_ASSERT(action->rsc != NULL);
CRM_ASSERT(pcmk__is_pacemaker_remote_node(action->node));
remote_rsc = action->node->private->remote;
CRM_ASSERT(remote_rsc != NULL);
launcher = remote_rsc->private->launcher;
CRM_ASSERT(launcher != NULL);
if (pcmk_is_set(launcher->flags, pcmk__rsc_failed)) {
pe_fence_node(action->rsc->private->scheduler, 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, pcmk__rsc_failed)? "failed " : "",
remote_rsc->id,
pcmk_is_set(launcher->flags, pcmk__rsc_failed)? "failed " : "",
launcher->id);
if (pcmk__strcase_any_of(action->task, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
- /* Migration ops map to pcmk_action_unspecified, but we need to apply
+ /* Migration ops map to pcmk__action_unspecified, but we need to apply
* the same ordering as for stop or demote (see get_router_node()).
*/
- task = pcmk_action_stop;
+ task = pcmk__action_stop;
}
switch (task) {
- case pcmk_action_start:
- case pcmk_action_promote:
+ case pcmk__action_start:
+ case pcmk__action_promote:
// Force resource recovery if the launcher is recovered
order_start_then_action(launcher, action,
pcmk__ar_first_implies_then);
// Wait for the connection resource to be up, too
order_start_then_action(remote_rsc, action, pcmk__ar_none);
break;
- case pcmk_action_stop:
- case pcmk_action_demote:
+ case pcmk__action_stop:
+ case pcmk__action_demote:
if (pcmk_is_set(launcher->flags, pcmk__rsc_failed)) {
/* When the launcher representing a guest node fails, any stop
* or demote actions for resources running on the guest node
* are implied by the launcher 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 launcher's current role was
* stopped (otherwise we re-introduce an ordering loop when the
* connection is restarting).
*/
order_action_then_stop(action, remote_rsc, pcmk__ar_none);
}
break;
default:
/* Wait for the connection resource to be up */
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 != pcmk_action_unspecified) {
+ if (task != pcmk__action_unspecified) {
order_start_then_action(remote_rsc, action,
pcmk__ar_first_implies_then);
}
} else {
order_start_then_action(remote_rsc, action, pcmk__ar_none);
}
break;
}
}
/*!
* \internal
* \brief Order all relevant actions relative to remote connection actions
*
* \param[in,out] scheduler Scheduler data
*/
void
pcmk__order_remote_connection_actions(pcmk_scheduler_t *scheduler)
{
if (!pcmk_is_set(scheduler->flags, pcmk_sched_have_remote_nodes)) {
return;
}
crm_trace("Creating remote connection orderings");
for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = iter->data;
pcmk_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 (pcmk_is_set(action->rsc->flags, pcmk__rsc_is_remote_connection)
&& pcmk__str_eq(action->task, PCMK_ACTION_CLEAR_FAILCOUNT,
pcmk__str_none)) {
pcmk__new_ordering(action->rsc, NULL, action, action->rsc,
pcmk__op_key(action->rsc->id, PCMK_ACTION_START,
0),
NULL, pcmk__ar_ordered, scheduler);
continue;
}
// We are only interested in actions assigned to a node
if (action->node == NULL) {
continue;
}
if (!pcmk__is_pacemaker_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, pcmk_action_pseudo)) {
continue;
}
remote = action->node->private->remote;
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, PCMK_ACTION_START, pcmk__str_none)) {
for (GList *item = action->rsc->private->actions; item != NULL;
item = item->next) {
pcmk_action_t *rsc_action = item->data;
if (!pcmk__same_node(rsc_action->node, action->node)
&& pcmk__str_eq(rsc_action->task, PCMK_ACTION_STOP,
pcmk__str_none)) {
pcmk__new_ordering(remote, start_key(remote), NULL,
action->rsc, NULL, rsc_action,
pcmk__ar_ordered, scheduler);
}
}
}
/* 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 PCMK__XA_ROUTER_NODE as part of
* this logic rather than create_graph_action().
*/
if (remote->private->launcher != NULL) {
crm_trace("Container ordering for %s", action->uuid);
apply_launcher_ordering(action);
} else {
crm_trace("Remote ordering for %s", action->uuid);
apply_remote_ordering(action);
}
}
}
/*!
* \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(const pcmk_node_t *node)
{
return pcmk__is_remote_node(node) && (node->private->remote != 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(const pcmk_resource_t *rsc,
const pcmk_node_t *node)
{
return (rsc != NULL) && (rsc->private->launched != NULL) && (node != NULL)
&& (node->private->remote != NULL)
&& (node->private->remote->private->launcher == 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
*/
pcmk_node_t *
pcmk__connection_host_for_action(const pcmk_action_t *action)
{
pcmk_node_t *began_on = NULL;
pcmk_node_t *ended_on = NULL;
bool partial_migration = false;
const char *task = action->task;
pcmk_resource_t *remote = NULL;
if (pcmk__str_eq(task, PCMK_ACTION_STONITH, pcmk__str_none)
|| !pcmk__is_pacemaker_remote_node(action->node)) {
return NULL;
}
remote = action->node->private->remote;
CRM_ASSERT(remote != NULL);
began_on = pcmk__current_node(remote);
ended_on = remote->private->assigned_node;
if ((remote->private->launcher == NULL)
&& (remote->private->partial_migration_target != NULL)) {
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->private->name : "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->private->name : "none"),
partial_migration? " (partial migration)" : "");
return began_on;
}
if (pcmk__same_node(began_on, ended_on)) {
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->private->name : "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, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
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, PCMK_ACTION_CANCEL, PCMK_ACTION_STOP,
PCMK_ACTION_DEMOTE, PCMK_ACTION_MIGRATE_FROM,
PCMK_ACTION_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->private->name : "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->private->name : "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,out] rsc Resource to check
* \param[in,out] params Resource parameters evaluated per node
*/
void
pcmk__substitute_remote_addr(pcmk_resource_t *rsc, GHashTable *params)
{
const char *remote_addr = g_hash_table_lookup(params, PCMK_REMOTE_RA_ADDR);
if (pcmk__str_eq(remote_addr, "#uname", pcmk__str_none)) {
GHashTable *base = pe_rsc_params(rsc, NULL, rsc->private->scheduler);
remote_addr = g_hash_table_lookup(base, PCMK_REMOTE_RA_ADDR);
if (remote_addr != NULL) {
pcmk__insert_dup(params, PCMK_REMOTE_RA_ADDR, remote_addr);
}
}
}
/*!
* \brief Add special guest node meta-attributes to XML
*
* If a given action will be executed on a guest node, add the following as XML
* attributes (using meta-attribute naming):
* * The resource's \c PCMK_META_CONTAINER_ATTRIBUTE_TARGET meta-attribute
* (usually set only for bundles), as \c PCMK_META_CONTAINER_ATTRIBUTE_TARGET
* * The guest's physical host (current host for "down" actions, next host for
* "up" actions), as \c PCMK__META_PHYSICAL_HOST
*
* If the guest node has no physical host, then don't add either attribute.
*
* \param[in,out] args_xml XML to add attributes to
* \param[in] action Action to check
*/
void
pcmk__add_guest_meta_to_xml(xmlNode *args_xml, const pcmk_action_t *action)
{
const pcmk_node_t *guest = action->node;
const pcmk_node_t *host = NULL;
const pcmk_resource_t *launcher = NULL;
- enum action_tasks task;
+ enum pcmk__action_type task;
if (!pcmk__is_guest_or_bundle_node(guest)) {
return;
}
launcher = guest->private->remote->private->launcher;
task = pcmk__parse_action(action->task);
- if ((task == pcmk_action_notify) || (task == pcmk_action_notified)) {
+ if ((task == pcmk__action_notify) || (task == pcmk__action_notified)) {
task = pcmk__parse_action(g_hash_table_lookup(action->meta,
"notify_operation"));
}
switch (task) {
- case pcmk_action_stop:
- case pcmk_action_stopped:
- case pcmk_action_demote:
- case pcmk_action_demoted:
+ case pcmk__action_stop:
+ case pcmk__action_stopped:
+ case pcmk__action_demote:
+ case pcmk__action_demoted:
// "Down" actions take place on guest's current host
host = pcmk__current_node(launcher);
break;
- case pcmk_action_start:
- case pcmk_action_started:
- case pcmk_action_monitor:
- case pcmk_action_promote:
- case pcmk_action_promoted:
+ case pcmk__action_start:
+ case pcmk__action_started:
+ case pcmk__action_monitor:
+ case pcmk__action_promote:
+ case pcmk__action_promoted:
// "Up" actions take place on guest's next host
host = launcher->private->assigned_node;
break;
default:
break;
}
if (host != NULL) {
gpointer target =
g_hash_table_lookup(action->rsc->private->meta,
PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
hash2metafield((gpointer) PCMK_META_CONTAINER_ATTRIBUTE_TARGET,
target,
(gpointer) args_xml);
hash2metafield((gpointer) PCMK__META_PHYSICAL_HOST,
(gpointer) host->private->name,
(gpointer) args_xml);
}
}
diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c
index d524d85e16..ca08a46430 100644
--- a/lib/pengine/pe_actions.c
+++ b/lib/pengine/pe_actions.c
@@ -1,1809 +1,1809 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <glib.h>
#include <stdbool.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/scheduler_internal.h>
#include <crm/pengine/internal.h>
#include <crm/common/xml_internal.h>
#include "pe_status_private.h"
static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms);
static void
add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action)
{
if (scheduler->singletons == NULL) {
scheduler->singletons = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(scheduler->singletons, action->uuid, action);
}
static pcmk_action_t *
lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid)
{
if (scheduler->singletons == NULL) {
return NULL;
}
return g_hash_table_lookup(scheduler->singletons, action_uuid);
}
/*!
* \internal
* \brief Find an existing action that matches arguments
*
* \param[in] key Action key to match
* \param[in] rsc Resource to match (if any)
* \param[in] node Node to match (if any)
* \param[in] scheduler Scheduler data
*
* \return Existing action that matches arguments (or NULL if none)
*/
static pcmk_action_t *
find_existing_action(const char *key, const pcmk_resource_t *rsc,
const pcmk_node_t *node, const pcmk_scheduler_t *scheduler)
{
/* When rsc is NULL, it would be quicker to check scheduler->singletons,
* but checking all scheduler->actions takes the node into account.
*/
GList *actions = (rsc == NULL)? scheduler->actions : rsc->private->actions;
GList *matches = find_actions(actions, key, node);
pcmk_action_t *action = NULL;
if (matches == NULL) {
return NULL;
}
CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches));
action = matches->data;
g_list_free(matches);
return action;
}
/*!
* \internal
* \brief Find the XML configuration corresponding to a specific action key
*
* \param[in] rsc Resource to find action configuration for
* \param[in] key "RSC_ACTION_INTERVAL" of action to find
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
static xmlNode *
find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
for (xmlNode *operation = pcmk__xe_first_child(rsc->private->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
const char *config_name = NULL;
const char *interval_spec = NULL;
guint tmp_ms = 0U;
// @TODO This does not consider meta-attributes, rules, defaults, etc.
if (!include_disabled
&& (pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &tmp_ms);
if (tmp_ms != interval_ms) {
continue;
}
config_name = crm_element_value(operation, PCMK_XA_NAME);
if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) {
return operation;
}
}
return NULL;
}
/*!
* \internal
* \brief Find the XML configuration of a resource action
*
* \param[in] rsc Resource to find action configuration for
* \param[in] action_name Action name to search for
* \param[in] interval_ms Action interval (in milliseconds) to search for
* \param[in] include_disabled If false, do not return disabled actions
*
* \return XML configuration of desired action if any, otherwise NULL
*/
xmlNode *
pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, bool include_disabled)
{
xmlNode *action_config = NULL;
// Try requested action first
action_config = find_exact_action_config(rsc, action_name, interval_ms,
include_disabled);
// For migrate_to and migrate_from actions, retry with "migrate"
// @TODO This should be either documented or deprecated
if ((action_config == NULL)
&& pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO,
PCMK_ACTION_MIGRATE_FROM, NULL)) {
action_config = find_exact_action_config(rsc, "migrate", 0,
include_disabled);
}
return action_config;
}
/*!
* \internal
* \brief Create a new action object
*
* \param[in] key Action key
* \param[in] task Action name
* \param[in,out] rsc Resource that action is for (if any)
* \param[in] node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Newly allocated action
* \note This function takes ownership of \p key. It is the caller's
* responsibility to free the return value with pe_free_action().
*/
static pcmk_action_t *
new_action(char *key, const char *task, pcmk_resource_t *rsc,
const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = pcmk__assert_alloc(1, sizeof(pcmk_action_t));
action->rsc = rsc;
action->task = pcmk__str_copy(task);
action->uuid = key;
if (node) {
action->node = pe__copy_node(node);
}
if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) {
// Resource history deletion for a node can be done on the DC
pcmk__set_action_flags(action, pcmk_action_on_dc);
}
pcmk__set_action_flags(action, pcmk_action_runnable);
if (optional) {
pcmk__set_action_flags(action, pcmk_action_optional);
} else {
pcmk__clear_action_flags(action, pcmk_action_optional);
}
if (rsc == NULL) {
action->meta = pcmk__strkey_table(free, free);
} else {
guint interval_ms = 0;
parse_op_key(key, NULL, NULL, &interval_ms);
action->op_entry = pcmk__find_action_config(rsc, task, interval_ms,
true);
/* If the given key is for one of the many notification pseudo-actions
* (pre_notify_promote, etc.), the actual action name is "notify"
*/
if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) {
action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY,
0, true);
}
unpack_operation(action, action->op_entry, interval_ms);
}
pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s",
(optional? "optional" : "required"),
scheduler->action_id, key, task,
((rsc == NULL)? "no resource" : rsc->id),
pcmk__node_name(node));
action->id = scheduler->action_id++;
scheduler->actions = g_list_prepend(scheduler->actions, action);
if (rsc == NULL) {
add_singleton(scheduler, action);
} else {
rsc->private->actions = g_list_prepend(rsc->private->actions, action);
}
return action;
}
/*!
* \internal
* \brief Unpack a resource's action-specific instance parameters
*
* \param[in] action_xml XML of action's configuration in CIB (if any)
* \param[in,out] node_attrs Table of node attributes (for rule evaluation)
* \param[in,out] scheduler Cluster working set (for rule evaluation)
*
* \return Newly allocated hash table of action-specific instance parameters
*/
GHashTable *
pcmk__unpack_action_rsc_params(const xmlNode *action_xml,
GHashTable *node_attrs,
pcmk_scheduler_t *scheduler)
{
GHashTable *params = pcmk__strkey_table(free, free);
pe_rule_eval_data_t rule_data = {
.node_hash = node_attrs,
.now = scheduler->now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe__unpack_dataset_nvpairs(action_xml, PCMK_XE_INSTANCE_ATTRIBUTES,
&rule_data, params, NULL,
FALSE, scheduler);
return params;
}
/*!
* \internal
* \brief Update an action's optional flag
*
* \param[in,out] action Action to update
* \param[in] optional Requested optional status
*/
static void
update_action_optional(pcmk_action_t *action, gboolean optional)
{
// Force a non-recurring action to be optional if its resource is unmanaged
if ((action->rsc != NULL) && (action->node != NULL)
&& !pcmk_is_set(action->flags, pcmk_action_pseudo)
&& !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)
&& (g_hash_table_lookup(action->meta, PCMK_META_INTERVAL) == NULL)) {
pcmk__rsc_debug(action->rsc,
"%s on %s is optional (%s is unmanaged)",
action->uuid, pcmk__node_name(action->node),
action->rsc->id);
pcmk__set_action_flags(action, pcmk_action_optional);
// We shouldn't clear runnable here because ... something
// Otherwise require the action if requested
} else if (!optional) {
pcmk__clear_action_flags(action, pcmk_action_optional);
}
}
static enum pe_quorum_policy
effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
{
enum pe_quorum_policy policy = scheduler->no_quorum_policy;
if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) {
policy = pcmk_no_quorum_ignore;
} else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) {
switch (rsc->private->orig_role) {
case pcmk_role_promoted:
case pcmk_role_unpromoted:
if (rsc->private->next_role > pcmk_role_unpromoted) {
pe__set_next_role(rsc, pcmk_role_unpromoted,
PCMK_OPT_NO_QUORUM_POLICY "=demote");
}
policy = pcmk_no_quorum_ignore;
break;
default:
policy = pcmk_no_quorum_stop;
break;
}
}
return policy;
}
/*!
* \internal
* \brief Update a resource action's runnable flag
*
* \param[in,out] action Action to update
* \param[in,out] scheduler Scheduler data
*
* \note This may also schedule fencing if a stop is unrunnable.
*/
static void
update_resource_action_runnable(pcmk_action_t *action,
pcmk_scheduler_t *scheduler)
{
pcmk_resource_t *rsc = action->rsc;
if (pcmk_is_set(action->flags, pcmk_action_pseudo)) {
return;
}
if (action->node == NULL) {
pcmk__rsc_trace(rsc, "%s is unrunnable (unallocated)", action->uuid);
pcmk__clear_action_flags(action, pcmk_action_runnable);
} else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
&& !(action->node->details->online)
&& (!pcmk__is_guest_or_bundle_node(action->node)
|| pcmk_is_set(action->node->private->flags,
pcmk__node_remote_reset))) {
pcmk__clear_action_flags(action, pcmk_action_runnable);
do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)",
action->uuid, pcmk__node_name(action->node));
if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)
&& pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)
&& !(action->node->details->unclean)) {
pe_fence_node(scheduler, action->node, "stop is unrunnable", false);
}
} else if (!pcmk_is_set(action->flags, pcmk_action_on_dc)
&& action->node->details->pending) {
pcmk__clear_action_flags(action, pcmk_action_runnable);
do_crm_log(LOG_WARNING,
"Action %s on %s is unrunnable (node is pending)",
action->uuid, pcmk__node_name(action->node));
} else if (action->needs == pcmk_requires_nothing) {
pe_action_set_reason(action, NULL, TRUE);
if (pcmk__is_guest_or_bundle_node(action->node)
&& !pe_can_fence(scheduler, action->node)) {
/* An action that requires nothing usually does not require any
* fencing in order to be runnable. However, there is an exception:
* such an action cannot be completed if it is on a guest node whose
* host is unclean and cannot be fenced.
*/
pcmk__rsc_debug(rsc,
"%s on %s is unrunnable "
"(node's host cannot be fenced)",
action->uuid, pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk_action_runnable);
} else {
pcmk__rsc_trace(rsc,
"%s on %s does not require fencing or quorum",
action->uuid, pcmk__node_name(action->node));
pcmk__set_action_flags(action, pcmk_action_runnable);
}
} else {
switch (effective_quorum_policy(rsc, scheduler)) {
case pcmk_no_quorum_stop:
pcmk__rsc_debug(rsc, "%s on %s is unrunnable (no quorum)",
action->uuid, pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk_action_runnable);
pe_action_set_reason(action, "no quorum", true);
break;
case pcmk_no_quorum_freeze:
if (!rsc->private->fns->active(rsc, TRUE)
|| (rsc->private->next_role > rsc->private->orig_role)) {
pcmk__rsc_debug(rsc, "%s on %s is unrunnable (no quorum)",
action->uuid,
pcmk__node_name(action->node));
pcmk__clear_action_flags(action, pcmk_action_runnable);
pe_action_set_reason(action, "quorum freeze", true);
}
break;
default:
//pe_action_set_reason(action, NULL, TRUE);
pcmk__set_action_flags(action, pcmk_action_runnable);
break;
}
}
}
static bool
valid_stop_on_fail(const char *value)
{
return !pcmk__strcase_any_of(value,
PCMK_VALUE_STANDBY, PCMK_VALUE_DEMOTE,
PCMK_VALUE_STOP, NULL);
}
/*!
* \internal
* \brief Validate (and possibly reset) resource action's on_fail meta-attribute
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] action_config Action configuration XML from CIB (if any)
* \param[in,out] meta Table of action meta-attributes
*/
static void
validate_on_fail(const pcmk_resource_t *rsc, const char *action_name,
const xmlNode *action_config, GHashTable *meta)
{
const char *name = NULL;
const char *role = NULL;
const char *interval_spec = NULL;
const char *value = g_hash_table_lookup(meta, PCMK_META_ON_FAIL);
guint interval_ms = 0U;
// Stop actions can only use certain on-fail values
if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)
&& !valid_stop_on_fail(value)) {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s stop "
"action to default value because '%s' is not "
"allowed for stop", rsc->id, value);
g_hash_table_remove(meta, PCMK_META_ON_FAIL);
return;
}
/* Demote actions default on-fail to the on-fail value for the first
* recurring monitor for the promoted role (if any).
*/
if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none)
&& (value == NULL)) {
/* @TODO This does not consider promote options set in a meta-attribute
* block (which may have rules that need to be evaluated) rather than
* XML properties.
*/
for (xmlNode *operation = pcmk__xe_first_child(rsc->private->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
const char *promote_on_fail = NULL;
/* We only care about explicit on-fail (if promote uses default, so
* can demote)
*/
promote_on_fail = crm_element_value(operation, PCMK_META_ON_FAIL);
if (promote_on_fail == NULL) {
continue;
}
// We only care about recurring monitors for the promoted role
name = crm_element_value(operation, PCMK_XA_NAME);
role = crm_element_value(operation, PCMK_XA_ROLE);
if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL)) {
continue;
}
interval_spec = crm_element_value(operation, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// We only care about enabled monitors
if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
/* Demote actions can't default to
* PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE
*/
if (pcmk__str_eq(promote_on_fail, PCMK_VALUE_DEMOTE,
pcmk__str_casei)) {
continue;
}
// Use value from first applicable promote action found
pcmk__insert_dup(meta, PCMK_META_ON_FAIL, promote_on_fail);
}
return;
}
if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none)
&& !pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) {
pcmk__insert_dup(meta, PCMK_META_ON_FAIL, PCMK_VALUE_IGNORE);
return;
}
// PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE is allowed only for certain actions
if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
name = crm_element_value(action_config, PCMK_XA_NAME);
role = crm_element_value(action_config, PCMK_XA_ROLE);
interval_spec = crm_element_value(action_config, PCMK_META_INTERVAL);
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none)
&& ((interval_ms == 0U)
|| !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none)
|| !pcmk__strcase_any_of(role, PCMK_ROLE_PROMOTED,
PCMK__ROLE_PROMOTED_LEGACY, NULL))) {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s %s "
"action to default value because 'demote' is not "
"allowed for it", rsc->id, name);
g_hash_table_remove(meta, PCMK_META_ON_FAIL);
return;
}
}
}
static int
unpack_timeout(const char *value)
{
long long timeout_ms = crm_get_msec(value);
if (timeout_ms <= 0) {
timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
return (int) QB_MIN(timeout_ms, INT_MAX);
}
// true if value contains valid, non-NULL interval origin for recurring op
static bool
unpack_interval_origin(const char *value, const xmlNode *xml_obj,
guint interval_ms, const crm_time_t *now,
long long *start_delay)
{
long long result = 0;
guint interval_sec = interval_ms / 1000;
crm_time_t *origin = NULL;
// Ignore unspecified values and non-recurring operations
if ((value == NULL) || (interval_ms == 0) || (now == NULL)) {
return false;
}
// Parse interval origin from text
origin = crm_time_new(value);
if (origin == NULL) {
pcmk__config_err("Ignoring '" PCMK_META_INTERVAL_ORIGIN "' for "
"operation '%s' because '%s' is not valid",
pcmk__s(pcmk__xe_id(xml_obj), "(missing ID)"), value);
return false;
}
// Get seconds since origin (negative if origin is in the future)
result = crm_time_get_seconds(now) - crm_time_get_seconds(origin);
crm_time_free(origin);
// Calculate seconds from closest interval to now
result = result % interval_sec;
// Calculate seconds remaining until next interval
result = ((result <= 0)? 0 : interval_sec) - result;
crm_info("Calculated a start delay of %llds for operation '%s'",
result, pcmk__s(pcmk__xe_id(xml_obj), "(unspecified)"));
if (start_delay != NULL) {
*start_delay = result * 1000; // milliseconds
}
return true;
}
static int
unpack_start_delay(const char *value, GHashTable *meta)
{
long long start_delay_ms = 0;
if (value == NULL) {
return 0;
}
start_delay_ms = crm_get_msec(value);
start_delay_ms = QB_MIN(start_delay_ms, INT_MAX);
if (start_delay_ms < 0) {
start_delay_ms = 0;
}
if (meta != NULL) {
g_hash_table_replace(meta, strdup(PCMK_META_START_DELAY),
pcmk__itoa(start_delay_ms));
}
return (int) start_delay_ms;
}
/*!
* \internal
* \brief Find a resource's most frequent recurring monitor
*
* \param[in] rsc Resource to check
*
* \return Operation XML configured for most frequent recurring monitor for
* \p rsc (if any)
*/
static xmlNode *
most_frequent_monitor(const pcmk_resource_t *rsc)
{
guint min_interval_ms = G_MAXUINT;
xmlNode *op = NULL;
for (xmlNode *operation = pcmk__xe_first_child(rsc->private->ops_xml,
PCMK_XE_OP, NULL, NULL);
operation != NULL; operation = pcmk__xe_next_same(operation)) {
bool enabled = false;
guint interval_ms = 0U;
const char *interval_spec = crm_element_value(operation,
PCMK_META_INTERVAL);
// We only care about enabled recurring monitors
if (!pcmk__str_eq(crm_element_value(operation, PCMK_XA_NAME),
PCMK_ACTION_MONITOR, pcmk__str_none)) {
continue;
}
pcmk_parse_interval_spec(interval_spec, &interval_ms);
if (interval_ms == 0U) {
continue;
}
// @TODO This does not consider meta-attributes, rules, defaults, etc.
if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED,
&enabled) == pcmk_rc_ok) && !enabled) {
continue;
}
if (interval_ms < min_interval_ms) {
min_interval_ms = interval_ms;
op = operation;
}
}
return op;
}
/*!
* \internal
* \brief Unpack action meta-attributes
*
* \param[in,out] rsc Resource that action is for
* \param[in] node Node that action is on
* \param[in] action_name Action name
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] action_config Action XML configuration from CIB (if any)
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds) from its CIB XML
* configuration (including defaults).
*
* \return Newly allocated hash table with normalized action meta-attributes
*/
GHashTable *
pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *action_name, guint interval_ms,
const xmlNode *action_config)
{
GHashTable *meta = NULL;
const char *timeout_spec = NULL;
const char *str = NULL;
pe_rsc_eval_data_t rsc_rule_data = {
.standard = crm_element_value(rsc->private->xml, PCMK_XA_CLASS),
.provider = crm_element_value(rsc->private->xml, PCMK_XA_PROVIDER),
.agent = crm_element_value(rsc->private->xml, PCMK_XA_TYPE),
};
pe_op_eval_data_t op_rule_data = {
.op_name = action_name,
.interval = interval_ms,
};
pe_rule_eval_data_t rule_data = {
/* @COMPAT Support for node attribute expressions in operation
* meta-attributes (whether in the operation configuration or operation
* defaults) is deprecated. When we can break behavioral backward
* compatibility, drop this line.
*/
.node_hash = (node == NULL)? NULL : node->private->attrs,
.now = rsc->private->scheduler->now,
.match_data = NULL,
.rsc_data = &rsc_rule_data,
.op_data = &op_rule_data,
};
meta = pcmk__strkey_table(free, free);
// Cluster-wide <op_defaults> <meta_attributes>
pe__unpack_dataset_nvpairs(rsc->private->scheduler->op_defaults,
PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL,
FALSE, rsc->private->scheduler);
// Derive default timeout for probes from recurring monitor timeouts
if (pcmk_is_probe(action_name, interval_ms)) {
xmlNode *min_interval_mon = most_frequent_monitor(rsc);
if (min_interval_mon != NULL) {
/* @TODO This does not consider timeouts set in
* PCMK_XE_META_ATTRIBUTES blocks (which may also have rules that
* need to be evaluated).
*/
timeout_spec = crm_element_value(min_interval_mon,
PCMK_META_TIMEOUT);
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting default timeout for %s probe to "
"most frequent monitor's timeout '%s'",
rsc->id, timeout_spec);
pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
}
}
}
if (action_config != NULL) {
// <op> <meta_attributes> take precedence over defaults
pe__unpack_dataset_nvpairs(action_config, PCMK_XE_META_ATTRIBUTES,
&rule_data, meta, NULL, TRUE,
rsc->private->scheduler);
/* Anything set as an <op> XML property has highest precedence.
* This ensures we use the name and interval from the <op> tag.
* (See below for the only exception, fence device start/probe timeout.)
*/
for (xmlAttrPtr attr = action_config->properties;
attr != NULL; attr = attr->next) {
pcmk__insert_dup(meta, (const char *) attr->name,
pcmk__xml_attr_value(attr));
}
}
g_hash_table_remove(meta, PCMK_XA_ID);
// Normalize interval to milliseconds
if (interval_ms > 0) {
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_INTERVAL),
crm_strdup_printf("%u", interval_ms));
} else {
g_hash_table_remove(meta, PCMK_META_INTERVAL);
}
/* Timeout order of precedence (highest to lowest):
* 1. pcmk_monitor_timeout resource parameter (only for starts and probes
* when rsc has pcmk_ra_cap_fence_params; this gets used for recurring
* monitors via the executor instead)
* 2. timeout configured in <op> (with <op timeout> taking precedence over
* <op> <meta_attributes>)
* 3. timeout configured in <op_defaults> <meta_attributes>
* 4. PCMK_DEFAULT_ACTION_TIMEOUT_MS
*/
// Check for pcmk_monitor_timeout
if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard),
pcmk_ra_cap_fence_params)
&& (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)
|| pcmk_is_probe(action_name, interval_ms))) {
GHashTable *params = pe_rsc_params(rsc, node, rsc->private->scheduler);
timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout");
if (timeout_spec != NULL) {
pcmk__rsc_trace(rsc,
"Setting timeout for %s %s to "
"pcmk_monitor_timeout (%s)",
rsc->id, action_name, timeout_spec);
pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec);
}
}
// Normalize timeout to positive milliseconds
timeout_spec = g_hash_table_lookup(meta, PCMK_META_TIMEOUT);
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_TIMEOUT),
pcmk__itoa(unpack_timeout(timeout_spec)));
// Ensure on-fail has a valid value
validate_on_fail(rsc, action_name, action_config, meta);
// Normalize PCMK_META_START_DELAY
str = g_hash_table_lookup(meta, PCMK_META_START_DELAY);
if (str != NULL) {
unpack_start_delay(str, meta);
} else {
long long start_delay = 0;
str = g_hash_table_lookup(meta, PCMK_META_INTERVAL_ORIGIN);
if (unpack_interval_origin(str, action_config, interval_ms,
rsc->private->scheduler->now,
&start_delay)) {
g_hash_table_insert(meta, pcmk__str_copy(PCMK_META_START_DELAY),
crm_strdup_printf("%lld", start_delay));
}
}
return meta;
}
/*!
* \internal
* \brief Determine an action's quorum and fencing dependency
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action being unpacked
*
* \return Quorum and fencing dependency appropriate to action
*/
enum rsc_start_requirement
pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name)
{
const char *value = NULL;
enum rsc_start_requirement requires = pcmk_requires_nothing;
CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires);
if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START,
PCMK_ACTION_PROMOTE, NULL)) {
value = "nothing (not start or promote)";
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
requires = pcmk_requires_fencing;
value = "fencing";
} else if (pcmk_is_set(rsc->flags, pcmk__rsc_needs_quorum)) {
requires = pcmk_requires_quorum;
value = "quorum";
} else {
value = "nothing";
}
pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value);
return requires;
}
/*!
* \internal
* \brief Parse action failure response from a user-provided string
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Name of action
* \param[in] interval_ms Action interval (in milliseconds)
* \param[in] value User-provided configuration value for on-fail
*
* \return Action failure response parsed from \p text
*/
enum action_fail_response
pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name,
guint interval_ms, const char *value)
{
const char *desc = NULL;
bool needs_remote_reset = false;
enum action_fail_response on_fail = pcmk_on_fail_ignore;
const pcmk_scheduler_t *scheduler = NULL;
// There's no enum value for unknown or invalid, so assert
CRM_ASSERT((rsc != NULL) && (action_name != NULL));
scheduler = rsc->private->scheduler;
if (value == NULL) {
// Use default
} else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
on_fail = pcmk_on_fail_block;
desc = "block";
} else if (pcmk__str_eq(value, PCMK_VALUE_FENCE, pcmk__str_casei)) {
if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
on_fail = pcmk_on_fail_fence_node;
desc = "node fencing";
} else {
pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for "
"%s of %s to 'stop' because 'fence' is not "
"valid when fencing is disabled",
action_name, rsc->id);
on_fail = pcmk_on_fail_stop;
desc = "stop resource";
}
} else if (pcmk__str_eq(value, PCMK_VALUE_STANDBY, pcmk__str_casei)) {
on_fail = pcmk_on_fail_standby_node;
desc = "node standby";
} else if (pcmk__strcase_any_of(value,
PCMK_VALUE_IGNORE, PCMK_VALUE_NOTHING,
NULL)) {
desc = "ignore";
} else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) {
on_fail = pcmk_on_fail_ban;
desc = "force migration";
} else if (pcmk__str_eq(value, PCMK_VALUE_STOP, pcmk__str_casei)) {
on_fail = pcmk_on_fail_stop;
desc = "stop resource";
} else if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) {
on_fail = pcmk_on_fail_restart;
desc = "restart (and possibly migrate)";
} else if (pcmk__str_eq(value, PCMK_VALUE_RESTART_CONTAINER,
pcmk__str_casei)) {
if (rsc->private->launcher == NULL) {
pcmk__rsc_debug(rsc,
"Using default " PCMK_META_ON_FAIL " for %s "
"of %s because it does not have a launcher",
action_name, rsc->id);
} else {
on_fail = pcmk_on_fail_restart_container;
desc = "restart container (and possibly migrate)";
}
} else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
on_fail = pcmk_on_fail_demote;
desc = "demote instance";
} else {
pcmk__config_err("Using default '" PCMK_META_ON_FAIL "' for "
"%s of %s because '%s' is not valid",
action_name, rsc->id, value);
}
/* Remote node connections are handled specially. Failures that result
* in dropping an active connection must result in fencing. The only
* failures that don't are probes and starts. The user can explicitly set
* PCMK_META_ON_FAIL=PCMK_VALUE_FENCE to fence after start failures.
*/
if (pcmk_is_set(rsc->flags, pcmk__rsc_is_remote_connection)
&& pcmk__is_remote_node(pcmk_find_node(scheduler, rsc->id))
&& !pcmk_is_probe(action_name, interval_ms)
&& !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) {
needs_remote_reset = true;
if (!pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
desc = NULL; // Force default for unmanaged connections
}
}
if (desc != NULL) {
// Explicit value used, default not needed
} else if (rsc->private->launcher != NULL) {
on_fail = pcmk_on_fail_restart_container;
desc = "restart container (and possibly migrate) (default)";
} else if (needs_remote_reset) {
if (pcmk_is_set(rsc->flags, pcmk__rsc_managed)) {
if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
desc = "fence remote node (default)";
} else {
desc = "recover remote node connection (default)";
}
on_fail = pcmk_on_fail_reset_remote;
} else {
on_fail = pcmk_on_fail_stop;
desc = "stop unmanaged remote node (enforcing default)";
}
} else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) {
if (pcmk_is_set(scheduler->flags, pcmk_sched_fencing_enabled)) {
on_fail = pcmk_on_fail_fence_node;
desc = "resource fence (default)";
} else {
on_fail = pcmk_on_fail_block;
desc = "resource block (default)";
}
} else {
on_fail = pcmk_on_fail_restart;
desc = "restart (and possibly migrate) (default)";
}
pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s",
pcmk__readable_interval(interval_ms), action_name,
rsc->id, desc);
return on_fail;
}
/*!
* \internal
* \brief Determine a resource's role after failure of an action
*
* \param[in] rsc Resource that action is for
* \param[in] action_name Action name
* \param[in] on_fail Failure handling for action
* \param[in] meta Unpacked action meta-attributes
*
* \return Resource role that results from failure of action
*/
enum rsc_role_e
pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name,
enum action_fail_response on_fail, GHashTable *meta)
{
const char *value = NULL;
enum rsc_role_e role = pcmk_role_unknown;
// Set default for role after failure specially in certain circumstances
switch (on_fail) {
case pcmk_on_fail_stop:
role = pcmk_role_stopped;
break;
case pcmk_on_fail_reset_remote:
if (rsc->private->remote_reconnect_ms != 0U) {
role = pcmk_role_stopped;
}
break;
default:
break;
}
// @COMPAT Check for explicitly configured role (deprecated)
value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE);
if (value != NULL) {
pcmk__warn_once(pcmk__wo_role_after,
"Support for " PCMK__META_ROLE_AFTER_FAILURE " is "
"deprecated and will be removed in a future release");
if (role == pcmk_role_unknown) {
role = pcmk_parse_role(value);
if (role == pcmk_role_unknown) {
pcmk__config_err("Ignoring invalid value %s for "
PCMK__META_ROLE_AFTER_FAILURE,
value);
}
}
}
if (role == pcmk_role_unknown) {
// Use default
if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
role = pcmk_role_unpromoted;
} else {
role = pcmk_role_started;
}
}
pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s",
rsc->id, action_name, pcmk_role_text(role));
return role;
}
/*!
* \internal
* \brief Unpack action configuration
*
* Unpack a resource action's meta-attributes (normalizing the interval,
* timeout, and start delay values as integer milliseconds), requirements, and
* failure policy from its CIB XML configuration (including defaults).
*
* \param[in,out] action Resource action to unpack into
* \param[in] xml_obj Action configuration XML (NULL for defaults only)
* \param[in] interval_ms How frequently to perform the operation
*/
static void
unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj,
guint interval_ms)
{
const char *value = NULL;
action->meta = pcmk__unpack_action_meta(action->rsc, action->node,
action->task, interval_ms, xml_obj);
action->needs = pcmk__action_requires(action->rsc, action->task);
value = g_hash_table_lookup(action->meta, PCMK_META_ON_FAIL);
action->on_fail = pcmk__parse_on_fail(action->rsc, action->task,
interval_ms, value);
action->fail_role = pcmk__role_after_failure(action->rsc, action->task,
action->on_fail, action->meta);
}
/*!
* \brief Create or update an action object
*
* \param[in,out] rsc Resource that action is for (if any)
* \param[in,out] key Action key (must be non-NULL)
* \param[in] task Action name (must be non-NULL)
* \param[in] on_node Node that action is on (if any)
* \param[in] optional Whether action should be considered optional
* \param[in,out] scheduler Scheduler data
*
* \return Action object corresponding to arguments (guaranteed not to be
* \c NULL)
* \note This function takes ownership of (and might free) \p key, and
* \p scheduler takes ownership of the returned action (the caller should
* not free it).
*/
pcmk_action_t *
custom_action(pcmk_resource_t *rsc, char *key, const char *task,
const pcmk_node_t *on_node, gboolean optional,
pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = NULL;
CRM_ASSERT((key != NULL) && (task != NULL) && (scheduler != NULL));
action = find_existing_action(key, rsc, on_node, scheduler);
if (action == NULL) {
action = new_action(key, task, rsc, on_node, optional, scheduler);
} else {
free(key);
}
update_action_optional(action, optional);
if (rsc != NULL) {
/* An action can be initially created with a NULL node, and later have
* the node added via find_existing_action() (above) -> find_actions().
* That is why the extra parameters are unpacked here rather than in
* new_action().
*/
if ((action->node != NULL) && (action->op_entry != NULL)
&& !pcmk_is_set(action->flags, pcmk_action_attrs_evaluated)) {
GHashTable *attrs = action->node->private->attrs;
if (action->extra != NULL) {
g_hash_table_destroy(action->extra);
}
action->extra = pcmk__unpack_action_rsc_params(action->op_entry,
attrs, scheduler);
pcmk__set_action_flags(action, pcmk_action_attrs_evaluated);
}
update_resource_action_runnable(action, scheduler);
}
if (action->extra == NULL) {
action->extra = pcmk__strkey_table(free, free);
}
return action;
}
pcmk_action_t *
get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler)
{
pcmk_action_t *op = lookup_singleton(scheduler, name);
if (op == NULL) {
op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler);
pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
}
return op;
}
static GList *
find_unfencing_devices(GList *candidates, GList *matches)
{
for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *candidate = gIter->data;
if (candidate->private->children != NULL) {
matches = find_unfencing_devices(candidate->private->children,
matches);
} else if (!pcmk_is_set(candidate->flags, pcmk__rsc_fence_device)) {
continue;
} else if (pcmk_is_set(candidate->flags, pcmk__rsc_needs_unfencing)) {
matches = g_list_prepend(matches, candidate);
} else if (pcmk__str_eq(g_hash_table_lookup(candidate->private->meta,
PCMK_STONITH_PROVIDES),
PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
matches = g_list_prepend(matches, candidate);
}
}
return matches;
}
static int
node_priority_fencing_delay(const pcmk_node_t *node,
const pcmk_scheduler_t *scheduler)
{
int member_count = 0;
int online_count = 0;
int top_priority = 0;
int lowest_priority = 0;
GList *gIter = NULL;
// PCMK_OPT_PRIORITY_FENCING_DELAY is disabled
if (scheduler->priority_fencing_delay <= 0) {
return 0;
}
/* No need to request a delay if the fencing target is not a normal cluster
* member, for example if it's a remote node or a guest node. */
if (node->private->variant != pcmk__node_variant_cluster) {
return 0;
}
// No need to request a delay if the fencing target is in our partition
if (node->details->online) {
return 0;
}
for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) {
pcmk_node_t *n = gIter->data;
if (n->private->variant != pcmk__node_variant_cluster) {
continue;
}
member_count ++;
if (n->details->online) {
online_count++;
}
if (member_count == 1
|| n->private->priority > top_priority) {
top_priority = n->private->priority;
}
if (member_count == 1
|| n->private->priority < lowest_priority) {
lowest_priority = n->private->priority;
}
}
// No need to delay if we have more than half of the cluster members
if (online_count > member_count / 2) {
return 0;
}
/* All the nodes have equal priority.
* Any configured corresponding `pcmk_delay_base/max` will be applied. */
if (lowest_priority == top_priority) {
return 0;
}
if (node->private->priority < top_priority) {
return 0;
}
return scheduler->priority_fencing_delay;
}
pcmk_action_t *
pe_fence_op(pcmk_node_t *node, const char *op, bool optional,
const char *reason, bool priority_delay,
pcmk_scheduler_t *scheduler)
{
char *op_key = NULL;
pcmk_action_t *stonith_op = NULL;
if(op == NULL) {
op = scheduler->stonith_action;
}
op_key = crm_strdup_printf("%s-%s-%s",
PCMK_ACTION_STONITH, node->private->name, op);
stonith_op = lookup_singleton(scheduler, op_key);
if(stonith_op == NULL) {
stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node,
TRUE, scheduler);
pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE, node->private->name);
pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE_UUID,
node->private->id);
pcmk__insert_meta(stonith_op, PCMK__META_STONITH_ACTION, op);
if (pcmk_is_set(scheduler->flags, pcmk_sched_enable_unfencing)) {
/* Extra work to detect device changes
*/
GString *digests_all = g_string_sized_new(1024);
GString *digests_secure = g_string_sized_new(1024);
GList *matches = find_unfencing_devices(scheduler->resources, NULL);
for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) {
pcmk_resource_t *match = gIter->data;
const char *agent = g_hash_table_lookup(match->private->meta,
PCMK_XA_TYPE);
pcmk__op_digest_t *data = NULL;
data = pe__compare_fencing_digest(match, agent, node,
scheduler);
if (data->rc == pcmk__digest_mismatch) {
optional = FALSE;
crm_notice("Unfencing node %s because the definition of "
"%s changed", pcmk__node_name(node), match->id);
if (!pcmk__is_daemon && scheduler->priv != NULL) {
pcmk__output_t *out = scheduler->priv;
out->info(out,
"notice: Unfencing node %s because the "
"definition of %s changed",
pcmk__node_name(node), match->id);
}
}
pcmk__g_strcat(digests_all,
match->id, ":", agent, ":",
data->digest_all_calc, ",", NULL);
pcmk__g_strcat(digests_secure,
match->id, ":", agent, ":",
data->digest_secure_calc, ",", NULL);
}
pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_ALL,
digests_all->str);
g_string_free(digests_all, TRUE);
pcmk__insert_dup(stonith_op->meta, PCMK__META_DIGESTS_SECURE,
digests_secure->str);
g_string_free(digests_secure, TRUE);
}
} else {
free(op_key);
}
if (scheduler->priority_fencing_delay > 0
/* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY
* applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as
* an indicator.
*/
&& (priority_delay
/* The priority delay needs to be recalculated if this function has
* been called by schedule_fencing_and_shutdowns() after node
* priority has already been calculated by native_add_running().
*/
|| g_hash_table_lookup(stonith_op->meta,
PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) {
/* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if
* it's 0 for the targeting node. So that it takes precedence over
* any possible `pcmk_delay_base/max`.
*/
char *delay_s = pcmk__itoa(node_priority_fencing_delay(node,
scheduler));
g_hash_table_insert(stonith_op->meta,
strdup(PCMK_OPT_PRIORITY_FENCING_DELAY),
delay_s);
}
if(optional == FALSE && pe_can_fence(scheduler, node)) {
pcmk__clear_action_flags(stonith_op, pcmk_action_optional);
pe_action_set_reason(stonith_op, reason, false);
} else if(reason && stonith_op->reason == NULL) {
stonith_op->reason = strdup(reason);
}
return stonith_op;
}
void
pe_free_action(pcmk_action_t *action)
{
if (action == NULL) {
return;
}
g_list_free_full(action->actions_before, free);
g_list_free_full(action->actions_after, free);
if (action->extra) {
g_hash_table_destroy(action->extra);
}
if (action->meta) {
g_hash_table_destroy(action->meta);
}
free(action->cancel_task);
free(action->reason);
free(action->task);
free(action->uuid);
free(action->node);
free(action);
}
-enum action_tasks
+enum pcmk__action_type
get_complex_task(const pcmk_resource_t *rsc, const char *name)
{
- enum action_tasks task = pcmk__parse_action(name);
+ enum pcmk__action_type task = pcmk__parse_action(name);
if (pcmk__is_primitive(rsc)) {
switch (task) {
- case pcmk_action_stopped:
- case pcmk_action_started:
- case pcmk_action_demoted:
- case pcmk_action_promoted:
+ case pcmk__action_stopped:
+ case pcmk__action_started:
+ case pcmk__action_demoted:
+ case pcmk__action_promoted:
crm_trace("Folding %s back into its atomic counterpart for %s",
name, rsc->id);
--task;
break;
default:
break;
}
}
return task;
}
/*!
* \internal
* \brief Find first matching action in a list
*
* \param[in] input List of actions to search
* \param[in] uuid If not NULL, action must have this UUID
* \param[in] task If not NULL, action must have this action name
* \param[in] on_node If not NULL, action must be on this node
*
* \return First action in list that matches criteria, or NULL if none
*/
pcmk_action_t *
find_first_action(const GList *input, const char *uuid, const char *task,
const pcmk_node_t *on_node)
{
CRM_CHECK(uuid || task, return NULL);
for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) {
continue;
} else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
return action;
} else if (action->node == NULL) {
continue;
} else if (pcmk__same_node(on_node, action->node)) {
return action;
}
}
return NULL;
}
GList *
find_actions(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *gIter = input;
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
for (; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) {
continue;
} else if (on_node == NULL) {
crm_trace("Action %s matches (ignoring node)", key);
result = g_list_prepend(result, action);
} else if (action->node == NULL) {
crm_trace("Action %s matches (unallocated, assigning to %s)",
key, pcmk__node_name(on_node));
action->node = pe__copy_node(on_node);
result = g_list_prepend(result, action);
} else if (pcmk__same_node(on_node, action->node)) {
crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
GList *
find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node)
{
GList *result = NULL;
CRM_CHECK(key != NULL, return NULL);
if (on_node == NULL) {
return NULL;
}
for (GList *gIter = input; gIter != NULL; gIter = gIter->next) {
pcmk_action_t *action = (pcmk_action_t *) gIter->data;
if ((action->node != NULL)
&& pcmk__str_eq(key, action->uuid, pcmk__str_casei)
&& pcmk__same_node(on_node, action->node)) {
crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node));
result = g_list_prepend(result, action);
}
}
return result;
}
/*!
* \brief Find all actions of given type for a resource
*
* \param[in] rsc Resource to search
* \param[in] node Find only actions scheduled on this node
* \param[in] task Action name to search for
* \param[in] require_node If TRUE, NULL node or action node will not match
*
* \return List of actions found (or NULL if none)
* \note If node is not NULL and require_node is FALSE, matching actions
* without a node will be assigned to node.
*/
GList *
pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node,
const char *task, bool require_node)
{
GList *result = NULL;
char *key = pcmk__op_key(rsc->id, task, 0);
if (require_node) {
result = find_actions_exact(rsc->private->actions, key, node);
} else {
result = find_actions(rsc->private->actions, key, node);
}
free(key);
return result;
}
/*!
* \internal
* \brief Create an action reason string based on the action itself
*
* \param[in] action Action to create reason string for
* \param[in] flag Action flag that was cleared
*
* \return Newly allocated string suitable for use as action reason
* \note It is the caller's responsibility to free() the result.
*/
char *
pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag)
{
const char *change = NULL;
switch (flag) {
case pcmk_action_runnable:
change = "unrunnable";
break;
case pcmk_action_migratable:
change = "unmigrateable";
break;
case pcmk_action_optional:
change = "required";
break;
default:
// Bug: caller passed unsupported flag
CRM_CHECK(change != NULL, change = "");
break;
}
return crm_strdup_printf("%s%s%s %s", change,
(action->rsc == NULL)? "" : " ",
(action->rsc == NULL)? "" : action->rsc->id,
action->task);
}
void pe_action_set_reason(pcmk_action_t *action, const char *reason,
bool overwrite)
{
if (action->reason != NULL && overwrite) {
pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'",
action->uuid, action->reason,
pcmk__s(reason, "(none)"));
} else if (action->reason == NULL) {
pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'",
action->uuid, pcmk__s(reason, "(none)"));
} else {
// crm_assert(action->reason != NULL && !overwrite);
return;
}
pcmk__str_update(&action->reason, reason);
}
/*!
* \internal
* \brief Create an action to clear a resource's history from CIB
*
* \param[in,out] rsc Resource to clear
* \param[in] node Node to clear history on
*/
void
pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node)
{
CRM_ASSERT((rsc != NULL) && (node != NULL));
custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0),
PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->private->scheduler);
}
#define sort_return(an_int, why) do { \
free(a_uuid); \
free(b_uuid); \
crm_trace("%s (%d) %c %s (%d) : %s", \
a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \
b_xml_id, b_call_id, why); \
return an_int; \
} while(0)
int
pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
bool same_node_default)
{
int a_call_id = -1;
int b_call_id = -1;
char *a_uuid = NULL;
char *b_uuid = NULL;
const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID);
const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID);
const char *a_node = crm_element_value(xml_a, PCMK__META_ON_NODE);
const char *b_node = crm_element_value(xml_b, PCMK__META_ON_NODE);
bool same_node = true;
/* @COMPAT The on_node attribute was added to last_failure as of 1.1.13 (via
* 8b3ca1c) and the other entries as of 1.1.12 (via 0b07b5c).
*
* In case that any of the PCMK__XE_LRM_RSC_OP entries doesn't have on_node
* attribute, we need to explicitly tell whether the two operations are on
* the same node.
*/
if (a_node == NULL || b_node == NULL) {
same_node = same_node_default;
} else {
same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei);
}
if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) {
/* We have duplicate PCMK__XE_LRM_RSC_OP entries in the status
* section which is unlikely to be a good thing
* - we can handle it easily enough, but we need to get
* to the bottom of why it's happening.
*/
pcmk__config_err("Duplicate " PCMK__XE_LRM_RSC_OP " entries named %s",
a_xml_id);
sort_return(0, "duplicate");
}
crm_element_value_int(xml_a, PCMK__XA_CALL_ID, &a_call_id);
crm_element_value_int(xml_b, PCMK__XA_CALL_ID, &b_call_id);
if (a_call_id == -1 && b_call_id == -1) {
/* both are pending ops so it doesn't matter since
* stops are never pending
*/
sort_return(0, "pending");
} else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) {
sort_return(-1, "call id");
} else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) {
sort_return(1, "call id");
} else if (a_call_id >= 0 && b_call_id >= 0
&& (!same_node || a_call_id == b_call_id)) {
/* The op and last_failed_op are the same. Order on
* PCMK_XA_LAST_RC_CHANGE.
*/
time_t last_a = -1;
time_t last_b = -1;
crm_element_value_epoch(xml_a, PCMK_XA_LAST_RC_CHANGE, &last_a);
crm_element_value_epoch(xml_b, PCMK_XA_LAST_RC_CHANGE, &last_b);
crm_trace("rc-change: %lld vs %lld",
(long long) last_a, (long long) last_b);
if (last_a >= 0 && last_a < last_b) {
sort_return(-1, "rc-change");
} else if (last_b >= 0 && last_a > last_b) {
sort_return(1, "rc-change");
}
sort_return(0, "rc-change");
} else {
/* One of the inputs is a pending operation.
* Attempt to use PCMK__XA_TRANSITION_MAGIC to determine its age relative
* to the other.
*/
int a_id = -1;
int b_id = -1;
const char *a_magic = crm_element_value(xml_a,
PCMK__XA_TRANSITION_MAGIC);
const char *b_magic = crm_element_value(xml_b,
PCMK__XA_TRANSITION_MAGIC);
CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic"));
if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic a");
}
if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL,
NULL)) {
sort_return(0, "bad magic b");
}
/* try to determine the relative age of the operation...
* some pending operations (e.g. a start) may have been superseded
* by a subsequent stop
*
* [a|b]_id == -1 means it's a shutdown operation and _always_ comes last
*/
if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) {
/*
* some of the logic in here may be redundant...
*
* if the UUID from the TE doesn't match then one better
* be a pending operation.
* pending operations don't survive between elections and joins
* because we query the LRM directly
*/
if (b_call_id == -1) {
sort_return(-1, "transition + call");
} else if (a_call_id == -1) {
sort_return(1, "transition + call");
}
} else if ((a_id >= 0 && a_id < b_id) || b_id == -1) {
sort_return(-1, "transition");
} else if ((b_id >= 0 && a_id > b_id) || a_id == -1) {
sort_return(1, "transition");
}
}
/* we should never end up here */
CRM_CHECK(FALSE, sort_return(0, "default"));
}
gint
sort_op_by_callid(gconstpointer a, gconstpointer b)
{
const xmlNode *xml_a = a;
const xmlNode *xml_b = b;
return pe__is_newer_op(xml_a, xml_b, true);
}
/*!
* \internal
* \brief Create a new pseudo-action for a resource
*
* \param[in,out] rsc Resource to create action for
* \param[in] task Action name
* \param[in] optional Whether action should be considered optional
* \param[in] runnable Whethe action should be considered runnable
*
* \return New action object corresponding to arguments
*/
pcmk_action_t *
pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional,
bool runnable)
{
pcmk_action_t *action = NULL;
CRM_ASSERT((rsc != NULL) && (task != NULL));
action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL,
optional, rsc->private->scheduler);
pcmk__set_action_flags(action, pcmk_action_pseudo);
if (runnable) {
pcmk__set_action_flags(action, pcmk_action_runnable);
}
return action;
}
/*!
* \internal
* \brief Add the expected result to an action
*
* \param[in,out] action Action to add expected result to
* \param[in] expected_result Expected result to add
*
* \note This is more efficient than calling pcmk__insert_meta().
*/
void
pe__add_action_expected_result(pcmk_action_t *action, int expected_result)
{
CRM_ASSERT((action != NULL) && (action->meta != NULL));
g_hash_table_insert(action->meta, pcmk__str_copy(PCMK__META_OP_TARGET_RC),
pcmk__itoa(expected_result));
}
diff --git a/lib/pengine/pe_notif.c b/lib/pengine/pe_notif.c
index fe7c7e8b99..93ee3d4371 100644
--- a/lib/pengine/pe_notif.c
+++ b/lib/pengine/pe_notif.c
@@ -1,1014 +1,1014 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/xml.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include "pe_status_private.h"
typedef struct notify_entry_s {
const pcmk_resource_t *rsc;
const pcmk_node_t *node;
} notify_entry_t;
/*!
* \internal
* \brief Compare two notification entries
*
* Compare two notification entries, where the one with the alphabetically first
* resource name (or if equal, node ID) sorts as first, with NULL sorting as
* less than non-NULL.
*
* \param[in] a First notification entry to compare
* \param[in] b Second notification entry to compare
*
* \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1
*/
static gint
compare_notify_entries(gconstpointer a, gconstpointer b)
{
int tmp;
const notify_entry_t *entry_a = a;
const notify_entry_t *entry_b = b;
// NULL a or b is not actually possible
if ((entry_a == NULL) && (entry_b == NULL)) {
return 0;
}
if (entry_a == NULL) {
return 1;
}
if (entry_b == NULL) {
return -1;
}
// NULL resources sort first
if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) {
return 0;
}
if (entry_a->rsc == NULL) {
return 1;
}
if (entry_b->rsc == NULL) {
return -1;
}
// Compare resource names
tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id);
if (tmp != 0) {
return tmp;
}
// Otherwise NULL nodes sort first
if ((entry_a->node == NULL) && (entry_b->node == NULL)) {
return 0;
}
if (entry_a->node == NULL) {
return 1;
}
if (entry_b->node == NULL) {
return -1;
}
// Finally, compare node IDs
return strcmp(entry_a->node->private->id, entry_b->node->private->id);
}
/*!
* \internal
* \brief Duplicate a notification entry
*
* \param[in] entry Entry to duplicate
*
* \return Newly allocated duplicate of \p entry
* \note It is the caller's responsibility to free the return value.
*/
static notify_entry_t *
dup_notify_entry(const notify_entry_t *entry)
{
notify_entry_t *dup = pcmk__assert_alloc(1, sizeof(notify_entry_t));
dup->rsc = entry->rsc;
dup->node = entry->node;
return dup;
}
/*!
* \internal
* \brief Given a list of nodes, create strings with node names
*
* \param[in] list List of nodes (as pcmk_node_t *)
* \param[out] all_node_names If not NULL, will be set to space-separated list
* of the names of all nodes in \p list
* \param[out] host_node_names Same as \p all_node_names, except active
* guest nodes will list the name of their host
*
* \note The caller is responsible for freeing the output argument values using
* \p g_string_free().
*/
static void
get_node_names(const GList *list, GString **all_node_names,
GString **host_node_names)
{
if (all_node_names != NULL) {
*all_node_names = NULL;
}
if (host_node_names != NULL) {
*host_node_names = NULL;
}
for (const GList *iter = list; iter != NULL; iter = iter->next) {
const pcmk_node_t *node = (const pcmk_node_t *) iter->data;
if (node->private->name == NULL) {
continue;
}
// Always add to list of all node names
if (all_node_names != NULL) {
pcmk__add_word(all_node_names, 1024, node->private->name);
}
// Add to host node name list if appropriate
if (host_node_names != NULL) {
if (pcmk__is_guest_or_bundle_node(node)) {
const pcmk_resource_t *launcher = NULL;
launcher = node->private->remote->private->launcher;
if (launcher->private->active_nodes != NULL) {
node = pcmk__current_node(launcher);
if (node->private->name == NULL) {
continue;
}
}
}
pcmk__add_word(host_node_names, 1024, node->private->name);
}
}
if ((all_node_names != NULL) && (*all_node_names == NULL)) {
*all_node_names = g_string_new(" ");
}
if ((host_node_names != NULL) && (*host_node_names == NULL)) {
*host_node_names = g_string_new(" ");
}
}
/*!
* \internal
* \brief Create strings of instance and node names from notification entries
*
* \param[in,out] list List of notification entries (will be sorted here)
* \param[out] rsc_names If not NULL, will be set to space-separated list
* of clone instances from \p list
* \param[out] node_names If not NULL, will be set to space-separated list
* of node names from \p list
*
* \return (Possibly new) head of sorted \p list
* \note The caller is responsible for freeing the output argument values using
* \p g_list_free_full() and \p g_string_free().
*/
static GList *
notify_entries_to_strings(GList *list, GString **rsc_names,
GString **node_names)
{
const char *last_rsc_id = NULL;
// Initialize output lists to NULL
if (rsc_names != NULL) {
*rsc_names = NULL;
}
if (node_names != NULL) {
*node_names = NULL;
}
// Sort input list for user-friendliness (and ease of filtering duplicates)
list = g_list_sort(list, compare_notify_entries);
for (GList *gIter = list; gIter != NULL; gIter = gIter->next) {
notify_entry_t *entry = (notify_entry_t *) gIter->data;
// Entry must have a resource (with ID)
CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL)
&& (entry->rsc->id != NULL));
if ((entry == NULL) || (entry->rsc == NULL)
|| (entry->rsc->id == NULL)) {
continue;
}
// Entry must have a node unless listing inactive resources
CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL));
if ((node_names != NULL) && (entry->node == NULL)) {
continue;
}
// Don't add duplicates of a particular clone instance
if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) {
continue;
}
last_rsc_id = entry->rsc->id;
if (rsc_names != NULL) {
pcmk__add_word(rsc_names, 1024, entry->rsc->id);
}
if ((node_names != NULL) && (entry->node->private->name != NULL)) {
pcmk__add_word(node_names, 1024, entry->node->private->name);
}
}
// If there are no entries, return "empty" lists
if ((rsc_names != NULL) && (*rsc_names == NULL)) {
*rsc_names = g_string_new(" ");
}
if ((node_names != NULL) && (*node_names == NULL)) {
*node_names = g_string_new(" ");
}
return list;
}
/*!
* \internal
* \brief Copy a meta-attribute into a notify action
*
* \param[in] key Name of meta-attribute to copy
* \param[in] value Value of meta-attribute to copy
* \param[in,out] user_data Notify action to copy into
*/
static void
copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data)
{
pcmk_action_t *notify = (pcmk_action_t *) user_data;
/* Any existing meta-attributes (for example, the action timeout) are for
* the notify action itself, so don't override those.
*/
if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) {
return;
}
pcmk__insert_dup(notify->meta, (const char *) key, (const char *) value);
}
static void
add_notify_data_to_action_meta(const notify_data_t *n_data,
pcmk_action_t *action)
{
for (const GSList *item = n_data->keys; item; item = item->next) {
const pcmk_nvpair_t *nvpair = (const pcmk_nvpair_t *) item->data;
pcmk__insert_meta(action, nvpair->name, nvpair->value);
}
}
/*!
* \internal
* \brief Create a new notify pseudo-action for a clone resource
*
* \param[in,out] rsc Clone resource that notification is for
* \param[in] action Action to use in notify action key
* \param[in] notif_action PCMK_ACTION_NOTIFY or PCMK_ACTION_NOTIFIED
* \param[in] notif_type "pre", "post", "confirmed-pre", "confirmed-post"
*
* \return Newly created notify pseudo-action
*/
static pcmk_action_t *
new_notify_pseudo_action(pcmk_resource_t *rsc, const pcmk_action_t *action,
const char *notif_action, const char *notif_type)
{
pcmk_action_t *notify = NULL;
notify = custom_action(rsc,
pcmk__notify_key(rsc->id, notif_type, action->task),
notif_action, NULL,
pcmk_is_set(action->flags, pcmk_action_optional),
rsc->private->scheduler);
pcmk__set_action_flags(notify, pcmk_action_pseudo);
pcmk__insert_meta(notify, "notify_key_type", notif_type);
pcmk__insert_meta(notify, "notify_key_operation", action->task);
return notify;
}
/*!
* \internal
* \brief Create a new notify action for a clone instance
*
* \param[in,out] rsc Clone instance that notification is for
* \param[in] node Node that notification is for
* \param[in,out] op Action that notification is for
* \param[in,out] notify_done Parent pseudo-action for notifications complete
* \param[in] n_data Notification values to add to action meta-data
*
* \return Newly created notify action
*/
static pcmk_action_t *
new_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
pcmk_action_t *op, pcmk_action_t *notify_done,
const notify_data_t *n_data)
{
char *key = NULL;
pcmk_action_t *notify_action = NULL;
const char *value = NULL;
const char *task = NULL;
const char *skip_reason = NULL;
CRM_CHECK((rsc != NULL) && (node != NULL), return NULL);
// Ensure we have all the info we need
if (op == NULL) {
skip_reason = "no action";
} else if (notify_done == NULL) {
skip_reason = "no parent notification";
} else if (!node->details->online) {
skip_reason = "node offline";
} else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
skip_reason = "original action not runnable";
}
if (skip_reason != NULL) {
pcmk__rsc_trace(rsc, "Skipping notify action for %s on %s: %s",
rsc->id, pcmk__node_name(node), skip_reason);
return NULL;
}
value = g_hash_table_lookup(op->meta, "notify_type"); // "pre" or "post"
task = g_hash_table_lookup(op->meta, "notify_operation"); // original action
pcmk__rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)",
rsc->id, pcmk__node_name(node), value, task);
// Create the notify action
key = pcmk__notify_key(rsc->id, value, task);
notify_action = custom_action(rsc, key, op->task, node,
pcmk_is_set(op->flags, pcmk_action_optional),
rsc->private->scheduler);
// Add meta-data to notify action
g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action);
add_notify_data_to_action_meta(n_data, notify_action);
// Order notify after original action and before parent notification
order_actions(op, notify_action, pcmk__ar_ordered);
order_actions(notify_action, notify_done, pcmk__ar_ordered);
return notify_action;
}
/*!
* \internal
* \brief Create a new "post-" notify action for a clone instance
*
* \param[in,out] rsc Clone instance that notification is for
* \param[in] node Node that notification is for
* \param[in,out] n_data Notification values to add to action meta-data
*/
static void
new_post_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node,
notify_data_t *n_data)
{
pcmk_action_t *notify = NULL;
CRM_ASSERT(n_data != NULL);
// Create the "post-" notify action for specified instance
notify = new_notify_action(rsc, node, n_data->post, n_data->post_done,
n_data);
if (notify != NULL) {
notify->priority = PCMK_SCORE_INFINITY;
}
// Order recurring monitors after all "post-" notifications complete
if (n_data->post_done == NULL) {
return;
}
for (GList *iter = rsc->private->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *mon = (pcmk_action_t *) iter->data;
const char *interval_ms_s = NULL;
interval_ms_s = g_hash_table_lookup(mon->meta, PCMK_META_INTERVAL);
if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)
|| pcmk__str_eq(mon->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
continue; // Not a recurring monitor
}
order_actions(n_data->post_done, mon, pcmk__ar_ordered);
}
}
/*!
* \internal
* \brief Create and order notification pseudo-actions for a clone action
*
* In addition to the actual notify actions needed for each clone instance,
* clone notifications also require pseudo-actions to provide ordering points
* in the notification process. This creates the notification data, along with
* appropriate pseudo-actions and their orderings.
*
* For example, the ordering sequence for starting a clone is:
*
* "pre-" notify pseudo-action for clone
* -> "pre-" notify actions for each clone instance
* -> "pre-" notifications complete pseudo-action for clone
* -> start actions for each clone instance
* -> "started" pseudo-action for clone
* -> "post-" notify pseudo-action for clone
* -> "post-" notify actions for each clone instance
* -> "post-" notifications complete pseudo-action for clone
*
* \param[in,out] rsc Clone that notifications are for
* \param[in] task Name of action that notifications are for
* \param[in,out] action If not NULL, create a "pre-" pseudo-action ordered
* before a "pre-" complete pseudo-action, ordered
* before this action
* \param[in,out] complete If not NULL, create a "post-" pseudo-action ordered
* after this action, and a "post-" complete
* pseudo-action ordered after that
*
* \return Newly created notification data
*/
notify_data_t *
pe__action_notif_pseudo_ops(pcmk_resource_t *rsc, const char *task,
pcmk_action_t *action, pcmk_action_t *complete)
{
notify_data_t *n_data = NULL;
if (!pcmk_is_set(rsc->flags, pcmk__rsc_notify)) {
return NULL;
}
n_data = pcmk__assert_alloc(1, sizeof(notify_data_t));
n_data->action = task;
if (action != NULL) { // Need "pre-" pseudo-actions
// Create "pre-" notify pseudo-action for clone
n_data->pre = new_notify_pseudo_action(rsc, action, PCMK_ACTION_NOTIFY,
"pre");
pcmk__set_action_flags(n_data->pre, pcmk_action_runnable);
pcmk__insert_meta(n_data->pre, "notify_type", "pre");
pcmk__insert_meta(n_data->pre, "notify_operation", n_data->action);
// Create "pre-" notifications complete pseudo-action for clone
n_data->pre_done = new_notify_pseudo_action(rsc, action,
PCMK_ACTION_NOTIFIED,
"confirmed-pre");
pcmk__set_action_flags(n_data->pre_done, pcmk_action_runnable);
pcmk__insert_meta(n_data->pre_done, "notify_type", "pre");
pcmk__insert_meta(n_data->pre_done, "notify_operation", n_data->action);
// Order "pre-" -> "pre-" complete -> original action
order_actions(n_data->pre, n_data->pre_done, pcmk__ar_ordered);
order_actions(n_data->pre_done, action, pcmk__ar_ordered);
}
if (complete != NULL) { // Need "post-" pseudo-actions
// Create "post-" notify pseudo-action for clone
n_data->post = new_notify_pseudo_action(rsc, complete,
PCMK_ACTION_NOTIFY, "post");
n_data->post->priority = PCMK_SCORE_INFINITY;
if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
pcmk__set_action_flags(n_data->post, pcmk_action_runnable);
} else {
pcmk__clear_action_flags(n_data->post, pcmk_action_runnable);
}
pcmk__insert_meta(n_data->post, "notify_type", "post");
pcmk__insert_meta(n_data->post, "notify_operation", n_data->action);
// Create "post-" notifications complete pseudo-action for clone
n_data->post_done = new_notify_pseudo_action(rsc, complete,
PCMK_ACTION_NOTIFIED,
"confirmed-post");
n_data->post_done->priority = PCMK_SCORE_INFINITY;
if (pcmk_is_set(complete->flags, pcmk_action_runnable)) {
pcmk__set_action_flags(n_data->post_done, pcmk_action_runnable);
} else {
pcmk__clear_action_flags(n_data->post_done, pcmk_action_runnable);
}
pcmk__insert_meta(n_data->post_done, "notify_type", "post");
pcmk__insert_meta(n_data->post_done,
"notify_operation", n_data->action);
// Order original action complete -> "post-" -> "post-" complete
order_actions(complete, n_data->post, pcmk__ar_first_implies_then);
order_actions(n_data->post, n_data->post_done,
pcmk__ar_first_implies_then);
}
// If we created both, order "pre-" complete -> "post-"
if ((action != NULL) && (complete != NULL)) {
order_actions(n_data->pre_done, n_data->post, pcmk__ar_ordered);
}
return n_data;
}
/*!
* \internal
* \brief Create a new notification entry
*
* \param[in] rsc Resource for notification
* \param[in] node Node for notification
*
* \return Newly allocated notification entry
* \note The caller is responsible for freeing the return value.
*/
static notify_entry_t *
new_notify_entry(const pcmk_resource_t *rsc, const pcmk_node_t *node)
{
notify_entry_t *entry = pcmk__assert_alloc(1, sizeof(notify_entry_t));
entry->rsc = rsc;
entry->node = node;
return entry;
}
/*!
* \internal
* \brief Add notification data for resource state and optionally actions
*
* \param[in] rsc Clone or clone instance being notified
* \param[in] activity Whether to add notification entries for actions
* \param[in,out] n_data Notification data for clone
*/
static void
collect_resource_data(const pcmk_resource_t *rsc, bool activity,
notify_data_t *n_data)
{
const GList *iter = NULL;
notify_entry_t *entry = NULL;
const pcmk_node_t *node = NULL;
if (n_data == NULL) {
return;
}
if (n_data->allowed_nodes == NULL) {
n_data->allowed_nodes = rsc->private->allowed_nodes;
}
// If this is a clone, call recursively for each instance
if (rsc->private->children != NULL) {
for (iter = rsc->private->children; iter != NULL; iter = iter->next) {
const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data;
collect_resource_data(child, activity, n_data);
}
return;
}
// This is a notification for a single clone instance
if (rsc->private->active_nodes != NULL) {
node = rsc->private->active_nodes->data; // First is sufficient
}
entry = new_notify_entry(rsc, node);
// Add notification indicating the resource state
switch (rsc->private->orig_role) {
case pcmk_role_stopped:
n_data->inactive = g_list_prepend(n_data->inactive, entry);
break;
case pcmk_role_started:
n_data->active = g_list_prepend(n_data->active, entry);
break;
case pcmk_role_unpromoted:
n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry);
n_data->active = g_list_prepend(n_data->active,
dup_notify_entry(entry));
break;
case pcmk_role_promoted:
n_data->promoted = g_list_prepend(n_data->promoted, entry);
n_data->active = g_list_prepend(n_data->active,
dup_notify_entry(entry));
break;
default:
pcmk__sched_err("Resource %s role on %s (%s) is not supported for "
"notifications (bug?)",
rsc->id, pcmk__node_name(node),
pcmk_role_text(rsc->private->orig_role));
free(entry);
break;
}
if (!activity) {
return;
}
// Add notification entries for each of the resource's actions
for (iter = rsc->private->actions; iter != NULL; iter = iter->next) {
const pcmk_action_t *op = (const pcmk_action_t *) iter->data;
if (!pcmk_is_set(op->flags, pcmk_action_optional)
&& (op->node != NULL)) {
- enum action_tasks task = pcmk__parse_action(op->task);
+ enum pcmk__action_type task = pcmk__parse_action(op->task);
- if ((task == pcmk_action_stop) && op->node->details->unclean) {
+ if ((task == pcmk__action_stop) && op->node->details->unclean) {
// Create anyway (additional noise if node can't be fenced)
} else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) {
continue;
}
entry = new_notify_entry(rsc, op->node);
switch (task) {
- case pcmk_action_start:
+ case pcmk__action_start:
n_data->start = g_list_prepend(n_data->start, entry);
break;
- case pcmk_action_stop:
+ case pcmk__action_stop:
n_data->stop = g_list_prepend(n_data->stop, entry);
break;
- case pcmk_action_promote:
+ case pcmk__action_promote:
n_data->promote = g_list_prepend(n_data->promote, entry);
break;
- case pcmk_action_demote:
+ case pcmk__action_demote:
n_data->demote = g_list_prepend(n_data->demote, entry);
break;
default:
free(entry);
break;
}
}
}
}
// For (char *) value
#define add_notify_env(n_data, key, value) do { \
n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \
} while (0)
// For (GString *) value
#define add_notify_env_gs(n_data, key, value) do { \
n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \
(const char *) value->str); \
} while (0)
// For (GString *) value
#define add_notify_env_free_gs(n_data, key, value) do { \
n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \
(const char *) value->str); \
g_string_free(value, TRUE); value = NULL; \
} while (0)
/*!
* \internal
* \brief Create notification name/value pairs from structured data
*
* \param[in] rsc Resource that notification is for
* \param[in,out] n_data Notification data
*/
static void
add_notif_keys(const pcmk_resource_t *rsc, notify_data_t *n_data)
{
bool required = false; // Whether to make notify actions required
GString *rsc_list = NULL;
GString *node_list = NULL;
GString *metal_list = NULL;
const char *source = NULL;
GList *nodes = NULL;
n_data->stop = notify_entries_to_strings(n_data->stop,
&rsc_list, &node_list);
if ((strcmp(" ", (const char *) rsc_list->str) != 0)
&& pcmk__str_eq(n_data->action, PCMK_ACTION_STOP, pcmk__str_none)) {
required = true;
}
add_notify_env_free_gs(n_data, "notify_stop_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_stop_uname", node_list);
if ((n_data->start != NULL)
&& pcmk__str_eq(n_data->action, PCMK_ACTION_START, pcmk__str_none)) {
required = true;
}
n_data->start = notify_entries_to_strings(n_data->start,
&rsc_list, &node_list);
add_notify_env_free_gs(n_data, "notify_start_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_start_uname", node_list);
if ((n_data->demote != NULL)
&& pcmk__str_eq(n_data->action, PCMK_ACTION_DEMOTE, pcmk__str_none)) {
required = true;
}
n_data->demote = notify_entries_to_strings(n_data->demote,
&rsc_list, &node_list);
add_notify_env_free_gs(n_data, "notify_demote_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_demote_uname", node_list);
if ((n_data->promote != NULL)
&& pcmk__str_eq(n_data->action, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
required = true;
}
n_data->promote = notify_entries_to_strings(n_data->promote,
&rsc_list, &node_list);
add_notify_env_free_gs(n_data, "notify_promote_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_promote_uname", node_list);
n_data->active = notify_entries_to_strings(n_data->active,
&rsc_list, &node_list);
add_notify_env_free_gs(n_data, "notify_active_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_active_uname", node_list);
n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted,
&rsc_list, &node_list);
add_notify_env_gs(n_data, "notify_unpromoted_resource", rsc_list);
add_notify_env_gs(n_data, "notify_unpromoted_uname", node_list);
// Deprecated: kept for backward compatibility with older resource agents
add_notify_env_free_gs(n_data, "notify_slave_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_slave_uname", node_list);
n_data->promoted = notify_entries_to_strings(n_data->promoted,
&rsc_list, &node_list);
add_notify_env_gs(n_data, "notify_promoted_resource", rsc_list);
add_notify_env_gs(n_data, "notify_promoted_uname", node_list);
// Deprecated: kept for backward compatibility with older resource agents
add_notify_env_free_gs(n_data, "notify_master_resource", rsc_list);
add_notify_env_free_gs(n_data, "notify_master_uname", node_list);
n_data->inactive = notify_entries_to_strings(n_data->inactive,
&rsc_list, NULL);
add_notify_env_free_gs(n_data, "notify_inactive_resource", rsc_list);
nodes = g_hash_table_get_values(n_data->allowed_nodes);
if (!pcmk__is_daemon) {
/* For display purposes, sort the node list, for consistent
* regression test output (while avoiding the performance hit
* for the live cluster).
*/
nodes = g_list_sort(nodes, pe__cmp_node_name);
}
get_node_names(nodes, &node_list, NULL);
add_notify_env_free_gs(n_data, "notify_available_uname", node_list);
g_list_free(nodes);
source = g_hash_table_lookup(rsc->private->meta,
PCMK_META_CONTAINER_ATTRIBUTE_TARGET);
if (pcmk__str_eq(PCMK_VALUE_HOST, source, pcmk__str_none)) {
get_node_names(rsc->private->scheduler->nodes, &node_list, &metal_list);
add_notify_env_free_gs(n_data, "notify_all_hosts", metal_list);
} else {
get_node_names(rsc->private->scheduler->nodes, &node_list, NULL);
}
add_notify_env_free_gs(n_data, "notify_all_uname", node_list);
if (required && (n_data->pre != NULL)) {
pcmk__clear_action_flags(n_data->pre, pcmk_action_optional);
pcmk__clear_action_flags(n_data->pre_done, pcmk_action_optional);
}
if (required && (n_data->post != NULL)) {
pcmk__clear_action_flags(n_data->post, pcmk_action_optional);
pcmk__clear_action_flags(n_data->post_done, pcmk_action_optional);
}
}
/*
* \internal
* \brief Find any remote connection start relevant to an action
*
* \param[in] action Action to check
*
* \return If action is behind a remote connection, connection's start
*/
static pcmk_action_t *
find_remote_start(pcmk_action_t *action)
{
if ((action != NULL) && (action->node != NULL)) {
pcmk_resource_t *remote_rsc = action->node->private->remote;
if (remote_rsc != NULL) {
return find_first_action(remote_rsc->private->actions, NULL,
PCMK_ACTION_START,
NULL);
}
}
return NULL;
}
/*!
* \internal
* \brief Create notify actions, and add notify data to original actions
*
* \param[in,out] rsc Clone or clone instance that notification is for
* \param[in,out] n_data Clone notification data for some action
*/
static void
create_notify_actions(pcmk_resource_t *rsc, notify_data_t *n_data)
{
GList *iter = NULL;
pcmk_action_t *stop = NULL;
pcmk_action_t *start = NULL;
- enum action_tasks task = pcmk__parse_action(n_data->action);
+ enum pcmk__action_type task = pcmk__parse_action(n_data->action);
// If this is a clone, call recursively for each instance
if (rsc->private->children != NULL) {
g_list_foreach(rsc->private->children, (GFunc) create_notify_actions,
n_data);
return;
}
// Add notification meta-attributes to original actions
for (iter = rsc->private->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *op = (pcmk_action_t *) iter->data;
if (!pcmk_is_set(op->flags, pcmk_action_optional)
&& (op->node != NULL)) {
switch (pcmk__parse_action(op->task)) {
- case pcmk_action_start:
- case pcmk_action_stop:
- case pcmk_action_promote:
- case pcmk_action_demote:
+ case pcmk__action_start:
+ case pcmk__action_stop:
+ case pcmk__action_promote:
+ case pcmk__action_demote:
add_notify_data_to_action_meta(n_data, op);
break;
default:
break;
}
}
}
// Skip notify action itself if original action was not needed
switch (task) {
- case pcmk_action_start:
+ case pcmk__action_start:
if (n_data->start == NULL) {
pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
rsc->id, n_data->action);
return;
}
break;
- case pcmk_action_promote:
+ case pcmk__action_promote:
if (n_data->promote == NULL) {
pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
rsc->id, n_data->action);
return;
}
break;
- case pcmk_action_demote:
+ case pcmk__action_demote:
if (n_data->demote == NULL) {
pcmk__rsc_trace(rsc, "No notify action needed for %s %s",
rsc->id, n_data->action);
return;
}
break;
default:
// We cannot do same for stop because it might be implied by fencing
break;
}
pcmk__rsc_trace(rsc, "Creating notify actions for %s %s",
rsc->id, n_data->action);
// Create notify actions for stop or demote
if ((rsc->private->orig_role != pcmk_role_stopped)
- && ((task == pcmk_action_stop) || (task == pcmk_action_demote))) {
+ && ((task == pcmk__action_stop) || (task == pcmk__action_demote))) {
stop = find_first_action(rsc->private->actions, NULL, PCMK_ACTION_STOP,
NULL);
for (iter = rsc->private->active_nodes;
iter != NULL; iter = iter->next) {
pcmk_node_t *current_node = (pcmk_node_t *) iter->data;
/* If a stop is a pseudo-action implied by fencing, don't try to
* notify the node getting fenced.
*/
if ((stop != NULL)
&& pcmk_is_set(stop->flags, pcmk_action_pseudo)
&& (current_node->details->unclean
|| pcmk_is_set(current_node->private->flags,
pcmk__node_remote_reset))) {
continue;
}
new_notify_action(rsc, current_node, n_data->pre,
n_data->pre_done, n_data);
- if ((task == pcmk_action_demote) || (stop == NULL)
+ if ((task == pcmk__action_demote) || (stop == NULL)
|| pcmk_is_set(stop->flags, pcmk_action_optional)) {
new_post_notify_action(rsc, current_node, n_data);
}
}
}
// Create notify actions for start or promote
if ((rsc->private->next_role != pcmk_role_stopped)
- && ((task == pcmk_action_start) || (task == pcmk_action_promote))) {
+ && ((task == pcmk__action_start) || (task == pcmk__action_promote))) {
start = find_first_action(rsc->private->actions, NULL,
PCMK_ACTION_START, NULL);
if (start != NULL) {
pcmk_action_t *remote_start = find_remote_start(start);
if ((remote_start != NULL)
&& !pcmk_is_set(remote_start->flags, pcmk_action_runnable)) {
/* Start and promote actions for a clone instance behind
* a Pacemaker Remote connection happen after the
* connection starts. If the connection start is blocked, do
* not schedule notifications for these actions.
*/
return;
}
}
if (rsc->private->assigned_node == NULL) {
pcmk__sched_err("Next role '%s' but %s is not allocated",
pcmk_role_text(rsc->private->next_role), rsc->id);
return;
}
- if ((task != pcmk_action_start) || (start == NULL)
+ if ((task != pcmk__action_start) || (start == NULL)
|| pcmk_is_set(start->flags, pcmk_action_optional)) {
new_notify_action(rsc, rsc->private->assigned_node, n_data->pre,
n_data->pre_done, n_data);
}
new_post_notify_action(rsc, rsc->private->assigned_node, n_data);
}
}
/*!
* \internal
* \brief Create notification data and actions for one clone action
*
* \param[in,out] rsc Clone resource that notification is for
* \param[in,out] n_data Clone notification data for some action
*/
void
pe__create_action_notifications(pcmk_resource_t *rsc, notify_data_t *n_data)
{
if ((rsc == NULL) || (n_data == NULL)) {
return;
}
collect_resource_data(rsc, true, n_data);
add_notif_keys(rsc, n_data);
create_notify_actions(rsc, n_data);
}
/*!
* \internal
* \brief Free notification data for one action
*
* \param[in,out] n_data Notification data to free
*/
void
pe__free_action_notification_data(notify_data_t *n_data)
{
if (n_data == NULL) {
return;
}
g_list_free_full(n_data->stop, free);
g_list_free_full(n_data->start, free);
g_list_free_full(n_data->demote, free);
g_list_free_full(n_data->promote, free);
g_list_free_full(n_data->promoted, free);
g_list_free_full(n_data->unpromoted, free);
g_list_free_full(n_data->active, free);
g_list_free_full(n_data->inactive, free);
pcmk_free_nvpairs(n_data->keys);
free(n_data);
}
/*!
* \internal
* \brief Order clone "notifications complete" pseudo-action after fencing
*
* If a stop action is implied by fencing, the usual notification pseudo-actions
* will not be sufficient to order things properly, or even create all needed
* notifications if the clone is also stopping on another node, and another
* clone is ordered after it. This function creates new notification
* pseudo-actions relative to the fencing to ensure everything works properly.
*
* \param[in] stop Stop action implied by fencing
* \param[in,out] rsc Clone resource that notification is for
* \param[in,out] stonith_op Fencing action that implies \p stop
*/
void
pe__order_notifs_after_fencing(const pcmk_action_t *stop, pcmk_resource_t *rsc,
pcmk_action_t *stonith_op)
{
notify_data_t *n_data;
crm_info("Ordering notifications for implied %s after fencing", stop->uuid);
n_data = pe__action_notif_pseudo_ops(rsc, PCMK_ACTION_STOP, NULL,
stonith_op);
if (n_data != NULL) {
collect_resource_data(rsc, false, n_data);
add_notify_env(n_data, "notify_stop_resource", rsc->id);
add_notify_env(n_data, "notify_stop_uname", stop->node->private->name);
create_notify_actions(uber_parent(rsc), n_data);
pe__free_action_notification_data(n_data);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:15 PM (3 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1659420
Default Alt Text
(417 KB)

Event Timeline