Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F7988901
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
192 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/crm/common/xml_names_internal.h b/include/crm/common/xml_names_internal.h
index 3f4d86650c..bace91ebad 100644
--- a/include/crm/common/xml_names_internal.h
+++ b/include/crm/common/xml_names_internal.h
@@ -1,293 +1,295 @@
/*
* 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_XML_NAMES_INTERNAL__H
#define PCMK__CRM_COMMON_XML_NAMES_INTERNAL__H
#ifdef __cplusplus
extern "C" {
#endif
/*
* XML element names used only by internal code
*/
#define PCMK__XE_ACK "ack"
#define PCMK__XE_ACTION_SET "action_set"
#define PCMK__XE_ATTRIBUTES "attributes"
#define PCMK__XE_CIB_CALLBACK "cib-callback"
#define PCMK__XE_CIB_CALLDATA "cib_calldata"
#define PCMK__XE_CIB_COMMAND "cib_command"
#define PCMK__XE_CIB_REPLY "cib-reply"
#define PCMK__XE_CIB_RESULT "cib_result"
#define PCMK__XE_CIB_TRANSACTION "cib_transaction"
#define PCMK__XE_CIB_UPDATE_RESULT "cib_update_result"
#define PCMK__XE_COPY "copy"
#define PCMK__XE_CRM_EVENT "crm_event"
#define PCMK__XE_CRM_XML "crm_xml"
#define PCMK__XE_DIV "div"
#define PCMK__XE_DOWNED "downed"
#define PCMK__XE_EXIT_NOTIFICATION "exit-notification"
#define PCMK__XE_FAILED_UPDATE "failed_update"
#define PCMK__XE_GENERATION_TUPLE "generation_tuple"
#define PCMK__XE_INPUTS "inputs"
#define PCMK__XE_LRM "lrm"
#define PCMK__XE_LRM_RESOURCE "lrm_resource"
#define PCMK__XE_LRM_RESOURCES "lrm_resources"
#define PCMK__XE_LRM_RSC_OP "lrm_rsc_op"
#define PCMK__XE_LRMD_ALERT "lrmd_alert"
#define PCMK__XE_LRMD_CALLDATA "lrmd_calldata"
#define PCMK__XE_LRMD_COMMAND "lrmd_command"
#define PCMK__XE_LRMD_IPC_MSG "lrmd_ipc_msg"
#define PCMK__XE_LRMD_IPC_PROXY "lrmd_ipc_proxy"
#define PCMK__XE_LRMD_NOTIFY "lrmd_notify"
#define PCMK__XE_LRMD_REPLY "lrmd_reply"
#define PCMK__XE_LRMD_RSC "lrmd_rsc"
#define PCMK__XE_LRMD_RSC_OP "lrmd_rsc_op"
#define PCMK__XE_MAINTENANCE "maintenance"
#define PCMK__XE_MESSAGE "message"
#define PCMK__XE_META "meta"
#define PCMK__XE_NACK "nack"
#define PCMK__XE_NODE_STATE "node_state"
#define PCMK__XE_NOTIFY "notify"
#define PCMK__XE_OPTIONS "options"
#define PCMK__XE_PARAM "param"
#define PCMK__XE_PING "ping"
#define PCMK__XE_PING_RESPONSE "ping_response"
#define PCMK__XE_PSEUDO_EVENT "pseudo_event"
#define PCMK__XE_RESOURCE_SETTINGS "resource-settings"
#define PCMK__XE_RSC_OP "rsc_op"
#define PCMK__XE_SHUTDOWN "shutdown"
#define PCMK__XE_SPAN "span"
#define PCMK__XE_ST_ASYNC_TIMEOUT_VALUE "st-async-timeout-value"
#define PCMK__XE_ST_CALLDATA "st_calldata"
#define PCMK__XE_ST_DEVICE_ACTION "st_device_action"
#define PCMK__XE_ST_DEVICE_ID "st_device_id"
#define PCMK__XE_ST_HISTORY "st_history"
#define PCMK__XE_ST_NOTIFY_FENCE "st_notify_fence"
#define PCMK__XE_ST_REPLY "st-reply"
#define PCMK__XE_STONITH_COMMAND "stonith_command"
#define PCMK__XE_SYNAPSE "synapse"
#define PCMK__XE_TICKET_STATE "ticket_state"
#define PCMK__XE_TRANSIENT_ATTRIBUTES "transient_attributes"
#define PCMK__XE_TRANSITION_GRAPH "transition_graph"
#define PCMK__XE_TRIGGER "trigger"
#define PCMK__XE_XPATH_QUERY "xpath-query"
#define PCMK__XE_XPATH_QUERY_PATH "xpath-query-path"
/* @COMPAT Deprecate somehow. It's undocumented and behaves the same as
* PCMK__XE_CIB in places where it's recognized.
*/
#define PCMK__XE_ALL "all"
// @COMPAT Deprecated since 2.1.8
#define PCMK__XE_FAILED "failed"
/*
* XML attribute names used only by internal code
*/
#define PCMK__XA_ACL_TARGET "acl_target"
#define PCMK__XA_ATTR_CLEAR_INTERVAL "attr_clear_interval"
#define PCMK__XA_ATTR_CLEAR_OPERATION "attr_clear_operation"
#define PCMK__XA_ATTR_DAMPENING "attr_dampening"
#define PCMK__XA_ATTR_HOST "attr_host"
#define PCMK__XA_ATTR_HOST_ID "attr_host_id"
#define PCMK__XA_ATTR_IS_PRIVATE "attr_is_private"
#define PCMK__XA_ATTR_IS_REMOTE "attr_is_remote"
#define PCMK__XA_ATTR_NAME "attr_name"
#define PCMK__XA_ATTR_REGEX "attr_regex"
#define PCMK__XA_ATTR_RESOURCE "attr_resource"
#define PCMK__XA_ATTR_SECTION "attr_section"
#define PCMK__XA_ATTR_SET "attr_set"
#define PCMK__XA_ATTR_SET_TYPE "attr_set_type"
#define PCMK__XA_ATTR_SYNC_POINT "attr_sync_point"
#define PCMK__XA_ATTR_USER "attr_user"
#define PCMK__XA_ATTR_VALUE "attr_value"
#define PCMK__XA_ATTR_VERSION "attr_version"
#define PCMK__XA_ATTR_WRITER "attr_writer"
#define PCMK__XA_ATTRD_IS_FORCE_WRITE "attrd_is_force_write"
#define PCMK__XA_CALL_ID "call-id"
#define PCMK__XA_CIB_CALLID "cib_callid"
#define PCMK__XA_CIB_CALLOPT "cib_callopt"
#define PCMK__XA_CIB_CLIENTID "cib_clientid"
#define PCMK__XA_CIB_CLIENTNAME "cib_clientname"
#define PCMK__XA_CIB_DELEGATED_FROM "cib_delegated_from"
#define PCMK__XA_CIB_HOST "cib_host"
#define PCMK__XA_CIB_ISREPLYTO "cib_isreplyto"
#define PCMK__XA_CIB_NOTIFY_ACTIVATE "cib_notify_activate"
#define PCMK__XA_CIB_NOTIFY_TYPE "cib_notify_type"
#define PCMK__XA_CIB_OP "cib_op"
#define PCMK__XA_CIB_PING_ID "cib_ping_id"
#define PCMK__XA_CIB_RC "cib_rc"
#define PCMK__XA_CIB_SCHEMA_MAX "cib_schema_max"
#define PCMK__XA_CIB_SECTION "cib_section"
#define PCMK__XA_CIB_UPDATE "cib_update"
#define PCMK__XA_CIB_UPGRADE_RC "cib_upgrade_rc"
#define PCMK__XA_CIB_USER "cib_user"
#define PCMK__XA_CLIENT_NAME "client_name"
#define PCMK__XA_CLIENT_UUID "client_uuid"
#define PCMK__XA_CONFIRM "confirm"
#define PCMK__XA_CONNECTION_HOST "connection_host"
#define PCMK__XA_CONTENT "content"
#define PCMK__XA_CRMD_STATE "crmd_state"
#define PCMK__XA_CRM_HOST_TO "crm_host_to"
#define PCMK__XA_CRM_LIMIT_MAX "crm-limit-max"
#define PCMK__XA_CRM_LIMIT_MODE "crm-limit-mode"
#define PCMK__XA_CRM_SUBSYSTEM "crm_subsystem"
#define PCMK__XA_CRM_SYS_FROM "crm_sys_from"
#define PCMK__XA_CRM_SYS_TO "crm_sys_to"
#define PCMK__XA_CRM_TASK "crm_task"
#define PCMK__XA_CRM_TGRAPH_IN "crm-tgraph-in"
#define PCMK__XA_CRM_USER "crm_user"
#define PCMK__XA_DC_LEAVING "dc-leaving"
#define PCMK__XA_DIGEST "digest"
#define PCMK__XA_ELECTION_AGE_SEC "election-age-sec"
#define PCMK__XA_ELECTION_AGE_NANO_SEC "election-age-nano-sec"
#define PCMK__XA_ELECTION_ID "election-id"
#define PCMK__XA_ELECTION_OWNER "election-owner"
+#define PCMK__XA_FAILED_START_OFFSET "failed-start-offset"
+#define PCMK__XA_FAILED_STOP_OFFSET "failed-stop-offset"
#define PCMK__XA_GRANTED "granted"
#define PCMK__XA_HIDDEN "hidden"
#define PCMK__XA_HTTP_EQUIV "http-equiv"
#define PCMK__XA_IN_CCM "in_ccm"
#define PCMK__XA_IPC_PROTO_VERSION "ipc-protocol-version"
#define PCMK__XA_JOIN "join"
#define PCMK__XA_JOIN_ID "join_id"
#define PCMK__XA_LINE "line"
#define PCMK__XA_LONG_ID "long-id"
#define PCMK__XA_LRMD_ALERT_ID "lrmd_alert_id"
#define PCMK__XA_LRMD_ALERT_PATH "lrmd_alert_path"
#define PCMK__XA_LRMD_CALLID "lrmd_callid"
#define PCMK__XA_LRMD_CALLOPT "lrmd_callopt"
#define PCMK__XA_LRMD_CLASS "lrmd_class"
#define PCMK__XA_LRMD_CLIENTID "lrmd_clientid"
#define PCMK__XA_LRMD_CLIENTNAME "lrmd_clientname"
#define PCMK__XA_LRMD_EXEC_OP_STATUS "lrmd_exec_op_status"
#define PCMK__XA_LRMD_EXEC_RC "lrmd_exec_rc"
#define PCMK__XA_LRMD_EXEC_TIME "lrmd_exec_time"
#define PCMK__XA_LRMD_IPC_CLIENT "lrmd_ipc_client"
#define PCMK__XA_LRMD_IPC_MSG_FLAGS "lrmd_ipc_msg_flags"
#define PCMK__XA_LRMD_IPC_MSG_ID "lrmd_ipc_msg_id"
#define PCMK__XA_LRMD_IPC_OP "lrmd_ipc_op"
#define PCMK__XA_LRMD_IPC_SERVER "lrmd_ipc_server"
#define PCMK__XA_LRMD_IPC_SESSION "lrmd_ipc_session"
#define PCMK__XA_LRMD_IPC_USER "lrmd_ipc_user"
#define PCMK__XA_LRMD_IS_IPC_PROVIDER "lrmd_is_ipc_provider"
#define PCMK__XA_LRMD_OP "lrmd_op"
#define PCMK__XA_LRMD_ORIGIN "lrmd_origin"
#define PCMK__XA_LRMD_PROTOCOL_VERSION "lrmd_protocol_version"
#define PCMK__XA_LRMD_PROVIDER "lrmd_provider"
#define PCMK__XA_LRMD_QUEUE_TIME "lrmd_queue_time"
#define PCMK__XA_LRMD_RC "lrmd_rc"
#define PCMK__XA_LRMD_RCCHANGE_TIME "lrmd_rcchange_time"
#define PCMK__XA_LRMD_REMOTE_MSG_ID "lrmd_remote_msg_id"
#define PCMK__XA_LRMD_REMOTE_MSG_TYPE "lrmd_remote_msg_type"
#define PCMK__XA_LRMD_RSC_ACTION "lrmd_rsc_action"
#define PCMK__XA_LRMD_RSC_DELETED "lrmd_rsc_deleted"
#define PCMK__XA_LRMD_RSC_EXIT_REASON "lrmd_rsc_exit_reason"
#define PCMK__XA_LRMD_RSC_ID "lrmd_rsc_id"
#define PCMK__XA_LRMD_RSC_INTERVAL "lrmd_rsc_interval"
#define PCMK__XA_LRMD_RSC_OUTPUT "lrmd_rsc_output"
#define PCMK__XA_LRMD_RSC_START_DELAY "lrmd_rsc_start_delay"
#define PCMK__XA_LRMD_RSC_USERDATA_STR "lrmd_rsc_userdata_str"
#define PCMK__XA_LRMD_RUN_TIME "lrmd_run_time"
#define PCMK__XA_LRMD_TIMEOUT "lrmd_timeout"
#define PCMK__XA_LRMD_TYPE "lrmd_type"
#define PCMK__XA_LRMD_WATCHDOG "lrmd_watchdog"
#define PCMK__XA_MAJOR_VERSION "major_version"
#define PCMK__XA_MINOR_VERSION "minor_version"
#define PCMK__XA_MODE "mode"
#define PCMK__XA_NAMESPACE "namespace"
#define PCMK__XA_NODE_FENCED "node_fenced"
#define PCMK__XA_NODE_IN_MAINTENANCE "node_in_maintenance"
#define PCMK__XA_NODE_START_STATE "node_start_state"
#define PCMK__XA_NODE_STATE "node_state"
#define PCMK__XA_OP_DIGEST "op-digest"
#define PCMK__XA_OP_FORCE_RESTART "op-force-restart"
#define PCMK__XA_OP_RESTART_DIGEST "op-restart-digest"
#define PCMK__XA_OP_SECURE_DIGEST "op-secure-digest"
#define PCMK__XA_OP_SECURE_PARAMS "op-secure-params"
#define PCMK__XA_OP_STATUS "op-status"
#define PCMK__XA_OPERATION_KEY "operation_key"
#define PCMK__XA_ORIGINAL_CIB_OP "original_cib_op"
#define PCMK__XA_PACEMAKERD_STATE "pacemakerd_state"
#define PCMK__XA_PASSWORD "password"
#define PCMK__XA_PRIORITY "priority"
#define PCMK__XA_RC_CODE "rc-code"
#define PCMK__XA_REAP "reap"
/* Actions to be executed on Pacemaker Remote nodes are routed through the
* controller on the cluster node hosting the remote connection. That cluster
* node is considered the router node for the action.
*/
#define PCMK__XA_ROUTER_NODE "router_node"
#define PCMK__XA_RSC_ID "rsc-id"
#define PCMK__XA_RSC_PROVIDES "rsc_provides"
#define PCMK__XA_SCHEMA "schema"
#define PCMK__XA_SCHEMAS "schemas"
#define PCMK__XA_SET "set"
#define PCMK__XA_SRC "src"
#define PCMK__XA_ST_ACTION_DISALLOWED "st_action_disallowed"
#define PCMK__XA_ST_ACTION_TIMEOUT "st_action_timeout"
#define PCMK__XA_ST_AVAILABLE_DEVICES "st-available-devices"
#define PCMK__XA_ST_CALLID "st_callid"
#define PCMK__XA_ST_CALLOPT "st_callopt"
#define PCMK__XA_ST_CLIENTID "st_clientid"
#define PCMK__XA_ST_CLIENTNAME "st_clientname"
#define PCMK__XA_ST_CLIENTNODE "st_clientnode"
#define PCMK__XA_ST_DATE "st_date"
#define PCMK__XA_ST_DATE_NSEC "st_date_nsec"
#define PCMK__XA_ST_DELAY "st_delay"
#define PCMK__XA_ST_DELAY_BASE "st_delay_base"
#define PCMK__XA_ST_DELAY_MAX "st_delay_max"
#define PCMK__XA_ST_DELEGATE "st_delegate"
#define PCMK__XA_ST_DEVICE_ACTION "st_device_action"
#define PCMK__XA_ST_DEVICE_ID "st_device_id"
#define PCMK__XA_ST_DEVICE_SUPPORT_FLAGS "st_device_support_flags"
#define PCMK__XA_ST_DIFFERENTIAL "st_differential"
#define PCMK__XA_ST_MONITOR_VERIFIED "st_monitor_verified"
#define PCMK__XA_ST_NOTIFY_ACTIVATE "st_notify_activate"
#define PCMK__XA_ST_NOTIFY_DEACTIVATE "st_notify_deactivate"
#define PCMK__XA_ST_OP "st_op"
#define PCMK__XA_ST_OP_MERGED "st_op_merged"
#define PCMK__XA_ST_ORIGIN "st_origin"
#define PCMK__XA_ST_OUTPUT "st_output"
#define PCMK__XA_ST_RC "st_rc"
#define PCMK__XA_ST_REMOTE_OP "st_remote_op"
#define PCMK__XA_ST_REMOTE_OP_RELAY "st_remote_op_relay"
#define PCMK__XA_ST_REQUIRED "st_required"
#define PCMK__XA_ST_STATE "st_state"
#define PCMK__XA_ST_TARGET "st_target"
#define PCMK__XA_ST_TIMEOUT "st_timeout"
#define PCMK__XA_ST_TOLERANCE "st_tolerance"
#define PCMK__XA_SUBT "subt" // subtype
#define PCMK__XA_T "t" // type
#define PCMK__XA_TRANSITION_KEY "transition-key"
#define PCMK__XA_TRANSITION_MAGIC "transition-magic"
#define PCMK__XA_UPTIME "uptime"
// @COMPAT Deprecated since 2.1.7
#define PCMK__XA_ORDERING "ordering"
// @COMPAT Deprecated alias for PCMK_XA_PROMOTED_ONLY since 2.0.0
#define PCMK__XA_PROMOTED_ONLY_LEGACY "master_only"
// @COMPAT Deprecated since 2.1.6
#define PCMK__XA_REPLACE "replace"
// @COMPAT Deprecated alias for \c PCMK_XA_AUTOMATIC since 1.1.14
#define PCMK__XA_REQUIRED "required"
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_XML_NAMES_INTERNAL__H
diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h
index de58ec60a6..58435a6217 100644
--- a/lib/pacemaker/libpacemaker_private.h
+++ b/lib/pacemaker/libpacemaker_private.h
@@ -1,1141 +1,1142 @@
/*
* Copyright 2021-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
#define PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
/* This header is for the sole use of libpacemaker, so that functions can be
* declared with G_GNUC_INTERNAL for efficiency.
*/
#include <stdio.h> // NULL
#include <stdint.h> // uint32_t
#include <stdbool.h> // bool, false
#include <glib.h> // guint, gpointer, GList, GHashTable
#include <libxml/tree.h> // xmlNode
#include <crm/common/scheduler.h> // pcmk_action_t, pcmk_node_t, etc.
#include <crm/common/scheduler_internal.h> // pcmk__location_t, etc.
#include <crm/cib.h> // cib_t
#include <crm/lrmd_events.h> // lrmd_event_data_t
#include <crm/pengine/internal.h> // pe__const_top_resource(), etc.
#include <pacemaker.h> // pcmk_injections_t
#include <pacemaker-internal.h> // pcmk__colocation_t
#ifdef __cplusplus
extern "C" {
#endif
// Colocation flags
enum pcmk__coloc_flags {
pcmk__coloc_none = 0U,
// Primary is affected even if already active
pcmk__coloc_influence = (1U << 0),
// Colocation was explicitly configured in CIB
pcmk__coloc_explicit = (1U << 1),
};
// Flags to modify the behavior of add_colocated_node_scores()
enum pcmk__coloc_select {
// With no other flags, apply all "with this" colocations
pcmk__coloc_select_default = 0,
// Apply "this with" colocations instead of "with this" colocations
pcmk__coloc_select_this_with = (1 << 0),
// Apply only colocations with non-negative scores
pcmk__coloc_select_nonnegative = (1 << 1),
// Apply only colocations with at least one matching node
pcmk__coloc_select_active = (1 << 2),
};
// Flags the update_ordered_actions() method can return
enum pcmk__updated {
pcmk__updated_none = 0, // Nothing changed
pcmk__updated_first = (1 << 0), // First action was updated
pcmk__updated_then = (1 << 1), // Then action was updated
};
#define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \
au_flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_set), #flags_to_set); \
} while (0)
#define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \
au_flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "Action update", \
(action)->uuid, au_flags, \
(flags_to_clear), #flags_to_clear); \
} while (0)
// Resource assignment methods
struct pcmk__assignment_methods {
/*!
* \internal
* \brief Assign a resource to a node
*
* \param[in,out] rsc Resource to assign to a node
* \param[in] prefer Node to prefer, if all else is equal
* \param[in] stop_if_fail If \c true and \p rsc can't be assigned to a
* node, set next role to stopped and update
* existing actions (if \p rsc is not a
* primitive, this applies to its primitive
* descendants instead)
*
* \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 *(*assign)(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
/*!
* \internal
* \brief Create all actions needed for a given resource
*
* \param[in,out] rsc Resource to create actions for
*/
void (*create_actions)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Schedule any probes needed for a resource on a node
*
* \param[in,out] rsc Resource to create probe for
* \param[in,out] node Node to create probe on
*
* \return true if any probe was created, otherwise false
*/
bool (*create_probe)(pcmk_resource_t *rsc, pcmk_node_t *node);
/*!
* \internal
* \brief Create implicit constraints needed for a resource
*
* \param[in,out] rsc Resource to create implicit constraints for
*/
void (*internal_constraints)(pcmk_resource_t *rsc);
/*!
* \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
*
* \return The score added to the dependent's priority
*/
int (*apply_coloc_score)(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
/*!
* \internal
* \brief Create list of all resources in colocations with a given resource
*
* Given a resource, create a list of all resources involved in mandatory
* colocations with it, whether directly or via chained colocations.
*
* \param[in] rsc Resource to add to colocated list
* \param[in] orig_rsc Resource originally requested
* \param[in,out] colocated_rscs Existing list
*
* \return List of given resource and all resources involved in colocations
*
* \note This function is recursive; top-level callers should pass NULL as
* \p colocated_rscs and \p orig_rsc, and the desired resource as
* \p rsc. The recursive calls will use other values.
*/
GList *(*colocated_resources)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
/*!
* \internal
* \brief Add colocations affecting a resource as primary to a list
*
* Given a resource being assigned (\p orig_rsc) and a resource somewhere in
* its chain of ancestors (\p rsc, which may be \p orig_rsc), get
* colocations that affect the ancestor as primary and should affect the
* resource, and add them to a given list.
*
* \param[in] rsc Resource whose colocations should be added
* \param[in] orig_rsc Affected resource (\p rsc or a descendant)
* \param[in,out] list List of colocations to add to
*
* \note All arguments should be non-NULL.
* \note The pcmk__with_this_colocations() wrapper should usually be used
* instead of using this method directly.
*/
void (*with_this_colocations)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
/*!
* \internal
* \brief Add colocations affecting a resource as dependent to a list
*
* Given a resource being assigned (\p orig_rsc) and a resource somewhere in
* its chain of ancestors (\p rsc, which may be \p orig_rsc), get
* colocations that affect the ancestor as dependent and should affect the
* resource, and add them to a given list.
*
*
* \param[in] rsc Resource whose colocations should be added
* \param[in] orig_rsc Affected resource (\p rsc or a descendant)
* \param[in,out] list List of colocations to add to
*
* \note All arguments should be non-NULL.
* \note The pcmk__this_with_colocations() wrapper should usually be used
* instead of using this method directly.
*/
void (*this_with_colocations)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
/*!
* \internal
* \brief Update nodes with scores of colocated resources' nodes
*
* Given a table of nodes and a resource, update the nodes' scores with the
* scores of the best nodes matching the attribute used for each of the
* resource's relevant colocations.
*
* \param[in,out] source_rsc Resource whose node scores to add
* \param[in] target_rsc Resource on whose behalf to update \p *nodes
* \param[in] log_id Resource ID for logs (if \c NULL, use
* \p source_rsc ID)
* \param[in,out] nodes Nodes to update (set initial contents to
* \c NULL to copy allowed nodes from
* \p source_rsc)
* \param[in] colocation Original colocation constraint (used to get
* configured primary resource's stickiness, and
* to get colocation node attribute; if \c NULL,
* <tt>source_rsc</tt>'s own matching node scores
* will not be added, and \p *nodes must be
* \c NULL as well)
* \param[in] factor Incorporate scores multiplied by this factor
* \param[in] flags Bitmask of enum pcmk__coloc_select values
*
* \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation,
* and the \c pcmk__coloc_select_this_with flag are used together (and
* only by \c cmp_resources()).
* \note The caller remains responsible for freeing \p *nodes.
*/
void (*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);
/*!
* \internal
* \brief Apply a location constraint to a resource's allowed node scores
*
* \param[in,out] rsc Resource to apply constraint to
* \param[in,out] location Location constraint to apply
*/
void (*apply_location)(pcmk_resource_t *rsc, pcmk__location_t *location);
/*!
* \internal
* \brief Return action flags for a given resource action
*
* \param[in,out] action Action to get flags for
* \param[in] node If not NULL, limit effects to this node
*
* \return Flags appropriate to \p action on \p node
* \note For primitives, this will be the same as action->flags regardless
* of node. For collective resources, the flags can differ due to
* multiple instances possibly being involved.
*/
uint32_t (*action_flags)(pcmk_action_t *action, const pcmk_node_t *node);
/*!
* \internal
* \brief Update two actions according to an ordering between them
*
* Given information about an ordering of two actions, update the actions'
* flags (and runnable_before members if appropriate) as appropriate for the
* ordering. 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
* \param[in,out] scheduler Scheduler data
*
* \return Group of enum pcmk__updated flags indicating what was updated
*/
uint32_t (*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);
/*!
* \internal
* \brief Output a summary of scheduled actions for a resource
*
* \param[in,out] rsc Resource to output actions for
*/
void (*output_actions)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Add a resource's actions to the transition graph
*
* \param[in,out] rsc Resource whose actions should be added
*/
void (*add_actions_to_graph)(pcmk_resource_t *rsc);
/*!
* \internal
* \brief Add meta-attributes relevant to transition graph actions to XML
*
* If a given resource supports variant-specific meta-attributes that are
* needed for transition graph actions, add them to a given XML element.
*
* \param[in] rsc Resource whose meta-attributes should be added
* \param[in,out] xml Transition graph action attributes XML to add to
*/
void (*add_graph_meta)(const pcmk_resource_t *rsc, xmlNode *xml);
/*!
* \internal
* \brief Add a resource's utilization to a table of utilization values
*
* This function is used when summing the utilization of a resource and all
* resources colocated with it, to determine whether a node has sufficient
* capacity. Given a resource and a table of utilization values, it will add
* the resource's utilization to the existing values, if the resource has
* not yet been assigned to a node.
*
* \param[in] rsc Resource with utilization to add
* \param[in] orig_rsc Resource being assigned (for logging only)
* \param[in] all_rscs List of all resources that will be summed
* \param[in,out] utilization Table of utilization values to add to
*/
void (*add_utilization)(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc, GList *all_rscs,
GHashTable *utilization);
/*!
* \internal
* \brief Apply a shutdown lock for a resource, if appropriate
*
* \param[in,out] rsc Resource to check for shutdown lock
*/
void (*shutdown_lock)(pcmk_resource_t *rsc);
};
// Actions (pcmk_sched_actions.c)
G_GNUC_INTERNAL
void pcmk__update_action_for_orderings(pcmk_action_t *action,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
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);
G_GNUC_INTERNAL
void pcmk__log_action(const char *pre_text, const pcmk_action_t *action,
bool details);
G_GNUC_INTERNAL
pcmk_action_t *pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *name,
guint interval_ms,
const pcmk_node_t *node);
G_GNUC_INTERNAL
pcmk_action_t *pcmk__new_shutdown_action(pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_locks_rsc_to_node(const pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__deduplicate_action_inputs(pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__output_actions(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__check_action_config(pcmk_resource_t *rsc, pcmk_node_t *node,
const xmlNode *xml_op);
G_GNUC_INTERNAL
void pcmk__handle_rsc_config_changes(pcmk_scheduler_t *scheduler);
// Recurring actions (pcmk_sched_recurring.c)
G_GNUC_INTERNAL
void pcmk__create_recurring_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id,
const char *task, guint interval_ms,
const pcmk_node_t *node, const char *reason);
G_GNUC_INTERNAL
void pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task,
guint interval_ms, pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__action_is_recurring(const pcmk_action_t *action);
// Producing transition graphs (pcmk_graph_producer.c)
G_GNUC_INTERNAL
bool pcmk__graph_has_loop(const pcmk_action_t *init_action,
const pcmk_action_t *action,
pcmk__related_action_t *input);
G_GNUC_INTERNAL
void pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_graph(pcmk_scheduler_t *scheduler);
// Fencing (pcmk_sched_fencing.c)
G_GNUC_INTERNAL
void pcmk__order_vs_fence(pcmk_action_t *stonith_op,
pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__order_vs_unfence(const pcmk_resource_t *rsc, pcmk_node_t *node,
pcmk_action_t *action,
enum pcmk__action_relation_flags order);
G_GNUC_INTERNAL
void pcmk__fence_guest(pcmk_node_t *node);
G_GNUC_INTERNAL
bool pcmk__node_unfenced(const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_restart_vs_unfence(gpointer data, gpointer user_data);
// Injected scheduler inputs (pcmk_sched_injections.c)
G_GNUC_INTERNAL
void pcmk__inject_scheduler_input(pcmk_scheduler_t *scheduler, cib_t *cib,
const pcmk_injections_t *injections);
// Constraints of any type (pcmk_sched_constraints.c)
G_GNUC_INTERNAL
pcmk_resource_t *pcmk__find_constraint_resource(GList *rsc_list,
const char *id);
G_GNUC_INTERNAL
int pcmk__parse_constraint_role(const char *id, const char *role_spec,
enum rsc_role_e *role);
G_GNUC_INTERNAL
xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj,
const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler,
const char *id, pcmk_resource_t **rsc,
pcmk__idref_t **tag);
G_GNUC_INTERNAL
bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
bool convert_rsc, const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler);
// Location constraints
G_GNUC_INTERNAL
void pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
pcmk__location_t *pcmk__new_location(const char *id, pcmk_resource_t *rsc,
int node_score, const char *discover_mode,
pcmk_node_t *foo_node);
G_GNUC_INTERNAL
void pcmk__apply_locations(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *constraint);
// Colocation constraints (pcmk_sched_colocation.c)
enum pcmk__coloc_affects {
pcmk__coloc_affects_nothing = 0,
pcmk__coloc_affects_location,
pcmk__coloc_affects_role,
};
G_GNUC_INTERNAL
const char *pcmk__colocation_node_attr(const pcmk_node_t *node,
const char *attr,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
enum pcmk__coloc_affects pcmk__colocation_affects(const pcmk_resource_t
*dependent,
const pcmk_resource_t
*primary,
const pcmk__colocation_t
*colocation,
bool preview);
G_GNUC_INTERNAL
void pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
int pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation);
G_GNUC_INTERNAL
void pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
const pcmk_resource_t *target_rsc,
const char *log_id, GHashTable **nodes,
const pcmk__colocation_t *colocation,
float factor, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__add_dependent_scores(gpointer data, gpointer user_data);
G_GNUC_INTERNAL
void pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
const GList *primary_nodes,
bool merge_scores);
G_GNUC_INTERNAL
void pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_this_with_list(GList **list, GList *addition,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__add_with_this_list(GList **list, GList *addition,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
GList *pcmk__with_this_colocations(const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
GList *pcmk__this_with_colocations(const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__new_colocation(const char *id, const char *node_attr, int score,
pcmk_resource_t *dependent, pcmk_resource_t *primary,
const char *dependent_role_spec,
const char *primary_role_spec, uint32_t flags);
G_GNUC_INTERNAL
void pcmk__block_colocation_dependents(pcmk_action_t *action);
G_GNUC_INTERNAL
bool pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
const pcmk_resource_t *rsc);
// Ordering constraints (pcmk_sched_ordering.c)
G_GNUC_INTERNAL
void pcmk__new_ordering(pcmk_resource_t *first_rsc, char *first_task,
pcmk_action_t *first_action, pcmk_resource_t *then_rsc,
char *then_task, pcmk_action_t *then_action,
uint32_t flags, pcmk_scheduler_t *sched);
G_GNUC_INTERNAL
void pcmk__unpack_ordering(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__disable_invalid_orderings(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
void pcmk__order_stops_before_shutdown(pcmk_node_t *node,
pcmk_action_t *shutdown_op);
G_GNUC_INTERNAL
void pcmk__apply_orderings(pcmk_scheduler_t *sched);
G_GNUC_INTERNAL
void pcmk__order_after_each(pcmk_action_t *after, GList *list);
/*!
* \internal
* \brief Create a new ordering between two resource actions
*
* \param[in,out] first_rsc Resource for 'first' action
* \param[in,out] first_task Action key for 'first' action
* \param[in] then_rsc Resource for 'then' action
* \param[in,out] then_task Action key for 'then' action
* \param[in] flags Group of enum pcmk__action_relation_flags
*/
#define pcmk__order_resource_actions(first_rsc, first_task, \
then_rsc, then_task, flags) \
pcmk__new_ordering((first_rsc), \
pcmk__op_key((first_rsc)->id, (first_task), 0), \
NULL, \
(then_rsc), \
pcmk__op_key((then_rsc)->id, (then_task), 0), \
NULL, (flags), (first_rsc)->priv->scheduler)
#define pcmk__order_starts(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), PCMK_ACTION_START, \
(rsc2), PCMK_ACTION_START, (flags))
#define pcmk__order_stops(rsc1, rsc2, flags) \
pcmk__order_resource_actions((rsc1), PCMK_ACTION_STOP, \
(rsc2), PCMK_ACTION_STOP, (flags))
// Ticket constraints (pcmk_sched_tickets.c)
G_GNUC_INTERNAL
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler);
// Promotable clone resources (pcmk_sched_promotable.c)
G_GNUC_INTERNAL
void pcmk__add_promotion_scores(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__require_promotion_tickets(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__set_instance_roles(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_promotable_actions(pcmk_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__promotable_restart_ordering(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__order_promotable_instances(pcmk_resource_t *clone);
G_GNUC_INTERNAL
void pcmk__update_dependent_with_promotable(const pcmk_resource_t *primary,
pcmk_resource_t *dependent,
const pcmk__colocation_t
*colocation);
G_GNUC_INTERNAL
int pcmk__update_promotable_dependent_priority(const pcmk_resource_t *primary,
pcmk_resource_t *dependent,
const pcmk__colocation_t
*colocation);
// Pacemaker Remote nodes (pcmk_sched_remote.c)
G_GNUC_INTERNAL
bool pcmk__is_failed_remote_node(const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_remote_connection_actions(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__rsc_corresponds_to_guest(const pcmk_resource_t *rsc,
const pcmk_node_t *node);
G_GNUC_INTERNAL
pcmk_node_t *pcmk__connection_host_for_action(const pcmk_action_t *action);
G_GNUC_INTERNAL
void pcmk__substitute_remote_addr(pcmk_resource_t *rsc, GHashTable *params);
G_GNUC_INTERNAL
void pcmk__add_guest_meta_to_xml(xmlNode *args_xml,
const pcmk_action_t *action);
// Primitives (pcmk_sched_primitive.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__primitive_assign(pcmk_resource_t *rsc,
const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__primitive_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__primitive_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
uint32_t pcmk__primitive_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
int pcmk__primitive_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_primitive_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__primitive_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__schedule_cleanup(pcmk_resource_t *rsc, const pcmk_node_t *node,
bool optional);
G_GNUC_INTERNAL
void pcmk__primitive_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__primitive_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__primitive_shutdown_lock(pcmk_resource_t *rsc);
// Groups (pcmk_sched_group.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__group_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__group_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
int pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_group_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__group_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
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);
G_GNUC_INTERNAL
void pcmk__group_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *location);
G_GNUC_INTERNAL
uint32_t pcmk__group_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
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);
G_GNUC_INTERNAL
GList *pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__group_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__group_shutdown_lock(pcmk_resource_t *rsc);
// Clones (pcmk_sched_clone.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__clone_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__clone_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__clone_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__clone_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
int pcmk__clone_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_clone_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__clone_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__clone_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *constraint);
G_GNUC_INTERNAL
uint32_t pcmk__clone_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__clone_add_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__clone_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__clone_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__clone_shutdown_lock(pcmk_resource_t *rsc);
// Bundles (pcmk_sched_bundle.c)
G_GNUC_INTERNAL
pcmk_node_t *pcmk__bundle_assign(pcmk_resource_t *rsc,
const pcmk_node_t *prefer, bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__bundle_create_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__bundle_create_probe(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__bundle_internal_constraints(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
int pcmk__bundle_apply_coloc_score(pcmk_resource_t *dependent,
const pcmk_resource_t *primary,
const pcmk__colocation_t *colocation,
bool for_dependent);
G_GNUC_INTERNAL
void pcmk__with_bundle_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__bundle_with_colocations(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList **list);
G_GNUC_INTERNAL
void pcmk__bundle_apply_location(pcmk_resource_t *rsc,
pcmk__location_t *constraint);
G_GNUC_INTERNAL
uint32_t pcmk__bundle_action_flags(pcmk_action_t *action,
const pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__output_bundle_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__bundle_add_actions_to_graph(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__bundle_add_utilization(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *all_rscs, GHashTable *utilization);
G_GNUC_INTERNAL
void pcmk__bundle_shutdown_lock(pcmk_resource_t *rsc);
// Clone instances or bundle replica containers (pcmk_sched_instances.c)
G_GNUC_INTERNAL
void pcmk__assign_instances(pcmk_resource_t *collective, GList *instances,
int max_total, int max_per_node);
G_GNUC_INTERNAL
void pcmk__create_instance_actions(pcmk_resource_t *rsc, GList *instances);
G_GNUC_INTERNAL
bool pcmk__instance_matches(const pcmk_resource_t *instance,
const pcmk_node_t *node, enum rsc_role_e role,
bool current);
G_GNUC_INTERNAL
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);
G_GNUC_INTERNAL
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);
G_GNUC_INTERNAL
uint32_t pcmk__collective_action_flags(pcmk_action_t *action,
const GList *instances,
const pcmk_node_t *node);
// Injections (pcmk_injections.c)
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node,
bool up);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
const char *resource,
const char *lrm_name,
const char *rclass,
const char *rtype,
const char *rprovider);
G_GNUC_INTERNAL
void pcmk__inject_failcount(pcmk__output_t *out, cib_t *cib_conn,
xmlNode *cib_node, const char *resource,
- const char *task, guint interval_ms, int rc);
+ const char *task, guint interval_ms, int rc,
+ bool infinity);
G_GNUC_INTERNAL
xmlNode *pcmk__inject_action_result(xmlNode *cib_resource,
lrmd_event_data_t *op, const char *node,
int target_rc);
// Nodes (pcmk_sched_nodes.c)
G_GNUC_INTERNAL
bool pcmk__node_available(const pcmk_node_t *node, bool consider_score,
bool consider_guest);
G_GNUC_INTERNAL
bool pcmk__any_node_available(GHashTable *nodes);
G_GNUC_INTERNAL
GHashTable *pcmk__copy_node_table(GHashTable *nodes);
G_GNUC_INTERNAL
void pcmk__copy_node_tables(const pcmk_resource_t *rsc, GHashTable **copy);
G_GNUC_INTERNAL
void pcmk__restore_node_tables(pcmk_resource_t *rsc, GHashTable *backup);
G_GNUC_INTERNAL
GList *pcmk__sort_nodes(GList *nodes, pcmk_node_t *active_node);
G_GNUC_INTERNAL
void pcmk__apply_node_health(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
pcmk_node_t *pcmk__top_allowed_node(const pcmk_resource_t *rsc,
const pcmk_node_t *node);
// Functions applying to more than one variant (pcmk_sched_resource.c)
G_GNUC_INTERNAL
void pcmk__set_assignment_methods(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__rsc_agent_changed(pcmk_resource_t *rsc, pcmk_node_t *node,
const xmlNode *rsc_entry, bool active_on_node);
G_GNUC_INTERNAL
GList *pcmk__rscs_matching_id(const char *id,
const pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
GList *pcmk__colocated_resources(const pcmk_resource_t *rsc,
const pcmk_resource_t *orig_rsc,
GList *colocated_rscs);
G_GNUC_INTERNAL
void pcmk__noop_add_graph_meta(const pcmk_resource_t *rsc, xmlNode *xml);
G_GNUC_INTERNAL
void pcmk__output_resource_actions(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__assign_resource(pcmk_resource_t *rsc, pcmk_node_t *node, bool force,
bool stop_if_fail);
G_GNUC_INTERNAL
void pcmk__unassign_resource(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
bool pcmk__threshold_reached(pcmk_resource_t *rsc, const pcmk_node_t *node,
pcmk_resource_t **failed);
G_GNUC_INTERNAL
void pcmk__sort_resources(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
gint pcmk__cmp_instance(gconstpointer a, gconstpointer b);
G_GNUC_INTERNAL
gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b);
// Functions related to probes (pcmk_sched_probes.c)
G_GNUC_INTERNAL
bool pcmk__probe_rsc_on_node(pcmk_resource_t *rsc, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__order_probes(pcmk_scheduler_t *scheduler);
G_GNUC_INTERNAL
bool pcmk__probe_resource_list(GList *rscs, pcmk_node_t *node);
G_GNUC_INTERNAL
void pcmk__schedule_probes(pcmk_scheduler_t *scheduler);
// Functions related to live migration (pcmk_sched_migration.c)
void pcmk__create_migration_actions(pcmk_resource_t *rsc,
const pcmk_node_t *current);
void pcmk__abort_dangling_migration(void *data, void *user_data);
bool pcmk__rsc_can_migrate(const pcmk_resource_t *rsc,
const pcmk_node_t *current);
void pcmk__order_migration_equivalents(pcmk__action_relation_t *order);
// Functions related to node utilization (pcmk_sched_utilization.c)
G_GNUC_INTERNAL
int pcmk__compare_node_capacities(const pcmk_node_t *node1,
const pcmk_node_t *node2);
G_GNUC_INTERNAL
void pcmk__consume_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__release_node_capacity(GHashTable *current_utilization,
const pcmk_resource_t *rsc);
G_GNUC_INTERNAL
const pcmk_node_t *pcmk__ban_insufficient_capacity(pcmk_resource_t *rsc);
G_GNUC_INTERNAL
void pcmk__create_utilization_constraints(pcmk_resource_t *rsc,
const GList *allowed_nodes);
G_GNUC_INTERNAL
void pcmk__show_node_capacities(const char *desc, pcmk_scheduler_t *scheduler);
// Functions related to the scheduler (pcmk_scheduler.c)
G_GNUC_INTERNAL
int pcmk__init_scheduler(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *date,
pcmk_scheduler_t **scheduler);
// General setup functions (pcmk_setup.c)
G_GNUC_INTERNAL
int pcmk__setup_output_cib_sched(pcmk__output_t **out, cib_t **cib,
pcmk_scheduler_t **scheduler, xmlNode **xml);
G_GNUC_INTERNAL
int pcmk__setup_output_fencing(pcmk__output_t **out, stonith_t **st, xmlNode **xml);
#ifdef __cplusplus
}
#endif
#endif // PCMK__PACEMAKER_LIBPACEMAKER_PRIVATE__H
diff --git a/lib/pacemaker/pcmk_graph_consumer.c b/lib/pacemaker/pcmk_graph_consumer.c
index b0735e38da..745ff4b525 100644
--- a/lib/pacemaker/pcmk_graph_consumer.c
+++ b/lib/pacemaker/pcmk_graph_consumer.c
@@ -1,868 +1,868 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <crm/lrmd_internal.h>
#include <pacemaker-internal.h>
/*
* Functions for freeing transition graph objects
*/
/*!
* \internal
* \brief Free a transition graph action object
*
* \param[in,out] user_data Action to free
*/
static void
free_graph_action(gpointer user_data)
{
pcmk__graph_action_t *action = user_data;
if (action->timer != 0) {
crm_warn("Cancelling timer for graph action %d", action->id);
g_source_remove(action->timer);
}
if (action->params != NULL) {
g_hash_table_destroy(action->params);
}
pcmk__xml_free(action->xml);
free(action);
}
/*!
* \internal
* \brief Free a transition graph synapse object
*
* \param[in,out] user_data Synapse to free
*/
static void
free_graph_synapse(gpointer user_data)
{
pcmk__graph_synapse_t *synapse = user_data;
g_list_free_full(synapse->actions, free_graph_action);
g_list_free_full(synapse->inputs, free_graph_action);
free(synapse);
}
/*!
* \internal
* \brief Free a transition graph object
*
* \param[in,out] graph Transition graph to free
*/
void
pcmk__free_graph(pcmk__graph_t *graph)
{
if (graph != NULL) {
g_list_free_full(graph->synapses, free_graph_synapse);
free(graph->source);
free(graph->failed_stop_offset);
free(graph->failed_start_offset);
free(graph);
}
}
/*
* Functions for updating graph
*/
/*!
* \internal
* \brief Update synapse after completed prerequisite
*
* A synapse is ready to be executed once all its prerequisite actions (inputs)
* complete. Given a completed action, check whether it is an input for a given
* synapse, and if so, mark the input as confirmed, and mark the synapse as
* ready if appropriate.
*
* \param[in,out] synapse Transition graph synapse to update
* \param[in] action_id ID of an action that completed
*
* \note The only substantial effect here is confirming synapse inputs.
* should_fire_synapse() will recalculate pcmk__synapse_ready, so the only
* thing that uses the pcmk__synapse_ready from here is
* synapse_state_str().
*/
static void
update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id)
{
if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
return; // All inputs have already been confirmed
}
// Presume ready until proven otherwise
pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
if (prereq->id == action_id) {
crm_trace("Confirming input %d of synapse %d",
action_id, synapse->id);
pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed);
} else if (!pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed)) {
pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
crm_trace("Synapse %d still not ready after action %d",
synapse->id, action_id);
}
}
if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
crm_trace("Synapse %d is now ready to execute", synapse->id);
}
}
/*!
* \internal
* \brief Update action and synapse confirmation after action completion
*
* \param[in,out] synapse Transition graph synapse that action belongs to
* \param[in] action_id ID of action that completed
*/
static void
update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id)
{
bool all_confirmed = true;
for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
if (action->id == action_id) {
crm_trace("Confirmed action %d of synapse %d",
action_id, synapse->id);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
} else if (all_confirmed &&
!pcmk_is_set(action->flags, pcmk__graph_action_confirmed)) {
all_confirmed = false;
crm_trace("Synapse %d still not confirmed after action %d",
synapse->id, action_id);
}
}
if (all_confirmed
&& !pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
crm_trace("Confirmed synapse %d", synapse->id);
pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
}
}
/*!
* \internal
* \brief Update the transition graph with a completed action result
*
* \param[in,out] graph Transition graph to update
* \param[in] action Action that completed
*/
void
pcmk__update_graph(pcmk__graph_t *graph, const pcmk__graph_action_t *action)
{
for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
if (pcmk_any_flags_set(synapse->flags,
pcmk__synapse_confirmed|pcmk__synapse_failed)) {
continue; // This synapse already completed
} else if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
update_synapse_confirmed(synapse, action->id);
} else if (!pcmk_is_set(action->flags, pcmk__graph_action_failed)
|| (synapse->priority == PCMK_SCORE_INFINITY)) {
update_synapse_ready(synapse, action->id);
}
}
}
/*
* Functions for executing graph
*/
/* A transition graph consists of various types of actions. The library caller
* registers execution functions for each action type, which will be stored
* here.
*/
static pcmk__graph_functions_t *graph_fns = NULL;
/*!
* \internal
* \brief Set transition graph execution functions
*
* \param[in] Execution functions to use
*/
void
pcmk__set_graph_functions(pcmk__graph_functions_t *fns)
{
pcmk__assert((fns != NULL) && (fns->rsc != NULL) && (fns->cluster != NULL)
&& (fns->pseudo != NULL) && (fns->fence != NULL));
crm_debug("Setting custom functions for executing transition graphs");
graph_fns = fns;
}
/*!
* \internal
* \brief Check whether a graph synapse is ready to be executed
*
* \param[in,out] graph Transition graph that synapse is part of
* \param[in,out] synapse Synapse to check
*
* \return true if synapse is ready, false otherwise
*/
static bool
should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
{
GList *lpc = NULL;
pcmk__set_synapse_flags(synapse, pcmk__synapse_ready);
for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) {
pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data;
if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) {
crm_trace("Input %d for synapse %d not yet confirmed",
prereq->id, synapse->id);
pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
break;
} else if (pcmk_is_set(prereq->flags, pcmk__graph_action_failed)) {
crm_trace("Input %d for synapse %d confirmed but failed",
prereq->id, synapse->id);
pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready);
break;
}
}
if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) {
crm_trace("Synapse %d is ready to execute", synapse->id);
} else {
return false;
}
for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data;
if (a->type == pcmk__pseudo_graph_action) {
/* None of the below applies to pseudo ops */
} else if (synapse->priority < graph->abort_priority) {
crm_trace("Skipping synapse %d: priority %d is less than "
"abort priority %d",
synapse->id, synapse->priority, graph->abort_priority);
graph->skipped++;
return false;
} else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) {
crm_trace("Deferring synapse %d: not allowed", synapse->id);
return false;
}
}
return true;
}
/*!
* \internal
* \brief Initiate an action from a transition graph
*
* \param[in,out] graph Transition graph containing action
* \param[in,out] action Action to execute
*
* \return Standard Pacemaker return code
*/
static int
initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *id = pcmk__xe_id(action->xml);
CRM_CHECK(id != NULL, return EINVAL);
CRM_CHECK(!pcmk_is_set(action->flags, pcmk__graph_action_executed),
return pcmk_rc_already);
pcmk__set_graph_action_flags(action, pcmk__graph_action_executed);
switch (action->type) {
case pcmk__pseudo_graph_action:
crm_trace("Executing pseudo-action %d (%s)", action->id, id);
return graph_fns->pseudo(graph, action);
case pcmk__rsc_graph_action:
crm_trace("Executing resource action %d (%s)", action->id, id);
return graph_fns->rsc(graph, action);
case pcmk__cluster_graph_action:
if (pcmk__str_eq(crm_element_value(action->xml, PCMK_XA_OPERATION),
PCMK_ACTION_STONITH, pcmk__str_none)) {
crm_trace("Executing fencing action %d (%s)",
action->id, id);
return graph_fns->fence(graph, action);
}
crm_trace("Executing cluster action %d (%s)", action->id, id);
return graph_fns->cluster(graph, action);
default:
crm_err("Unsupported graph action type <%s " PCMK_XA_ID "='%s'> "
"(bug?)",
action->xml->name, id);
return EINVAL;
}
}
/*!
* \internal
* \brief Execute a graph synapse
*
* \param[in,out] graph Transition graph with synapse to execute
* \param[in,out] synapse Synapse to execute
*
* \return Standard Pacemaker return value
*/
static int
fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse)
{
pcmk__set_synapse_flags(synapse, pcmk__synapse_executed);
for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) {
pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data;
int rc = initiate_action(graph, action);
if (rc != pcmk_rc_ok) {
crm_err("Failed initiating <%s " PCMK_XA_ID "=%d> in synapse %d: "
"%s",
action->xml->name, action->id, synapse->id,
pcmk_rc_str(rc));
pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed);
pcmk__set_graph_action_flags(action,
pcmk__graph_action_confirmed
|pcmk__graph_action_failed);
return pcmk_rc_error;
}
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Dummy graph method that can be used with simulations
*
* \param[in,out] graph Transition graph containing action
* \param[in,out] action Graph action to be initiated
*
* \return Standard Pacemaker return code
* \note If the PE_fail environment variable is set to the action ID,
* then the graph action will be marked as failed.
*/
static int
pseudo_action_dummy(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
static int fail = -1;
if (fail < 0) {
long long fail_ll;
if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok)
&& (fail_ll > 0LL) && (fail_ll <= INT_MAX)) {
fail = (int) fail_ll;
} else {
fail = 0;
}
}
if (action->id == fail) {
crm_err("Dummy event handler: pretending action %d failed", action->id);
pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
graph->abort_priority = PCMK_SCORE_INFINITY;
} else {
crm_trace("Dummy event handler: action %d initiated", action->id);
}
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
static pcmk__graph_functions_t default_fns = {
pseudo_action_dummy,
pseudo_action_dummy,
pseudo_action_dummy,
pseudo_action_dummy
};
/*!
* \internal
* \brief Execute all actions in a transition graph
*
* \param[in,out] graph Transition graph to execute
*
* \return Status of transition after execution
*/
enum pcmk__graph_status
pcmk__execute_graph(pcmk__graph_t *graph)
{
GList *lpc = NULL;
int log_level = LOG_DEBUG;
enum pcmk__graph_status pass_result = pcmk__graph_active;
const char *status = "In progress";
if (graph_fns == NULL) {
graph_fns = &default_fns;
}
if (graph == NULL) {
return pcmk__graph_complete;
}
graph->fired = 0;
graph->pending = 0;
graph->skipped = 0;
graph->completed = 0;
graph->incomplete = 0;
// Count completed and in-flight synapses
for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) {
graph->completed++;
} else if (!pcmk_is_set(synapse->flags, pcmk__synapse_failed)
&& pcmk_is_set(synapse->flags, pcmk__synapse_executed)) {
graph->pending++;
}
}
crm_trace("Executing graph %d (%d synapses already completed, %d pending)",
graph->id, graph->completed, graph->pending);
// Execute any synapses that are ready
for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) {
pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data;
if ((graph->batch_limit > 0)
&& (graph->pending >= graph->batch_limit)) {
crm_debug("Throttling graph execution: batch limit (%d) reached",
graph->batch_limit);
break;
} else if (pcmk_is_set(synapse->flags, pcmk__synapse_failed)) {
graph->skipped++;
continue;
} else if (pcmk_any_flags_set(synapse->flags,
pcmk__synapse_confirmed
|pcmk__synapse_executed)) {
continue; // Already handled
} else if (should_fire_synapse(graph, synapse)) {
graph->fired++;
if (fire_synapse(graph, synapse) != pcmk_rc_ok) {
crm_err("Synapse %d failed to fire", synapse->id);
log_level = LOG_ERR;
graph->abort_priority = PCMK_SCORE_INFINITY;
graph->incomplete++;
graph->fired--;
}
if (!(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) {
graph->pending++;
}
} else {
crm_trace("Synapse %d cannot fire", synapse->id);
graph->incomplete++;
}
}
if ((graph->pending == 0) && (graph->fired == 0)) {
graph->complete = true;
if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) {
log_level = LOG_WARNING;
pass_result = pcmk__graph_terminated;
status = "Terminated";
} else if (graph->skipped != 0) {
log_level = LOG_NOTICE;
pass_result = pcmk__graph_complete;
status = "Stopped";
} else {
log_level = LOG_NOTICE;
pass_result = pcmk__graph_complete;
status = "Complete";
}
} else if (graph->fired == 0) {
pass_result = pcmk__graph_pending;
}
do_crm_log(log_level,
"Transition %d (Complete=%d, Pending=%d,"
" Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s",
graph->id, graph->completed, graph->pending, graph->fired,
graph->skipped, graph->incomplete, graph->source, status);
return pass_result;
}
/*
* Functions for unpacking transition graph XML into structs
*/
/*!
* \internal
* \brief Unpack a transition graph action from XML
*
* \param[in] parent Synapse that action is part of
* \param[in] xml_action Action XML to unparse
*
* \return Newly allocated action on success, or NULL otherwise
*/
static pcmk__graph_action_t *
unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action)
{
enum pcmk__graph_action_type action_type;
pcmk__graph_action_t *action = NULL;
const char *value = pcmk__xe_id(xml_action);
if (value == NULL) {
crm_err("Ignoring transition graph action without " PCMK_XA_ID
" (bug?)");
crm_log_xml_trace(xml_action, "invalid");
return NULL;
}
if (pcmk__xe_is(xml_action, PCMK__XE_RSC_OP)) {
action_type = pcmk__rsc_graph_action;
} else if (pcmk__xe_is(xml_action, PCMK__XE_PSEUDO_EVENT)) {
action_type = pcmk__pseudo_graph_action;
} else if (pcmk__xe_is(xml_action, PCMK__XE_CRM_EVENT)) {
action_type = pcmk__cluster_graph_action;
} else {
crm_err("Ignoring transition graph action of unknown type '%s' (bug?)",
xml_action->name);
crm_log_xml_trace(xml_action, "invalid");
return NULL;
}
action = calloc(1, sizeof(pcmk__graph_action_t));
if (action == NULL) {
crm_perror(LOG_CRIT, "Cannot unpack transition graph action");
crm_log_xml_trace(xml_action, "lost");
return NULL;
}
pcmk__scan_min_int(value, &(action->id), -1);
action->type = pcmk__rsc_graph_action;
action->xml = pcmk__xml_copy(NULL, xml_action);
action->synapse = parent;
action->type = action_type;
action->params = xml2list(action->xml);
value = crm_meta_value(action->params, PCMK_META_TIMEOUT);
pcmk__scan_min_int(value, &(action->timeout), 0);
/* Take PCMK_META_START_DELAY into account for the timeout of the action
* timer
*/
value = crm_meta_value(action->params, PCMK_META_START_DELAY);
{
int start_delay;
pcmk__scan_min_int(value, &start_delay, 0);
action->timeout += start_delay;
}
if (pcmk__guint_from_hash(action->params, CRM_META "_" PCMK_META_INTERVAL,
0, &(action->interval_ms)) != pcmk_rc_ok) {
action->interval_ms = 0;
}
crm_trace("Action %d has timer set to %dms", action->id, action->timeout);
return action;
}
/*!
* \internal
* \brief Unpack transition graph synapse from XML
*
* \param[in,out] new_graph Transition graph that synapse is part of
* \param[in] xml_synapse Synapse XML
*
* \return Newly allocated synapse on success, or NULL otherwise
*/
static pcmk__graph_synapse_t *
unpack_synapse(pcmk__graph_t *new_graph, const xmlNode *xml_synapse)
{
const char *value = NULL;
xmlNode *action_set = NULL;
pcmk__graph_synapse_t *new_synapse = NULL;
crm_trace("Unpacking synapse %s", pcmk__xe_id(xml_synapse));
new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t));
if (new_synapse == NULL) {
return NULL;
}
pcmk__scan_min_int(pcmk__xe_id(xml_synapse), &(new_synapse->id), 0);
value = crm_element_value(xml_synapse, PCMK__XA_PRIORITY);
pcmk__scan_min_int(value, &(new_synapse->priority), 0);
CRM_CHECK(new_synapse->id >= 0,
free_graph_synapse((gpointer) new_synapse); return NULL);
new_graph->num_synapses++;
crm_trace("Unpacking synapse %s action sets",
crm_element_value(xml_synapse, PCMK_XA_ID));
for (action_set = pcmk__xe_first_child(xml_synapse, PCMK__XE_ACTION_SET,
NULL, NULL);
action_set != NULL;
action_set = pcmk__xe_next(action_set, PCMK__XE_ACTION_SET)) {
for (xmlNode *action = pcmk__xe_first_child(action_set, NULL, NULL,
NULL);
action != NULL; action = pcmk__xe_next(action, NULL)) {
pcmk__graph_action_t *new_action = unpack_action(new_synapse,
action);
if (new_action == NULL) {
continue;
}
crm_trace("Adding action %d to synapse %d",
new_action->id, new_synapse->id);
new_graph->num_actions++;
new_synapse->actions = g_list_append(new_synapse->actions,
new_action);
}
}
crm_trace("Unpacking synapse %s inputs", pcmk__xe_id(xml_synapse));
for (xmlNode *inputs = pcmk__xe_first_child(xml_synapse, PCMK__XE_INPUTS,
NULL, NULL);
inputs != NULL; inputs = pcmk__xe_next(inputs, PCMK__XE_INPUTS)) {
for (xmlNode *trigger = pcmk__xe_first_child(inputs, PCMK__XE_TRIGGER,
NULL, NULL);
trigger != NULL;
trigger = pcmk__xe_next(trigger, PCMK__XE_TRIGGER)) {
for (xmlNode *input = pcmk__xe_first_child(trigger, NULL, NULL,
NULL);
input != NULL; input = pcmk__xe_next(input, NULL)) {
pcmk__graph_action_t *new_input = unpack_action(new_synapse,
input);
if (new_input == NULL) {
continue;
}
crm_trace("Adding input %d to synapse %d",
new_input->id, new_synapse->id);
new_synapse->inputs = g_list_append(new_synapse->inputs,
new_input);
}
}
}
return new_synapse;
}
/*!
* \internal
* \brief Unpack transition graph XML
*
* \param[in] xml_graph Transition graph XML to unpack
* \param[in] reference Where the XML came from (for logging)
*
* \return Newly allocated transition graph on success, NULL otherwise
* \note The caller is responsible for freeing the return value using
* pcmk__free_graph().
* \note The XML is expected to be structured like:
<transition_graph ...>
<synapse id="0">
<action_set>
<rsc_op id="2" ...>
...
</action_set>
<inputs>
<rsc_op id="1" ...
...
</inputs>
</synapse>
...
</transition_graph>
*/
pcmk__graph_t *
pcmk__unpack_graph(const xmlNode *xml_graph, const char *reference)
{
pcmk__graph_t *new_graph = NULL;
new_graph = calloc(1, sizeof(pcmk__graph_t));
if (new_graph == NULL) {
return NULL;
}
new_graph->source = strdup(pcmk__s(reference, "unknown"));
if (new_graph->source == NULL) {
pcmk__free_graph(new_graph);
return NULL;
}
new_graph->completion_action = pcmk__graph_done;
// Parse top-level attributes from PCMK__XE_TRANSITION_GRAPH
if (xml_graph != NULL) {
const char *buf = crm_element_value(xml_graph, "transition_id");
CRM_CHECK(buf != NULL,
pcmk__free_graph(new_graph); return NULL);
pcmk__scan_min_int(buf, &(new_graph->id), 1);
buf = crm_element_value(xml_graph, PCMK_OPT_CLUSTER_DELAY);
CRM_CHECK(buf != NULL,
pcmk__free_graph(new_graph); return NULL);
pcmk_parse_interval_spec(buf, &(new_graph->network_delay));
buf = crm_element_value(xml_graph, PCMK_OPT_STONITH_TIMEOUT);
if (buf == NULL) {
new_graph->stonith_timeout = new_graph->network_delay;
} else {
pcmk_parse_interval_spec(buf, &(new_graph->stonith_timeout));
}
// Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum
buf = crm_element_value(xml_graph, PCMK_OPT_BATCH_LIMIT);
if ((buf == NULL)
|| (pcmk__scan_min_int(buf, &(new_graph->batch_limit),
-1) != pcmk_rc_ok)) {
new_graph->batch_limit = 0;
}
buf = crm_element_value(xml_graph, PCMK_OPT_MIGRATION_LIMIT);
pcmk__scan_min_int(buf, &(new_graph->migration_limit), -1);
new_graph->failed_stop_offset =
- crm_element_value_copy(xml_graph, "failed-stop-offset");
+ crm_element_value_copy(xml_graph, PCMK__XA_FAILED_STOP_OFFSET);
new_graph->failed_start_offset =
- crm_element_value_copy(xml_graph, "failed-start-offset");
+ crm_element_value_copy(xml_graph, PCMK__XA_FAILED_START_OFFSET);
if (crm_element_value_epoch(xml_graph, "recheck-by",
&(new_graph->recheck_by)) != pcmk_ok) {
new_graph->recheck_by = 0;
}
}
// Unpack each child <synapse> element
for (const xmlNode *synapse_xml = pcmk__xe_first_child(xml_graph,
PCMK__XE_SYNAPSE,
NULL, NULL);
synapse_xml != NULL;
synapse_xml = pcmk__xe_next(synapse_xml, PCMK__XE_SYNAPSE)) {
pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph,
synapse_xml);
if (new_synapse != NULL) {
new_graph->synapses = g_list_append(new_graph->synapses,
new_synapse);
}
}
crm_debug("Unpacked transition %d from %s: %d actions in %d synapses",
new_graph->id, new_graph->source, new_graph->num_actions,
new_graph->num_synapses);
return new_graph;
}
/*
* Other transition graph utilities
*/
/*!
* \internal
* \brief Synthesize an executor event from a graph action
*
* \param[in] resource If not NULL, use greater call ID than in this XML
* \param[in] action Graph action
* \param[in] status What to use as event execution status
* \param[in] rc What to use as event exit status
* \param[in] exit_reason What to use as event exit reason
*
* \return Newly allocated executor event on success, or NULL otherwise
*/
lrmd_event_data_t *
pcmk__event_from_graph_action(const xmlNode *resource,
const pcmk__graph_action_t *action,
int status, int rc, const char *exit_reason)
{
lrmd_event_data_t *op = NULL;
GHashTableIter iter;
const char *name = NULL;
const char *value = NULL;
xmlNode *action_resource = NULL;
CRM_CHECK(action != NULL, return NULL);
CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL);
action_resource = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
NULL);
CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid");
return NULL);
op = lrmd_new_event(pcmk__xe_id(action_resource),
crm_element_value(action->xml, PCMK_XA_OPERATION),
action->interval_ms);
lrmd__set_result(op, rc, status, exit_reason);
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
op->params = pcmk__strkey_table(free, free);
g_hash_table_iter_init(&iter, action->params);
while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) {
pcmk__insert_dup(op->params, name, value);
}
for (xmlNode *xop = pcmk__xe_first_child(resource, NULL, NULL, NULL);
xop != NULL; xop = pcmk__xe_next(xop, NULL)) {
int tmp = 0;
crm_element_value_int(xop, PCMK__XA_CALL_ID, &tmp);
crm_debug("Got call_id=%d for %s", tmp, pcmk__xe_id(resource));
if (tmp > op->call_id) {
op->call_id = tmp;
}
}
op->call_id++;
return op;
}
diff --git a/lib/pacemaker/pcmk_graph_producer.c b/lib/pacemaker/pcmk_graph_producer.c
index 352001c753..31ea0227d6 100644
--- a/lib/pacemaker/pcmk_graph_producer.c
+++ b/lib/pacemaker/pcmk_graph_producer.c
@@ -1,1105 +1,1107 @@
/*
* 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 <glib.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
// 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->priv->name)
/*!
* \internal
* \brief Add an XML node tag for a specified ID
*
* \param[in] id Node UUID to add
* \param[in,out] xml Parent XML tag to add to
*/
static xmlNode*
add_node_to_xml_by_id(const char *id, xmlNode *xml)
{
xmlNode *node_xml;
node_xml = pcmk__xe_create(xml, PCMK_XE_NODE);
crm_xml_add(node_xml, PCMK_XA_ID, id);
return node_xml;
}
/*!
* \internal
* \brief Add an XML node tag for a specified node
*
* \param[in] node Node to add
* \param[in,out] xml XML to add node to
*/
static void
add_node_to_xml(const pcmk_node_t *node, void *xml)
{
add_node_to_xml_by_id(node->priv->id, (xmlNode *) xml);
}
/*!
* \internal
* \brief Count (optionally add to XML) nodes needing maintenance state update
*
* \param[in,out] xml Parent XML tag to add to, if any
* \param[in] scheduler Scheduler data
*
* \return Count of nodes added
* \note Only Pacemaker Remote nodes are considered currently
*/
static int
add_maintenance_nodes(xmlNode *xml, const pcmk_scheduler_t *scheduler)
{
xmlNode *maintenance = NULL;
int count = 0;
if (xml != NULL) {
maintenance = pcmk__xe_create(xml, PCMK__XE_MAINTENANCE);
}
for (const GList *iter = scheduler->nodes;
iter != NULL; iter = iter->next) {
const pcmk_node_t *node = iter->data;
if (!pcmk__is_pacemaker_remote_node(node)) {
continue;
}
if ((node->details->maintenance
&& !pcmk_is_set(node->priv->flags, pcmk__node_remote_maint))
|| (!node->details->maintenance
&& pcmk_is_set(node->priv->flags, pcmk__node_remote_maint))) {
if (maintenance != NULL) {
crm_xml_add(add_node_to_xml_by_id(node->priv->id,
maintenance),
PCMK__XA_NODE_IN_MAINTENANCE,
(node->details->maintenance? "1" : "0"));
}
count++;
}
}
crm_trace("%s %d nodes in need of maintenance mode update in state",
((maintenance == NULL)? "Counted" : "Added"), count);
return count;
}
/*!
* \internal
* \brief Add pseudo action with nodes needing maintenance state update
*
* \param[in,out] scheduler Scheduler data
*/
static void
add_maintenance_update(pcmk_scheduler_t *scheduler)
{
pcmk_action_t *action = NULL;
if (add_maintenance_nodes(NULL, scheduler) != 0) {
action = get_pseudo_op(PCMK_ACTION_MAINTENANCE_NODES, scheduler);
pcmk__set_action_flags(action, pcmk__action_always_in_graph);
}
}
/*!
* \internal
* \brief Add XML with nodes that an action is expected to bring down
*
* If a specified action is expected to bring any nodes down, add an XML block
* with their UUIDs. When a node is lost, this allows the controller to
* determine whether it was expected.
*
* \param[in,out] xml Parent XML tag to add to
* \param[in] action Action to check for downed nodes
*/
static void
add_downed_nodes(xmlNode *xml, const pcmk_action_t *action)
{
CRM_CHECK((xml != NULL) && (action != NULL) && (action->node != NULL),
return);
if (pcmk__str_eq(action->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none)) {
/* Shutdown makes the action's node down */
xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
add_node_to_xml_by_id(action->node->priv->id, downed);
} else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
pcmk__str_none)) {
/* Fencing makes the action's node and any hosted guest nodes down */
const char *fence = g_hash_table_lookup(action->meta,
PCMK__META_STONITH_ACTION);
if (pcmk__is_fencing_action(fence)) {
xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
add_node_to_xml_by_id(action->node->priv->id, downed);
pe_foreach_guest_node(action->node->priv->scheduler,
action->node, add_node_to_xml, downed);
}
} else if ((action->rsc != NULL)
&& pcmk_is_set(action->rsc->flags,
pcmk__rsc_is_remote_connection)
&& pcmk__str_eq(action->task, PCMK_ACTION_STOP,
pcmk__str_none)) {
/* Stopping a remote connection resource makes connected node down,
* unless it's part of a migration
*/
GList *iter;
pcmk_action_t *input;
bool migrating = false;
for (iter = action->actions_before; iter != NULL; iter = iter->next) {
input = ((pcmk__related_action_t *) iter->data)->action;
if ((input->rsc != NULL)
&& pcmk__str_eq(action->rsc->id, input->rsc->id, pcmk__str_none)
&& pcmk__str_eq(input->task, PCMK_ACTION_MIGRATE_FROM,
pcmk__str_none)) {
migrating = true;
break;
}
}
if (!migrating) {
xmlNode *downed = pcmk__xe_create(xml, PCMK__XE_DOWNED);
add_node_to_xml_by_id(action->rsc->id, downed);
}
}
}
/*!
* \internal
* \brief Create a transition graph operation key for a clone action
*
* \param[in] action Clone action
* \param[in] interval_ms Action interval in milliseconds
*
* \return Newly allocated string with transition graph operation key
*/
static char *
clone_op_key(const pcmk_action_t *action, guint interval_ms)
{
if (pcmk__str_eq(action->task, PCMK_ACTION_NOTIFY, pcmk__str_none)) {
const char *n_type = g_hash_table_lookup(action->meta, "notify_type");
const char *n_task = g_hash_table_lookup(action->meta,
"notify_operation");
return pcmk__notify_key(action->rsc->priv->history_id, n_type,
n_task);
}
return pcmk__op_key(action->rsc->priv->history_id,
pcmk__s(action->cancel_task, action->task),
interval_ms);
}
/*!
* \internal
* \brief Add node details to transition graph action XML
*
* \param[in] action Scheduled action
* \param[in,out] xml Transition graph action XML for \p action
*/
static void
add_node_details(const pcmk_action_t *action, xmlNode *xml)
{
pcmk_node_t *router_node = pcmk__connection_host_for_action(action);
crm_xml_add(xml, PCMK__META_ON_NODE, action->node->priv->name);
crm_xml_add(xml, PCMK__META_ON_NODE_UUID, action->node->priv->id);
if (router_node != NULL) {
crm_xml_add(xml, PCMK__XA_ROUTER_NODE, router_node->priv->name);
}
}
/*!
* \internal
* \brief Add resource details to transition graph action XML
*
* \param[in] action Scheduled action
* \param[in,out] action_xml Transition graph action XML for \p action
*/
static void
add_resource_details(const pcmk_action_t *action, xmlNode *action_xml)
{
xmlNode *rsc_xml = NULL;
const char *attr_list[] = {
PCMK_XA_CLASS,
PCMK_XA_PROVIDER,
PCMK_XA_TYPE,
};
/* If a resource is locked to a node via PCMK_OPT_SHUTDOWN_LOCK, mark its
* actions so the controller can preserve the lock when the action
* completes.
*/
if (pcmk__action_locks_rsc_to_node(action)) {
crm_xml_add_ll(action_xml, PCMK_OPT_SHUTDOWN_LOCK,
(long long) action->rsc->priv->lock_time);
}
// List affected resource
rsc_xml = pcmk__xe_create(action_xml,
(const char *) action->rsc->priv->xml->name);
if (pcmk_is_set(action->rsc->flags, pcmk__rsc_removed)
&& (action->rsc->priv->history_id != NULL)) {
/* Use the numbered instance name here, because if there is more
* than one instance on a node, we need to make sure the command
* goes to the right one.
*
* This is important even for anonymous clones, because the clone's
* unique meta-attribute might have just been toggled from on to
* off.
*/
crm_debug("Using orphan clone name %s instead of history ID %s",
action->rsc->id, action->rsc->priv->history_id);
crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->priv->history_id);
crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
} else if (!pcmk_is_set(action->rsc->flags, pcmk__rsc_unique)) {
const char *xml_id = pcmk__xe_id(action->rsc->priv->xml);
crm_debug("Using anonymous clone name %s for %s (aka %s)",
xml_id, action->rsc->id, action->rsc->priv->history_id);
/* ID is what we'd like client to use
* LONG_ID is what they might know it as instead
*
* LONG_ID is only strictly needed /here/ during the
* transition period until all nodes in the cluster
* are running the new software /and/ have rebooted
* once (meaning that they've only ever spoken to a DC
* supporting this feature).
*
* If anyone toggles the unique flag to 'on', the
* 'instance free' name will correspond to an orphan
* and fall into the clause above instead
*/
crm_xml_add(rsc_xml, PCMK_XA_ID, xml_id);
if ((action->rsc->priv->history_id != NULL)
&& !pcmk__str_eq(xml_id, action->rsc->priv->history_id,
pcmk__str_none)) {
crm_xml_add(rsc_xml, PCMK__XA_LONG_ID,
action->rsc->priv->history_id);
} else {
crm_xml_add(rsc_xml, PCMK__XA_LONG_ID, action->rsc->id);
}
} else {
pcmk__assert(action->rsc->priv->history_id == NULL);
crm_xml_add(rsc_xml, PCMK_XA_ID, action->rsc->id);
}
for (int lpc = 0; lpc < PCMK__NELEM(attr_list); lpc++) {
crm_xml_add(rsc_xml, attr_list[lpc],
g_hash_table_lookup(action->rsc->priv->meta,
attr_list[lpc]));
}
}
/*!
* \internal
* \brief Add action attributes to transition graph action XML
*
* \param[in,out] action Scheduled action
* \param[in,out] action_xml Transition graph action XML for \p action
*/
static void
add_action_attributes(pcmk_action_t *action, xmlNode *action_xml)
{
xmlNode *args_xml = NULL;
pcmk_resource_t *rsc = action->rsc;
/* We create free-standing XML to start, so we can sort the attributes
* before adding it to action_xml, which keeps the scheduler regression
* test graphs comparable.
*/
args_xml = pcmk__xe_create(action_xml, PCMK__XE_ATTRIBUTES);
crm_xml_add(args_xml, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
g_hash_table_foreach(action->extra, hash2field, args_xml);
if ((rsc != NULL) && (action->node != NULL)) {
// Get the resource instance attributes, evaluated properly for node
GHashTable *params = pe_rsc_params(rsc, action->node,
rsc->priv->scheduler);
pcmk__substitute_remote_addr(rsc, params);
g_hash_table_foreach(params, hash2smartfield, args_xml);
} else if ((rsc != NULL)
&& (rsc->priv->variant <= pcmk__rsc_variant_primitive)) {
GHashTable *params = pe_rsc_params(rsc, NULL, rsc->priv->scheduler);
g_hash_table_foreach(params, hash2smartfield, args_xml);
}
g_hash_table_foreach(action->meta, hash2metafield, args_xml);
if (rsc != NULL) {
pcmk_resource_t *parent = rsc;
while (parent != NULL) {
parent->priv->cmds->add_graph_meta(parent, args_xml);
parent = parent->priv->parent;
}
pcmk__add_guest_meta_to_xml(args_xml, action);
}
pcmk__xe_sort_attrs(args_xml);
}
/*!
* \internal
* \brief Create the transition graph XML for a scheduled action
*
* \param[in,out] parent Parent XML element to add action to
* \param[in,out] action Scheduled action
* \param[in] skip_details If false, add action details as sub-elements
* \param[in] scheduler Scheduler data
*/
static void
create_graph_action(xmlNode *parent, pcmk_action_t *action, bool skip_details,
const pcmk_scheduler_t *scheduler)
{
bool needs_node_info = true;
bool needs_maintenance_info = false;
xmlNode *action_xml = NULL;
if ((action == NULL) || (scheduler == NULL)) {
return;
}
// Create the top-level element based on task
if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) {
/* All fences need node info; guest node fences are pseudo-events */
if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
} else {
action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
}
} else if (pcmk__str_any_of(action->task,
PCMK_ACTION_DO_SHUTDOWN,
PCMK_ACTION_CLEAR_FAILCOUNT, NULL)) {
action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
} else if (pcmk__str_eq(action->task, PCMK_ACTION_LRM_DELETE,
pcmk__str_none)) {
// CIB-only clean-up for shutdown locks
action_xml = pcmk__xe_create(parent, PCMK__XE_CRM_EVENT);
crm_xml_add(action_xml, PCMK__XA_MODE, PCMK__VALUE_CIB);
} else if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
if (pcmk__str_eq(action->task, PCMK_ACTION_MAINTENANCE_NODES,
pcmk__str_none)) {
needs_maintenance_info = true;
}
action_xml = pcmk__xe_create(parent, PCMK__XE_PSEUDO_EVENT);
needs_node_info = false;
} else {
action_xml = pcmk__xe_create(parent, PCMK__XE_RSC_OP);
}
crm_xml_add_int(action_xml, PCMK_XA_ID, action->id);
crm_xml_add(action_xml, PCMK_XA_OPERATION, action->task);
if ((action->rsc != NULL) && (action->rsc->priv->history_id != NULL)) {
char *clone_key = NULL;
guint interval_ms;
if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
&interval_ms) != pcmk_rc_ok) {
interval_ms = 0;
}
clone_key = clone_op_key(action, interval_ms);
crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, clone_key);
crm_xml_add(action_xml, "internal_" PCMK__XA_OPERATION_KEY,
action->uuid);
free(clone_key);
} else {
crm_xml_add(action_xml, PCMK__XA_OPERATION_KEY, action->uuid);
}
if (needs_node_info && (action->node != NULL)) {
add_node_details(action, action_xml);
pcmk__insert_dup(action->meta, PCMK__META_ON_NODE,
action->node->priv->name);
pcmk__insert_dup(action->meta, PCMK__META_ON_NODE_UUID,
action->node->priv->id);
}
if (skip_details) {
return;
}
if ((action->rsc != NULL)
&& !pcmk_is_set(action->flags, pcmk__action_pseudo)) {
// This is a real resource action, so add resource details
add_resource_details(action, action_xml);
}
/* List any attributes in effect */
add_action_attributes(action, action_xml);
/* List any nodes this action is expected to make down */
if (needs_node_info && (action->node != NULL)) {
add_downed_nodes(action_xml, action);
}
if (needs_maintenance_info) {
add_maintenance_nodes(action_xml, scheduler);
}
}
/*!
* \internal
* \brief Check whether an action should be added to the transition graph
*
* \param[in,out] action Action to check
*
* \return true if action should be added to graph, otherwise false
*/
static bool
should_add_action_to_graph(pcmk_action_t *action)
{
if (!pcmk_is_set(action->flags, pcmk__action_runnable)) {
crm_trace("Ignoring action %s (%d): unrunnable",
action->uuid, action->id);
return false;
}
if (pcmk_is_set(action->flags, pcmk__action_optional)
&& !pcmk_is_set(action->flags, pcmk__action_always_in_graph)) {
crm_trace("Ignoring action %s (%d): optional",
action->uuid, action->id);
return false;
}
/* Actions for unmanaged resources should be excluded from the graph,
* with the exception of monitors and cancellation of recurring monitors.
*/
if ((action->rsc != NULL)
&& !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)
&& !pcmk__str_eq(action->task, PCMK_ACTION_MONITOR, pcmk__str_none)) {
const char *interval_ms_s;
/* A cancellation of a recurring monitor will get here because the task
* is cancel rather than monitor, but the interval can still be used to
* recognize it. The interval has been normalized to milliseconds by
* this point, so a string comparison is sufficient.
*/
interval_ms_s = g_hash_table_lookup(action->meta, PCMK_META_INTERVAL);
if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) {
crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)",
action->uuid, action->id, action->rsc->id);
return false;
}
}
/* Always add pseudo-actions, fence actions, and shutdown actions (already
* determined to be required and runnable by this point)
*/
if (pcmk_is_set(action->flags, pcmk__action_pseudo)
|| pcmk__strcase_any_of(action->task, PCMK_ACTION_STONITH,
PCMK_ACTION_DO_SHUTDOWN, NULL)) {
return true;
}
if (action->node == NULL) {
pcmk__sched_err(action->scheduler,
"Skipping action %s (%d) "
"because it was not assigned to a node (bug?)",
action->uuid, action->id);
pcmk__log_action("Unassigned", action, false);
return false;
}
if (pcmk_is_set(action->flags, pcmk__action_on_dc)) {
crm_trace("Action %s (%d) should be dumped: "
"can run on DC instead of %s",
action->uuid, action->id, pcmk__node_name(action->node));
} else if (pcmk__is_guest_or_bundle_node(action->node)
&& !pcmk_is_set(action->node->priv->flags,
pcmk__node_remote_reset)) {
crm_trace("Action %s (%d) should be dumped: "
"assuming will be runnable on guest %s",
action->uuid, action->id, pcmk__node_name(action->node));
} else if (!action->node->details->online) {
pcmk__sched_err(action->scheduler,
"Skipping action %s (%d) "
"because it was scheduled for offline node (bug?)",
action->uuid, action->id);
pcmk__log_action("Offline node", action, false);
return false;
} else if (action->node->details->unclean) {
pcmk__sched_err(action->scheduler,
"Skipping action %s (%d) "
"because it was scheduled for unclean node (bug?)",
action->uuid, action->id);
pcmk__log_action("Unclean node", action, false);
return false;
}
return true;
}
/*!
* \internal
* \brief Check whether an ordering's flags can change an action
*
* \param[in] ordering Ordering to check
*
* \return true if ordering has flags that can change an action, false otherwise
*/
static bool
ordering_can_change_actions(const pcmk__related_action_t *ordering)
{
return pcmk_any_flags_set(ordering->flags,
~(pcmk__ar_then_implies_first_graphed
|pcmk__ar_first_implies_then_graphed
|pcmk__ar_ordered));
}
/*!
* \internal
* \brief Check whether an action input should be in the transition graph
*
* \param[in] action Action to check
* \param[in,out] input Action input to check
*
* \return true if input should be in graph, false otherwise
* \note This function may not only check an input, but disable it under certian
* circumstances (load or anti-colocation orderings that are not needed).
*/
static bool
should_add_input_to_graph(const pcmk_action_t *action,
pcmk__related_action_t *input)
{
if (input->graphed) {
return true;
}
if (input->flags == pcmk__ar_none) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"ordering disabled",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if (!pcmk_is_set(input->action->flags, pcmk__action_runnable)
&& !ordering_can_change_actions(input)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"optional and input unrunnable",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if (!pcmk_is_set(input->action->flags, pcmk__action_runnable)
&& pcmk_is_set(input->flags, pcmk__ar_min_runnable)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"minimum number of instances required but input unrunnable",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if (pcmk_is_set(input->flags, pcmk__ar_unmigratable_then_blocks)
&& !pcmk_is_set(input->action->flags, pcmk__action_runnable)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"input blocked if 'then' unmigratable",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if (pcmk_is_set(input->flags, pcmk__ar_if_first_unmigratable)
&& pcmk_is_set(input->action->flags, pcmk__action_migratable)) {
crm_trace("Ignoring %s (%d) input %s (%d): ordering applies "
"only if input is unmigratable, but it is migratable",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if ((input->flags == pcmk__ar_ordered)
&& pcmk_is_set(input->action->flags, pcmk__action_migratable)
&& pcmk__ends_with(input->action->uuid, "_stop_0")) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"optional but stop in migration",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
} else if (input->flags == pcmk__ar_if_on_same_node_or_target) {
pcmk_node_t *input_node = input->action->node;
if ((action->rsc != NULL)
&& pcmk__str_eq(action->task, PCMK_ACTION_MIGRATE_TO,
pcmk__str_none)) {
pcmk_node_t *assigned = action->rsc->priv->assigned_node;
/* For load_stopped -> migrate_to orderings, we care about where
* the resource has been assigned, not where migrate_to will be
* executed.
*/
if (!pcmk__same_node(input_node, assigned)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"migration target %s is not same as input node %s",
action->uuid, action->id,
input->action->uuid, input->action->id,
(assigned? assigned->priv->name : "<none>"),
(input_node? input_node->priv->name : "<none>"));
input->flags = pcmk__ar_none;
return false;
}
} else if (!pcmk__same_node(input_node, action->node)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"not on same node (%s vs %s)",
action->uuid, action->id,
input->action->uuid, input->action->id,
(action->node? action->node->priv->name : "<none>"),
(input_node? input_node->priv->name : "<none>"));
input->flags = pcmk__ar_none;
return false;
} else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"ordering optional",
action->uuid, action->id,
input->action->uuid, input->action->id);
input->flags = pcmk__ar_none;
return false;
}
} else if (input->flags == pcmk__ar_if_required_on_same_node) {
if (input->action->node && action->node
&& !pcmk__same_node(input->action->node, action->node)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"not on same node (%s vs %s)",
action->uuid, action->id,
input->action->uuid, input->action->id,
pcmk__node_name(action->node),
pcmk__node_name(input->action->node));
input->flags = pcmk__ar_none;
return false;
} else if (pcmk_is_set(input->action->flags, pcmk__action_optional)) {
crm_trace("Ignoring %s (%d) input %s (%d): optional",
action->uuid, action->id,
input->action->uuid, input->action->id);
input->flags = pcmk__ar_none;
return false;
}
} else if (input->action->rsc
&& input->action->rsc != action->rsc
&& pcmk_is_set(input->action->rsc->flags, pcmk__rsc_failed)
&& !pcmk_is_set(input->action->rsc->flags, pcmk__rsc_managed)
&& pcmk__ends_with(input->action->uuid, "_stop_0")
&& pcmk__is_clone(action->rsc)) {
crm_warn("Ignoring requirement that %s complete before %s:"
" unmanaged failed resources cannot prevent clone shutdown",
input->action->uuid, action->uuid);
return false;
} else if (pcmk_is_set(input->action->flags, pcmk__action_optional)
&& !pcmk_any_flags_set(input->action->flags,
pcmk__action_always_in_graph
|pcmk__action_added_to_graph)
&& !should_add_action_to_graph(input->action)) {
crm_trace("Ignoring %s (%d) input %s (%d): "
"input optional",
action->uuid, action->id,
input->action->uuid, input->action->id);
return false;
}
crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s %#.6x",
action->uuid, action->id, action_type_str(input->action->flags),
input->action->uuid, input->action->id,
action_node_str(input->action),
action_runnable_str(input->action->flags),
action_optional_str(input->action->flags), input->flags);
return true;
}
/*!
* \internal
* \brief Check whether an ordering creates an ordering loop
*
* \param[in] init_action "First" action in ordering
* \param[in] action Callers should always set this the same as
* \p init_action (this function may use a different
* value for recursive calls)
* \param[in,out] input Action wrapper for "then" action in ordering
*
* \return true if the ordering creates a loop, otherwise false
*/
bool
pcmk__graph_has_loop(const pcmk_action_t *init_action,
const pcmk_action_t *action, pcmk__related_action_t *input)
{
bool has_loop = false;
if (pcmk_is_set(input->action->flags, pcmk__action_detect_loop)) {
crm_trace("Breaking tracking loop: %s@%s -> %s@%s (%#.6x)",
input->action->uuid,
input->action->node? input->action->node->priv->name : "",
action->uuid,
action->node? action->node->priv->name : "",
input->flags);
return false;
}
// Don't need to check inputs that won't be used
if (!should_add_input_to_graph(action, input)) {
return false;
}
if (input->action == init_action) {
crm_debug("Input loop found in %s@%s ->...-> %s@%s",
action->uuid,
action->node? action->node->priv->name : "",
init_action->uuid,
init_action->node? init_action->node->priv->name : "");
return true;
}
pcmk__set_action_flags(input->action, pcmk__action_detect_loop);
crm_trace("Checking inputs of action %s@%s input %s@%s (%#.6x)"
"for graph loop with %s@%s ",
action->uuid,
action->node? action->node->priv->name : "",
input->action->uuid,
input->action->node? input->action->node->priv->name : "",
input->flags,
init_action->uuid,
init_action->node? init_action->node->priv->name : "");
// Recursively check input itself for loops
for (GList *iter = input->action->actions_before;
iter != NULL; iter = iter->next) {
if (pcmk__graph_has_loop(init_action, input->action,
(pcmk__related_action_t *) iter->data)) {
// Recursive call already logged a debug message
has_loop = true;
break;
}
}
pcmk__clear_action_flags(input->action, pcmk__action_detect_loop);
if (!has_loop) {
crm_trace("No input loop found in %s@%s -> %s@%s (%#.6x)",
input->action->uuid,
input->action->node? input->action->node->priv->name : "",
action->uuid,
action->node? action->node->priv->name : "",
input->flags);
}
return has_loop;
}
/*!
* \internal
* \brief Create a synapse XML element for a transition graph
*
* \param[in] action Action that synapse is for
* \param[in,out] scheduler Scheduler data containing graph
*
* \return Newly added XML element for new graph synapse
*/
static xmlNode *
create_graph_synapse(const pcmk_action_t *action, pcmk_scheduler_t *scheduler)
{
int synapse_priority = 0;
xmlNode *syn = pcmk__xe_create(scheduler->priv->graph, PCMK__XE_SYNAPSE);
crm_xml_add_int(syn, PCMK_XA_ID, scheduler->priv->synapse_count++);
if (action->rsc != NULL) {
synapse_priority = action->rsc->priv->priority;
}
if (action->priority > synapse_priority) {
synapse_priority = action->priority;
}
if (synapse_priority > 0) {
crm_xml_add_int(syn, PCMK__XA_PRIORITY, synapse_priority);
}
return syn;
}
/*!
* \internal
* \brief Add an action to the transition graph XML if appropriate
*
* \param[in,out] data Action to possibly add
* \param[in,out] user_data Scheduler data
*
* \note This will de-duplicate the action inputs, meaning that the
* pcmk__related_action_t:type flags can no longer be relied on to retain
* their original settings. That means this MUST be called after
* pcmk__apply_orderings() is complete, and nothing after this should rely
* on those type flags. (For example, some code looks for type equal to
* some flag rather than whether the flag is set, and some code looks for
* particular combinations of flags -- such code must be done before
* pcmk__create_graph().)
*/
static void
add_action_to_graph(gpointer data, gpointer user_data)
{
pcmk_action_t *action = (pcmk_action_t *) data;
pcmk_scheduler_t *scheduler = (pcmk_scheduler_t *) user_data;
xmlNode *syn = NULL;
xmlNode *set = NULL;
xmlNode *in = NULL;
/* If we haven't already, de-duplicate inputs (even if we won't be adding
* the action to the graph, so that crm_simulate's dot graphs don't have
* duplicates).
*/
if (!pcmk_is_set(action->flags, pcmk__action_inputs_deduplicated)) {
pcmk__deduplicate_action_inputs(action);
pcmk__set_action_flags(action, pcmk__action_inputs_deduplicated);
}
if (pcmk_is_set(action->flags, pcmk__action_added_to_graph)
|| !should_add_action_to_graph(action)) {
return; // Already added, or shouldn't be
}
pcmk__set_action_flags(action, pcmk__action_added_to_graph);
crm_trace("Adding action %d (%s%s%s) to graph",
action->id, action->uuid,
((action->node == NULL)? "" : " on "),
((action->node == NULL)? "" : action->node->priv->name));
syn = create_graph_synapse(action, scheduler);
set = pcmk__xe_create(syn, PCMK__XE_ACTION_SET);
in = pcmk__xe_create(syn, PCMK__XE_INPUTS);
create_graph_action(set, action, false, scheduler);
for (GList *lpc = action->actions_before; lpc != NULL; lpc = lpc->next) {
pcmk__related_action_t *input = lpc->data;
if (should_add_input_to_graph(action, input)) {
xmlNode *input_xml = pcmk__xe_create(in, PCMK__XE_TRIGGER);
input->graphed = true;
create_graph_action(input_xml, input->action, true, scheduler);
}
}
}
static int transition_id = 0;
/*!
* \internal
* \brief Log a message after calculating a transition
*
* \param[in] scheduler Scheduler data
* \param[in] filename Where transition input is stored
*/
void
pcmk__log_transition_summary(const pcmk_scheduler_t *scheduler,
const char *filename)
{
if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_error)
|| pcmk__config_has_error) {
crm_err("Calculated transition %d (with errors)%s%s",
transition_id,
(filename == NULL)? "" : ", saving inputs in ",
(filename == NULL)? "" : filename);
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_processing_warning)
|| pcmk__config_has_warning) {
crm_warn("Calculated transition %d (with warnings)%s%s",
transition_id,
(filename == NULL)? "" : ", saving inputs in ",
(filename == NULL)? "" : filename);
} else {
crm_notice("Calculated transition %d%s%s",
transition_id,
(filename == NULL)? "" : ", saving inputs in ",
(filename == NULL)? "" : filename);
}
if (pcmk__config_has_error) {
crm_notice("Configuration errors found during scheduler processing,"
" please run \"crm_verify -L\" to identify issues");
}
}
/*!
* \internal
* \brief Add a resource's actions to the transition graph
*
* \param[in,out] rsc Resource whose actions should be added
*/
void
pcmk__add_rsc_actions_to_graph(pcmk_resource_t *rsc)
{
GList *iter = NULL;
pcmk__assert(rsc != NULL);
pcmk__rsc_trace(rsc, "Adding actions for %s to graph", rsc->id);
// First add the resource's own actions
g_list_foreach(rsc->priv->actions, add_action_to_graph,
rsc->priv->scheduler);
// Then recursively add its children's actions (appropriate to variant)
for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
pcmk_resource_t *child_rsc = (pcmk_resource_t *) iter->data;
child_rsc->priv->cmds->add_actions_to_graph(child_rsc);
}
}
/*!
* \internal
* \brief Create a transition graph with all cluster actions needed
*
* \param[in,out] scheduler Scheduler data
*/
void
pcmk__create_graph(pcmk_scheduler_t *scheduler)
{
GList *iter = NULL;
const char *value = NULL;
long long limit = 0LL;
GHashTable *config_hash = scheduler->priv->options;
int rc = pcmk_rc_ok;
transition_id++;
crm_trace("Creating transition graph %d", transition_id);
scheduler->priv->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
value = pcmk__cluster_option(config_hash, PCMK_OPT_CLUSTER_DELAY);
crm_xml_add(scheduler->priv->graph, PCMK_OPT_CLUSTER_DELAY, value);
value = pcmk__cluster_option(config_hash, PCMK_OPT_STONITH_TIMEOUT);
crm_xml_add(scheduler->priv->graph, PCMK_OPT_STONITH_TIMEOUT, value);
- crm_xml_add(scheduler->priv->graph, "failed-stop-offset", "INFINITY");
+ crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_STOP_OFFSET,
+ PCMK_VALUE_INFINITY);
if (pcmk_is_set(scheduler->flags, pcmk__sched_start_failure_fatal)) {
- crm_xml_add(scheduler->priv->graph, "failed-start-offset", "INFINITY");
+ crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_START_OFFSET,
+ PCMK_VALUE_INFINITY);
} else {
- crm_xml_add(scheduler->priv->graph, "failed-start-offset", "1");
+ crm_xml_add(scheduler->priv->graph, PCMK__XA_FAILED_START_OFFSET, "1");
}
value = pcmk__cluster_option(config_hash, PCMK_OPT_BATCH_LIMIT);
crm_xml_add(scheduler->priv->graph, PCMK_OPT_BATCH_LIMIT, value);
crm_xml_add_int(scheduler->priv->graph, "transition_id", transition_id);
value = pcmk__cluster_option(config_hash, PCMK_OPT_MIGRATION_LIMIT);
rc = pcmk__scan_ll(value, &limit, 0LL);
if (rc != pcmk_rc_ok) {
crm_warn("Ignoring invalid value '%s' for " PCMK_OPT_MIGRATION_LIMIT
": %s", value, pcmk_rc_str(rc));
} else if (limit > 0) {
crm_xml_add(scheduler->priv->graph, PCMK_OPT_MIGRATION_LIMIT, value);
}
if (scheduler->priv->recheck_by > 0) {
char *recheck_epoch = NULL;
recheck_epoch = crm_strdup_printf("%llu", (unsigned long long)
scheduler->priv->recheck_by);
crm_xml_add(scheduler->priv->graph, "recheck-by", recheck_epoch);
free(recheck_epoch);
}
/* The following code will de-duplicate action inputs, so nothing past this
* should rely on the action input type flags retaining their original
* values.
*/
// Add resource actions to graph
for (iter = scheduler->priv->resources; iter != NULL; iter = iter->next) {
pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
pcmk__rsc_trace(rsc, "Processing actions for %s", rsc->id);
rsc->priv->cmds->add_actions_to_graph(rsc);
}
// Add pseudo-action for list of nodes with maintenance state update
add_maintenance_update(scheduler);
// Add non-resource (node) actions
for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = (pcmk_action_t *) iter->data;
if ((action->rsc != NULL)
&& (action->node != NULL)
&& action->node->details->shutdown
&& !pcmk_is_set(action->rsc->flags, pcmk__rsc_maintenance)
&& !pcmk_any_flags_set(action->flags,
pcmk__action_optional|pcmk__action_runnable)
&& pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_none)) {
/* Eventually we should just ignore the 'fence' case, but for now
* it's the best way to detect (in CTS) when CIB resource updates
* are being lost.
*/
if (pcmk_is_set(scheduler->flags, pcmk__sched_quorate)
|| (scheduler->no_quorum_policy == pcmk_no_quorum_ignore)) {
const bool managed = pcmk_is_set(action->rsc->flags,
pcmk__rsc_managed);
const bool failed = pcmk_is_set(action->rsc->flags,
pcmk__rsc_failed);
crm_crit("Cannot %s %s because of %s:%s%s (%s)",
action->node->details->unclean? "fence" : "shut down",
pcmk__node_name(action->node), action->rsc->id,
(managed? " blocked" : " unmanaged"),
(failed? " failed" : ""), action->uuid);
}
}
add_action_to_graph((gpointer) action, (gpointer) scheduler);
}
crm_log_xml_trace(scheduler->priv->graph, "graph");
}
diff --git a/lib/pacemaker/pcmk_injections.c b/lib/pacemaker/pcmk_injections.c
index d249ae7bb0..d728cfea1e 100644
--- a/lib/pacemaker/pcmk_injections.c
+++ b/lib/pacemaker/pcmk_injections.c
@@ -1,777 +1,796 @@
/*
* Copyright 2009-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/types.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/cib/internal.h>
#include <crm/common/util.h>
#include <crm/common/iso8601.h>
#include <crm/common/xml_internal.h>
#include <crm/lrmd_events.h> // lrmd_event_data_t, etc.
#include <crm/lrmd_internal.h>
#include <crm/pengine/status.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
bool pcmk__simulate_node_config = false;
#define XPATH_NODE_CONFIG "//" PCMK_XE_NODE "[@" PCMK_XA_UNAME "='%s']"
#define XPATH_NODE_STATE "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_UNAME "='%s']"
#define XPATH_NODE_STATE_BY_ID "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "='%s']"
#define XPATH_RSC_HISTORY XPATH_NODE_STATE \
"//" PCMK__XE_LRM_RESOURCE "[@" PCMK_XA_ID "='%s']"
/*!
* \internal
* \brief Inject a fictitious transient node attribute into scheduler input
*
* \param[in,out] out Output object for displaying error messages
* \param[in,out] cib_node \c PCMK__XE_NODE_STATE XML to inject attribute into
* \param[in] name Transient node attribute name to inject
* \param[in] value Transient node attribute value to inject
*/
static void
inject_transient_attr(pcmk__output_t *out, xmlNode *cib_node,
const char *name, const char *value)
{
xmlNode *attrs = NULL;
xmlNode *instance_attrs = NULL;
const char *node_uuid = pcmk__xe_id(cib_node);
out->message(out, "inject-attr", name, value, cib_node);
attrs = pcmk__xe_first_child(cib_node, PCMK__XE_TRANSIENT_ATTRIBUTES, NULL,
NULL);
if (attrs == NULL) {
attrs = pcmk__xe_create(cib_node, PCMK__XE_TRANSIENT_ATTRIBUTES);
crm_xml_add(attrs, PCMK_XA_ID, node_uuid);
}
instance_attrs = pcmk__xe_first_child(attrs, PCMK_XE_INSTANCE_ATTRIBUTES,
NULL, NULL);
if (instance_attrs == NULL) {
instance_attrs = pcmk__xe_create(attrs, PCMK_XE_INSTANCE_ATTRIBUTES);
crm_xml_add(instance_attrs, PCMK_XA_ID, node_uuid);
}
crm_create_nvpair_xml(instance_attrs, NULL, name, value);
}
/*!
* \internal
* \brief Inject a fictitious fail count into a scheduler input
*
* \param[in,out] out Output object for displaying error messages
* \param[in,out] cib_conn CIB connection
* \param[in,out] cib_node Node state XML to inject into
* \param[in] resource ID of resource for fail count to inject
* \param[in] task Action name for fail count to inject
* \param[in] interval_ms Action interval (in milliseconds) for fail count
* \param[in] exit_status Action result for fail count to inject (if
* \c PCMK_OCF_OK, or \c PCMK_OCF_NOT_RUNNING when
* \p interval_ms is 0, inject nothing)
+ * \param[in] infinity If true, set fail count to "INFINITY", otherwise
+ * increase it by 1
*/
void
pcmk__inject_failcount(pcmk__output_t *out, cib_t *cib_conn, xmlNode *cib_node,
const char *resource, const char *task,
- guint interval_ms, int exit_status)
+ guint interval_ms, int exit_status, bool infinity)
{
char *name = NULL;
char *value = NULL;
int failcount = 0;
xmlNode *output = NULL;
CRM_CHECK((out != NULL) && (cib_conn != NULL) && (cib_node != NULL)
&& (resource != NULL) && (task != NULL), return);
if ((exit_status == PCMK_OCF_OK)
|| ((exit_status == PCMK_OCF_NOT_RUNNING) && (interval_ms == 0))) {
return;
}
// Get current failcount and increment it
name = pcmk__failcount_name(resource, task, interval_ms);
if (cib__get_node_attrs(out, cib_conn, PCMK_XE_STATUS,
pcmk__xe_id(cib_node), NULL, NULL, NULL, name,
NULL, &output) == pcmk_rc_ok) {
if (crm_element_value_int(output, PCMK_XA_VALUE, &failcount) != 0) {
failcount = 0;
}
}
- value = pcmk__itoa(failcount + 1);
+
+ if (infinity) {
+ value = pcmk__str_copy(PCMK_VALUE_INFINITY);
+
+ } else {
+ value = pcmk__itoa(failcount + 1);
+ }
+
inject_transient_attr(out, cib_node, name, value);
free(name);
free(value);
pcmk__xml_free(output);
name = pcmk__lastfailure_name(resource, task, interval_ms);
value = pcmk__ttoa(time(NULL));
inject_transient_attr(out, cib_node, name, value);
free(name);
free(value);
}
/*!
* \internal
* \brief Create a CIB configuration entry for a fictitious node
*
* \param[in,out] cib_conn CIB object to use
* \param[in] node Node name to use
*/
static void
create_node_entry(cib_t *cib_conn, const char *node)
{
int rc = pcmk_ok;
char *xpath = crm_strdup_printf(XPATH_NODE_CONFIG, node);
rc = cib_conn->cmds->query(cib_conn, xpath, NULL, cib_xpath|cib_sync_call);
if (rc == -ENXIO) { // Only add if not already existing
xmlNode *cib_object = pcmk__xe_create(NULL, PCMK_XE_NODE);
crm_xml_add(cib_object, PCMK_XA_ID, node); // Use node name as ID
crm_xml_add(cib_object, PCMK_XA_UNAME, node);
cib_conn->cmds->create(cib_conn, PCMK_XE_NODES, cib_object,
cib_sync_call);
/* Not bothering with subsequent query to see if it exists,
we'll bomb out later in the call to query_node_uuid()... */
pcmk__xml_free(cib_object);
}
free(xpath);
}
/*!
* \internal
* \brief Synthesize a fake executor event for an action
*
* \param[in] cib_resource XML for any existing resource action history
* \param[in] task Name of action to synthesize
* \param[in] interval_ms Interval of action to synthesize
* \param[in] outcome Result of action to synthesize
*
* \return Newly allocated executor event
* \note It is the caller's responsibility to free the result with
* lrmd_free_event().
*/
static lrmd_event_data_t *
create_op(const xmlNode *cib_resource, const char *task, guint interval_ms,
int outcome)
{
lrmd_event_data_t *op = NULL;
xmlNode *xop = NULL;
op = lrmd_new_event(pcmk__xe_id(cib_resource), task, interval_ms);
lrmd__set_result(op, outcome, PCMK_EXEC_DONE, "Simulated action result");
op->params = NULL; // Not needed for simulation purposes
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
// Use a call ID higher than any existing history entries
op->call_id = 0;
for (xop = pcmk__xe_first_child(cib_resource, NULL, NULL, NULL);
xop != NULL; xop = pcmk__xe_next(xop, NULL)) {
int tmp = 0;
crm_element_value_int(xop, PCMK__XA_CALL_ID, &tmp);
if (tmp > op->call_id) {
op->call_id = tmp;
}
}
op->call_id++;
return op;
}
/*!
* \internal
* \brief Inject a fictitious resource history entry into a scheduler input
*
* \param[in,out] cib_resource Resource history XML to inject entry into
* \param[in,out] op Action result to inject
* \param[in] node Name of node where the action occurred
* \param[in] target_rc Expected result for action to inject
*
* \return XML of injected resource history entry
*/
xmlNode *
pcmk__inject_action_result(xmlNode *cib_resource, lrmd_event_data_t *op,
const char *node, int target_rc)
{
return pcmk__create_history_xml(cib_resource, op, CRM_FEATURE_SET,
target_rc, node, crm_system_name);
}
/*!
* \internal
* \brief Inject a fictitious node into a scheduler input
*
* \param[in,out] cib_conn Scheduler input CIB to inject node into
* \param[in] node Name of node to inject
* \param[in] uuid UUID of node to inject
*
* \return XML of \c PCMK__XE_NODE_STATE entry for new node
* \note If the global pcmk__simulate_node_config has been set to true, a
* node entry in the configuration section will be added, as well as a
* node state entry in the status section.
*/
xmlNode *
pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid)
{
int rc = pcmk_ok;
xmlNode *cib_object = NULL;
char *xpath = crm_strdup_printf(XPATH_NODE_STATE, node);
bool duplicate = false;
char *found_uuid = NULL;
if (pcmk__simulate_node_config) {
create_node_entry(cib_conn, node);
}
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath|cib_sync_call);
if ((cib_object != NULL) && (pcmk__xe_id(cib_object) == NULL)) {
crm_err("Detected multiple " PCMK__XE_NODE_STATE " entries for "
"xpath=%s, bailing",
xpath);
duplicate = true;
goto done;
}
if (rc == -ENXIO) {
if (uuid == NULL) {
query_node_uuid(cib_conn, node, &found_uuid, NULL);
} else {
found_uuid = strdup(uuid);
}
if (found_uuid) {
char *xpath_by_uuid = crm_strdup_printf(XPATH_NODE_STATE_BY_ID,
found_uuid);
/* It's possible that a PCMK__XE_NODE_STATE entry doesn't have a
* PCMK_XA_UNAME yet
*/
rc = cib_conn->cmds->query(cib_conn, xpath_by_uuid, &cib_object,
cib_xpath|cib_sync_call);
if ((cib_object != NULL) && (pcmk__xe_id(cib_object) == NULL)) {
crm_err("Can't inject node state for %s because multiple "
"state entries found for ID %s", node, found_uuid);
duplicate = true;
free(xpath_by_uuid);
goto done;
} else if (cib_object != NULL) {
crm_xml_add(cib_object, PCMK_XA_UNAME, node);
rc = cib_conn->cmds->modify(cib_conn, PCMK_XE_STATUS,
cib_object, cib_sync_call);
}
free(xpath_by_uuid);
}
}
if (rc == -ENXIO) {
cib_object = pcmk__xe_create(NULL, PCMK__XE_NODE_STATE);
crm_xml_add(cib_object, PCMK_XA_ID, found_uuid);
crm_xml_add(cib_object, PCMK_XA_UNAME, node);
cib_conn->cmds->create(cib_conn, PCMK_XE_STATUS, cib_object,
cib_sync_call);
pcmk__xml_free(cib_object);
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath|cib_sync_call);
crm_trace("Injecting node state for %s (rc=%d)", node, rc);
}
done:
free(found_uuid);
free(xpath);
if (duplicate) {
crm_log_xml_warn(cib_object, "Duplicates");
crm_exit(CRM_EX_SOFTWARE);
return NULL; // not reached, but makes static analysis happy
}
pcmk__assert(rc == pcmk_ok);
return cib_object;
}
/*!
* \internal
* \brief Inject a fictitious node state change into a scheduler input
*
* \param[in,out] cib_conn Scheduler input CIB to inject into
* \param[in] node Name of node to inject change for
* \param[in] up If true, change state to online, otherwise offline
*
* \return XML of changed (or added) node state entry
*/
xmlNode *
pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up)
{
xmlNode *cib_node = pcmk__inject_node(cib_conn, node, NULL);
if (up) {
pcmk__xe_set_props(cib_node,
PCMK__XA_IN_CCM, PCMK_VALUE_TRUE,
PCMK_XA_CRMD, PCMK_VALUE_ONLINE,
PCMK__XA_JOIN, CRMD_JOINSTATE_MEMBER,
PCMK_XA_EXPECTED, CRMD_JOINSTATE_MEMBER,
NULL);
} else {
pcmk__xe_set_props(cib_node,
PCMK__XA_IN_CCM, PCMK_VALUE_FALSE,
PCMK_XA_CRMD, PCMK_VALUE_OFFLINE,
PCMK__XA_JOIN, CRMD_JOINSTATE_DOWN,
PCMK_XA_EXPECTED, CRMD_JOINSTATE_DOWN,
NULL);
}
crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, crm_system_name);
return cib_node;
}
/*!
* \internal
* \brief Check whether a node has history for a given resource
*
* \param[in,out] cib_node Node state XML to check
* \param[in] resource Resource name to check for
*
* \return Resource's \c PCMK__XE_LRM_RESOURCE XML entry beneath \p cib_node if
* found, otherwise \c NULL
*/
static xmlNode *
find_resource_xml(xmlNode *cib_node, const char *resource)
{
const char *node = crm_element_value(cib_node, PCMK_XA_UNAME);
char *xpath = crm_strdup_printf(XPATH_RSC_HISTORY, node, resource);
xmlNode *match = get_xpath_object(xpath, cib_node, LOG_TRACE);
free(xpath);
return match;
}
/*!
* \internal
* \brief Inject a resource history element into a scheduler input
*
* \param[in,out] out Output object for displaying error messages
* \param[in,out] cib_node Node state XML to inject resource history entry into
* \param[in] resource ID (in configuration) of resource to inject
* \param[in] lrm_name ID as used in history (could be clone instance)
* \param[in] rclass Resource agent class of resource to inject
* \param[in] rtype Resource agent type of resource to inject
* \param[in] rprovider Resource agent provider of resource to inject
*
* \return XML of injected resource history element
* \note If a history element already exists under either \p resource or
* \p lrm_name, this will return it rather than injecting a new one.
*/
xmlNode *
pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node,
const char *resource, const char *lrm_name,
const char *rclass, const char *rtype,
const char *rprovider)
{
xmlNode *lrm = NULL;
xmlNode *container = NULL;
xmlNode *cib_resource = NULL;
cib_resource = find_resource_xml(cib_node, resource);
if (cib_resource != NULL) {
/* If an existing LRM history entry uses the resource name,
* continue using it, even if lrm_name is different.
*/
return cib_resource;
}
// Check for history entry under preferred name
if (strcmp(resource, lrm_name) != 0) {
cib_resource = find_resource_xml(cib_node, lrm_name);
if (cib_resource != NULL) {
return cib_resource;
}
}
if ((rclass == NULL) || (rtype == NULL)) {
// @TODO query configuration for class, provider, type
out->err(out,
"Resource %s not found in the status section of %s "
"(supply class and type to continue)",
resource, pcmk__xe_id(cib_node));
return NULL;
} else if (!pcmk__strcase_any_of(rclass,
PCMK_RESOURCE_CLASS_OCF,
PCMK_RESOURCE_CLASS_STONITH,
PCMK_RESOURCE_CLASS_SERVICE,
PCMK_RESOURCE_CLASS_SYSTEMD,
PCMK_RESOURCE_CLASS_LSB, NULL)) {
out->err(out, "Invalid class for %s: %s", resource, rclass);
return NULL;
} else if (pcmk_is_set(pcmk_get_ra_caps(rclass), pcmk_ra_cap_provider)
&& (rprovider == NULL)) {
// @TODO query configuration for provider
out->err(out, "Please specify the provider for resource %s", resource);
return NULL;
}
crm_info("Injecting new resource %s into node state '%s'",
lrm_name, pcmk__xe_id(cib_node));
lrm = pcmk__xe_first_child(cib_node, PCMK__XE_LRM, NULL, NULL);
if (lrm == NULL) {
const char *node_uuid = pcmk__xe_id(cib_node);
lrm = pcmk__xe_create(cib_node, PCMK__XE_LRM);
crm_xml_add(lrm, PCMK_XA_ID, node_uuid);
}
container = pcmk__xe_first_child(lrm, PCMK__XE_LRM_RESOURCES, NULL, NULL);
if (container == NULL) {
container = pcmk__xe_create(lrm, PCMK__XE_LRM_RESOURCES);
}
cib_resource = pcmk__xe_create(container, PCMK__XE_LRM_RESOURCE);
// If we're creating a new entry, use the preferred name
crm_xml_add(cib_resource, PCMK_XA_ID, lrm_name);
crm_xml_add(cib_resource, PCMK_XA_CLASS, rclass);
crm_xml_add(cib_resource, PCMK_XA_PROVIDER, rprovider);
crm_xml_add(cib_resource, PCMK_XA_TYPE, rtype);
return cib_resource;
}
/*!
* \internal
* \brief Inject a ticket attribute into ticket state
*
* \param[in,out] out Output object for displaying error messages
* \param[in] ticket_id Ticket whose state should be changed
* \param[in] attr_name Ticket attribute name to inject
* \param[in] attr_value Boolean value of ticket attribute to inject
* \param[in,out] cib CIB object to use
*
* \return Standard Pacemaker return code
*/
static int
set_ticket_state_attr(pcmk__output_t *out, const char *ticket_id,
const char *attr_name, bool attr_value, cib_t *cib)
{
int rc = pcmk_rc_ok;
xmlNode *xml_top = NULL;
xmlNode *ticket_state_xml = NULL;
// Check for an existing ticket state entry
rc = pcmk__get_ticket_state(cib, ticket_id, &ticket_state_xml);
if (rc == pcmk_rc_duplicate_id) {
out->err(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket_id=%s",
ticket_id);
rc = pcmk_rc_ok;
}
if (rc == pcmk_rc_ok) { // Ticket state found, use it
crm_debug("Injecting attribute into existing ticket state %s",
ticket_id);
xml_top = ticket_state_xml;
} else if (rc == ENXIO) { // No ticket state, create it
xmlNode *xml_obj = NULL;
xml_top = pcmk__xe_create(NULL, PCMK_XE_STATUS);
xml_obj = pcmk__xe_create(xml_top, PCMK_XE_TICKETS);
ticket_state_xml = pcmk__xe_create(xml_obj, PCMK__XE_TICKET_STATE);
crm_xml_add(ticket_state_xml, PCMK_XA_ID, ticket_id);
} else { // Error
return rc;
}
// Add the attribute to the ticket state
pcmk__xe_set_bool_attr(ticket_state_xml, attr_name, attr_value);
crm_log_xml_debug(xml_top, "Update");
// Commit the change to the CIB
rc = cib->cmds->modify(cib, PCMK_XE_STATUS, xml_top, cib_sync_call);
rc = pcmk_legacy2rc(rc);
pcmk__xml_free(xml_top);
return rc;
}
/*!
* \internal
* \brief Inject a fictitious action into the cluster
*
* \param[in,out] out Output object for displaying error messages
* \param[in] spec Action specification to inject
* \param[in,out] cib CIB object for scheduler input
* \param[in] scheduler Scheduler data
*/
static void
inject_action(pcmk__output_t *out, const char *spec, cib_t *cib,
const pcmk_scheduler_t *scheduler)
{
int rc;
int outcome = PCMK_OCF_OK;
guint interval_ms = 0;
char *key = NULL;
char *node = NULL;
char *task = NULL;
char *resource = NULL;
const char *rtype = NULL;
const char *rclass = NULL;
const char *rprovider = NULL;
xmlNode *cib_op = NULL;
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
const pcmk_resource_t *rsc = NULL;
lrmd_event_data_t *op = NULL;
+ bool infinity = false;
out->message(out, "inject-spec", spec);
key = pcmk__assert_alloc(1, strlen(spec) + 1);
node = pcmk__assert_alloc(1, strlen(spec) + 1);
rc = sscanf(spec, "%[^@]@%[^=]=%d", key, node, &outcome);
if (rc != 3) {
out->err(out, "Invalid operation spec: %s. Only found %d fields",
spec, rc);
goto done;
}
parse_op_key(key, &resource, &task, &interval_ms);
rsc = pe_find_resource(scheduler->priv->resources, resource);
if (rsc == NULL) {
out->err(out, "Invalid resource name: %s", resource);
goto done;
}
rclass = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS);
rtype = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE);
rprovider = crm_element_value(rsc->priv->xml, PCMK_XA_PROVIDER);
cib_node = pcmk__inject_node(cib, node, NULL);
pcmk__assert(cib_node != NULL);
+ if (pcmk__str_eq(task, PCMK_ACTION_STOP, pcmk__str_none)) {
+ infinity = true;
+
+ } else if (pcmk__str_eq(task, PCMK_ACTION_START, pcmk__str_none)
+ && pcmk_is_set(scheduler->flags,
+ pcmk__sched_start_failure_fatal)) {
+ infinity = true;
+ }
+
pcmk__inject_failcount(out, cib, cib_node, resource, task, interval_ms,
- outcome);
+ outcome, infinity);
cib_resource = pcmk__inject_resource_history(out, cib_node,
resource, resource,
rclass, rtype, rprovider);
pcmk__assert(cib_resource != NULL);
op = create_op(cib_resource, task, interval_ms, outcome);
pcmk__assert(op != NULL);
cib_op = pcmk__inject_action_result(cib_resource, op, node, 0);
pcmk__assert(cib_op != NULL);
lrmd_free_event(op);
rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
pcmk__assert(rc == pcmk_ok);
done:
free(task);
free(node);
free(key);
}
/*!
* \internal
* \brief Inject fictitious scheduler inputs
*
* \param[in,out] scheduler Scheduler data
* \param[in,out] cib CIB object for scheduler input to modify
* \param[in] injections Injections to apply
*/
void
pcmk__inject_scheduler_input(pcmk_scheduler_t *scheduler, cib_t *cib,
const pcmk_injections_t *injections)
{
int rc = pcmk_ok;
const GList *iter = NULL;
xmlNode *cib_node = NULL;
pcmk__output_t *out = scheduler->priv->out;
out->message(out, "inject-modify-config", injections->quorum,
injections->watchdog);
if (injections->quorum != NULL) {
xmlNode *top = pcmk__xe_create(NULL, PCMK_XE_CIB);
/* crm_xml_add(top, PCMK_XA_DC_UUID, dc_uuid); */
crm_xml_add(top, PCMK_XA_HAVE_QUORUM, injections->quorum);
rc = cib->cmds->modify(cib, NULL, top, cib_sync_call);
pcmk__assert(rc == pcmk_ok);
}
if (injections->watchdog != NULL) {
rc = cib__update_node_attr(out, cib, cib_sync_call, PCMK_XE_CRM_CONFIG,
NULL, NULL, NULL, NULL,
PCMK_OPT_HAVE_WATCHDOG, injections->watchdog,
NULL, NULL);
pcmk__assert(rc == pcmk_rc_ok);
}
for (iter = injections->node_up; iter != NULL; iter = iter->next) {
const char *node = (const char *) iter->data;
out->message(out, "inject-modify-node", "Online", node);
cib_node = pcmk__inject_node_state_change(cib, node, true);
pcmk__assert(cib_node != NULL);
rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
pcmk__assert(rc == pcmk_ok);
pcmk__xml_free(cib_node);
}
for (iter = injections->node_down; iter != NULL; iter = iter->next) {
const char *node = (const char *) iter->data;
char *xpath = NULL;
out->message(out, "inject-modify-node", "Offline", node);
cib_node = pcmk__inject_node_state_change(cib, node, false);
pcmk__assert(cib_node != NULL);
rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
pcmk__assert(rc == pcmk_ok);
pcmk__xml_free(cib_node);
xpath = crm_strdup_printf("//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='%s']"
"/" PCMK__XE_LRM,
node);
cib->cmds->remove(cib, xpath, NULL, cib_xpath|cib_sync_call);
free(xpath);
xpath = crm_strdup_printf("//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='%s']"
"/" PCMK__XE_TRANSIENT_ATTRIBUTES,
node);
cib->cmds->remove(cib, xpath, NULL, cib_xpath|cib_sync_call);
free(xpath);
}
for (iter = injections->node_fail; iter != NULL; iter = iter->next) {
const char *node = (const char *) iter->data;
out->message(out, "inject-modify-node", "Failing", node);
cib_node = pcmk__inject_node_state_change(cib, node, true);
crm_xml_add(cib_node, PCMK__XA_IN_CCM, PCMK_VALUE_FALSE);
pcmk__assert(cib_node != NULL);
rc = cib->cmds->modify(cib, PCMK_XE_STATUS, cib_node, cib_sync_call);
pcmk__assert(rc == pcmk_ok);
pcmk__xml_free(cib_node);
}
for (iter = injections->ticket_grant; iter != NULL; iter = iter->next) {
const char *ticket_id = (const char *) iter->data;
out->message(out, "inject-modify-ticket", "Granting", ticket_id);
rc = set_ticket_state_attr(out, ticket_id, PCMK__XA_GRANTED, true, cib);
pcmk__assert(rc == pcmk_rc_ok);
}
for (iter = injections->ticket_revoke; iter != NULL; iter = iter->next) {
const char *ticket_id = (const char *) iter->data;
out->message(out, "inject-modify-ticket", "Revoking", ticket_id);
rc = set_ticket_state_attr(out, ticket_id, PCMK__XA_GRANTED, false,
cib);
pcmk__assert(rc == pcmk_rc_ok);
}
for (iter = injections->ticket_standby; iter != NULL; iter = iter->next) {
const char *ticket_id = (const char *) iter->data;
out->message(out, "inject-modify-ticket", "Standby", ticket_id);
rc = set_ticket_state_attr(out, ticket_id, PCMK_XA_STANDBY, true, cib);
pcmk__assert(rc == pcmk_rc_ok);
}
for (iter = injections->ticket_activate; iter != NULL; iter = iter->next) {
const char *ticket_id = (const char *) iter->data;
out->message(out, "inject-modify-ticket", "Activating", ticket_id);
rc = set_ticket_state_attr(out, ticket_id, PCMK_XA_STANDBY, false, cib);
pcmk__assert(rc == pcmk_rc_ok);
}
for (iter = injections->op_inject; iter != NULL; iter = iter->next) {
inject_action(out, (const char *) iter->data, cib, scheduler);
}
if (!out->is_quiet(out)) {
out->end_list(out);
}
}
void
pcmk_free_injections(pcmk_injections_t *injections)
{
if (injections == NULL) {
return;
}
g_list_free_full(injections->node_up, g_free);
g_list_free_full(injections->node_down, g_free);
g_list_free_full(injections->node_fail, g_free);
g_list_free_full(injections->op_fail, g_free);
g_list_free_full(injections->op_inject, g_free);
g_list_free_full(injections->ticket_grant, g_free);
g_list_free_full(injections->ticket_revoke, g_free);
g_list_free_full(injections->ticket_standby, g_free);
g_list_free_full(injections->ticket_activate, g_free);
free(injections->quorum);
free(injections->watchdog);
free(injections);
}
diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c
index 7b3080d57e..dadf52c1e3 100644
--- a/lib/pacemaker/pcmk_simulate.c
+++ b/lib/pacemaker/pcmk_simulate.c
@@ -1,1006 +1,1017 @@
/*
* Copyright 2021-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/cib/internal.h>
#include <crm/common/output.h>
#include <crm/common/results.h>
#include <crm/common/scheduler.h>
#include <pacemaker-internal.h>
#include <pacemaker.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "libpacemaker_private.h"
static pcmk__output_t *out = NULL;
static cib_t *fake_cib = NULL;
static GList *fake_resource_list = NULL;
static const GList *fake_op_fail_list = NULL;
static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
const char *use_date);
/*!
* \internal
* \brief Create an action name for use in a dot graph
*
* \param[in] action Action to create name for
* \param[in] verbose If true, add action ID to name
*
* \return Newly allocated string with action name
* \note It is the caller's responsibility to free the result.
*/
static char *
create_action_name(const pcmk_action_t *action, bool verbose)
{
char *action_name = NULL;
const char *prefix = "";
const char *action_host = NULL;
const char *history_id = NULL;
const char *task = action->task;
if (action->node != NULL) {
action_host = action->node->priv->name;
} else if (!pcmk_is_set(action->flags, pcmk__action_pseudo)) {
action_host = "<none>";
}
if (pcmk__str_eq(action->task, PCMK_ACTION_CANCEL, pcmk__str_none)) {
prefix = "Cancel ";
task = action->cancel_task;
}
if (action->rsc != NULL) {
history_id = action->rsc->priv->history_id;
}
if (history_id != NULL) {
char *key = NULL;
guint interval_ms = 0;
if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0,
&interval_ms) != pcmk_rc_ok) {
interval_ms = 0;
}
if (pcmk__strcase_any_of(action->task, PCMK_ACTION_NOTIFY,
PCMK_ACTION_NOTIFIED, NULL)) {
const char *n_type = g_hash_table_lookup(action->meta,
"notify_key_type");
const char *n_task = g_hash_table_lookup(action->meta,
"notify_key_operation");
pcmk__assert((n_type != NULL) && (n_task != NULL));
key = pcmk__notify_key(history_id, n_type, n_task);
} else {
key = pcmk__op_key(history_id, task, interval_ms);
}
if (action_host != NULL) {
action_name = crm_strdup_printf("%s%s %s",
prefix, key, action_host);
} else {
action_name = crm_strdup_printf("%s%s", prefix, key);
}
free(key);
} else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH,
pcmk__str_none)) {
const char *op = g_hash_table_lookup(action->meta,
PCMK__META_STONITH_ACTION);
action_name = crm_strdup_printf("%s%s '%s' %s",
prefix, action->task, op, action_host);
} else if (action->rsc && action_host) {
action_name = crm_strdup_printf("%s%s %s",
prefix, action->uuid, action_host);
} else if (action_host) {
action_name = crm_strdup_printf("%s%s %s",
prefix, action->task, action_host);
} else {
action_name = crm_strdup_printf("%s", action->uuid);
}
if (verbose) {
char *with_id = crm_strdup_printf("%s (%d)", action_name, action->id);
free(action_name);
action_name = with_id;
}
return action_name;
}
/*!
* \internal
* \brief Display the status of a cluster
*
* \param[in,out] scheduler Scheduler data
* \param[in] show_opts How to modify display (as pcmk_show_opt_e flags)
* \param[in] section_opts Sections to display (as pcmk_section_e flags)
* \param[in] title What to use as list title
* \param[in] print_spacer Whether to display a spacer first
*/
static void
print_cluster_status(pcmk_scheduler_t *scheduler, uint32_t show_opts,
uint32_t section_opts, const char *title,
bool print_spacer)
{
pcmk__output_t *out = scheduler->priv->out;
GList *all = NULL;
crm_exit_t stonith_rc = 0;
enum pcmk_pacemakerd_state state = pcmk_pacemakerd_state_invalid;
section_opts |= pcmk_section_nodes | pcmk_section_resources;
show_opts |= pcmk_show_inactive_rscs | pcmk_show_failed_detail;
all = g_list_prepend(all, (gpointer) "*");
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "%s", title);
out->message(out, "cluster-status",
scheduler, state, stonith_rc, NULL,
pcmk__fence_history_none, section_opts, show_opts, NULL,
all, all);
out->end_list(out);
g_list_free(all);
}
/*!
* \internal
* \brief Display a summary of all actions scheduled in a transition
*
* \param[in,out] scheduler Scheduler data (fully scheduled)
* \param[in] print_spacer Whether to display a spacer first
*/
static void
print_transition_summary(pcmk_scheduler_t *scheduler, bool print_spacer)
{
pcmk__output_t *out = scheduler->priv->out;
PCMK__OUTPUT_SPACER_IF(out, print_spacer);
out->begin_list(out, NULL, NULL, "Transition Summary");
pcmk__output_actions(scheduler);
out->end_list(out);
}
/*!
* \internal
* \brief Reset scheduler input, output, date, and flags
*
* \param[in,out] scheduler Scheduler data
* \param[in] input What to set as cluster input
* \param[in] out What to set as cluster output object
* \param[in] use_date What to set as cluster's current timestamp
* \param[in] flags Group of enum pcmk__scheduler_flags to set
*/
static void
reset(pcmk_scheduler_t *scheduler, xmlNodePtr input, pcmk__output_t *out,
const char *use_date, unsigned int flags)
{
scheduler->input = input;
scheduler->priv->out = out;
set_effective_date(scheduler, true, use_date);
if (pcmk_is_set(flags, pcmk_sim_sanitized)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_sanitized);
}
if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
}
if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_show_utilization);
}
}
/*!
* \brief Write out a file in dot(1) format describing the actions that will
* be taken by the scheduler in response to an input CIB file.
*
* \param[in,out] scheduler Scheduler data
* \param[in] dot_file The filename to write
* \param[in] all_actions Write all actions, even those that are optional
* or are on unmanaged resources
* \param[in] verbose Add extra information, such as action IDs, to the
* output
*
* \return Standard Pacemaker return code
*/
static int
write_sim_dotfile(pcmk_scheduler_t *scheduler, const char *dot_file,
bool all_actions, bool verbose)
{
GList *iter = NULL;
FILE *dot_strm = fopen(dot_file, "w");
if (dot_strm == NULL) {
return errno;
}
fprintf(dot_strm, " digraph \"g\" {\n");
for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = (pcmk_action_t *) iter->data;
const char *style = "dashed";
const char *font = "black";
const char *color = "black";
char *action_name = create_action_name(action, verbose);
if (pcmk_is_set(action->flags, pcmk__action_pseudo)) {
font = "orange";
}
if (pcmk_is_set(action->flags, pcmk__action_added_to_graph)) {
style = PCMK__VALUE_BOLD;
color = "green";
} else if ((action->rsc != NULL)
&& !pcmk_is_set(action->rsc->flags, pcmk__rsc_managed)) {
color = "red";
font = "purple";
if (!all_actions) {
goto do_not_write;
}
} else if (pcmk_is_set(action->flags, pcmk__action_optional)) {
color = "blue";
if (!all_actions) {
goto do_not_write;
}
} else {
color = "red";
CRM_LOG_ASSERT(!pcmk_is_set(action->flags, pcmk__action_runnable));
}
pcmk__set_action_flags(action, pcmk__action_added_to_graph);
fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
action_name, style, color, font);
do_not_write:
free(action_name);
}
for (iter = scheduler->priv->actions; iter != NULL; iter = iter->next) {
pcmk_action_t *action = (pcmk_action_t *) iter->data;
for (GList *before_iter = action->actions_before;
before_iter != NULL; before_iter = before_iter->next) {
pcmk__related_action_t *before = before_iter->data;
char *before_name = NULL;
char *after_name = NULL;
const char *style = "dashed";
bool optional = true;
if (before->graphed) {
optional = false;
style = PCMK__VALUE_BOLD;
} else if (before->flags == pcmk__ar_none) {
continue;
} else if (pcmk_is_set(before->action->flags,
pcmk__action_added_to_graph)
&& pcmk_is_set(action->flags, pcmk__action_added_to_graph)
&& before->flags != pcmk__ar_if_on_same_node_or_target) {
optional = false;
}
if (all_actions || !optional) {
before_name = create_action_name(before->action, verbose);
after_name = create_action_name(action, verbose);
fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
before_name, after_name, style);
free(before_name);
free(after_name);
}
}
}
fprintf(dot_strm, "}\n");
fflush(dot_strm);
fclose(dot_strm);
return pcmk_rc_ok;
}
/*!
* \brief Profile the configuration updates and scheduler actions in a single
* CIB file, printing the profiling timings.
*
* \note \p scheduler->priv->out must have been set to a valid \p pcmk__output_t
* object before this function is called.
*
* \param[in] xml_file The CIB file to profile
* \param[in] repeat Number of times to run
* \param[in,out] scheduler Scheduler data
* \param[in] use_date The date to set the cluster's time to (may be NULL)
*/
static void
profile_file(const char *xml_file, long long repeat,
pcmk_scheduler_t *scheduler, const char *use_date)
{
pcmk__output_t *out = scheduler->priv->out;
xmlNode *cib_object = NULL;
clock_t start = 0;
clock_t end;
unsigned long long scheduler_flags = pcmk__sched_none;
pcmk__assert(out != NULL);
cib_object = pcmk__xml_read(xml_file);
start = clock();
if (pcmk_find_cib_element(cib_object, PCMK_XE_STATUS) == NULL) {
pcmk__xe_create(cib_object, PCMK_XE_STATUS);
}
if (pcmk__update_configured_schema(&cib_object, false) != pcmk_rc_ok) {
pcmk__xml_free(cib_object);
return;
}
if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) {
pcmk__xml_free(cib_object);
return;
}
if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
scheduler_flags |= pcmk__sched_output_scores;
}
if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
scheduler_flags |= pcmk__sched_show_utilization;
}
for (int i = 0; i < repeat; ++i) {
xmlNode *input = cib_object;
if (repeat > 1) {
input = pcmk__xml_copy(NULL, cib_object);
}
scheduler->input = input;
set_effective_date(scheduler, false, use_date);
pcmk__schedule_actions(input, scheduler_flags, scheduler);
pe_reset_working_set(scheduler);
}
end = clock();
out->message(out, "profile", xml_file, start, end);
}
void
pcmk__profile_dir(const char *dir, long long repeat,
pcmk_scheduler_t *scheduler, const char *use_date)
{
pcmk__output_t *out = scheduler->priv->out;
struct dirent **namelist;
int file_num = scandir(dir, &namelist, 0, alphasort);
pcmk__assert(out != NULL);
if (file_num > 0) {
struct stat prop;
char buffer[FILENAME_MAX];
out->begin_list(out, NULL, NULL, "Timings");
while (file_num--) {
if ('.' == namelist[file_num]->d_name[0]) {
free(namelist[file_num]);
continue;
} else if (!pcmk__ends_with_ext(namelist[file_num]->d_name,
".xml")) {
free(namelist[file_num]);
continue;
}
snprintf(buffer, sizeof(buffer), "%s/%s",
dir, namelist[file_num]->d_name);
if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
profile_file(buffer, repeat, scheduler, use_date);
}
free(namelist[file_num]);
}
free(namelist);
out->end_list(out);
}
}
/*!
* \brief Set the date of the cluster, either to the value given by
* \p use_date, or to the \c PCMK_XA_EXECUTION_DATE value in the CIB.
*
* \note \p scheduler->priv->out must have been set to a valid \p pcmk__output_t
* object before this function is called.
*
* \param[in,out] scheduler Scheduler data
* \param[in] print_original If \p true, the \c PCMK_XA_EXECUTION_DATE
* should also be printed
* \param[in] use_date The date to set the cluster's time to
* (may be NULL)
*/
static void
set_effective_date(pcmk_scheduler_t *scheduler, bool print_original,
const char *use_date)
{
pcmk__output_t *out = scheduler->priv->out;
time_t original_date = 0;
pcmk__assert(out != NULL);
crm_element_value_epoch(scheduler->input, PCMK_XA_EXECUTION_DATE,
&original_date);
if (use_date) {
scheduler->priv->now = crm_time_new(use_date);
out->info(out, "Setting effective cluster time: %s", use_date);
crm_time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->priv->now,
crm_time_log_date | crm_time_log_timeofday);
} else if (original_date != 0) {
scheduler->priv->now = pcmk__copy_timet(original_date);
if (print_original) {
char *when = crm_time_as_string(scheduler->priv->now,
crm_time_log_date
|crm_time_log_timeofday);
out->info(out, "Using the original execution date of: %s", when);
free(when);
}
}
}
/*!
* \internal
* \brief Simulate successfully executing a pseudo-action in a graph
*
* \param[in,out] graph Graph to update with pseudo-action result
* \param[in,out] action Pseudo-action to simulate executing
*
* \return Standard Pacemaker return code
*/
static int
simulate_pseudo_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
const char *task = crm_element_value(action->xml, PCMK__XA_OPERATION_KEY);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
out->message(out, "inject-pseudo-action", node, task);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate executing a resource action in a graph
*
* \param[in,out] graph Graph to update with resource action result
* \param[in,out] action Resource action to simulate executing
*
* \return Standard Pacemaker return code
*/
static int
simulate_resource_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
int rc;
lrmd_event_data_t *op = NULL;
int target_outcome = PCMK_OCF_OK;
const char *rtype = NULL;
const char *rclass = NULL;
const char *resource = NULL;
const char *rprovider = NULL;
const char *resource_config_name = NULL;
const char *operation = crm_element_value(action->xml, PCMK_XA_OPERATION);
const char *target_rc_s = crm_meta_value(action->params,
PCMK__META_OP_TARGET_RC);
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
xmlNode *action_rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE,
NULL, NULL);
char *node = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
char *uuid = NULL;
const char *router_node = crm_element_value(action->xml,
PCMK__XA_ROUTER_NODE);
// Certain actions don't need to be displayed or history entries
if (pcmk__str_eq(operation, CRM_OP_REPROBE, pcmk__str_none)) {
crm_debug("No history injection for %s op on %s", operation, node);
goto done; // Confirm action and update graph
}
if (action_rsc == NULL) { // Shouldn't be possible
crm_log_xml_err(action->xml, "Bad");
free(node);
return EPROTO;
}
/* A resource might be known by different names in the configuration and in
* the action (for example, a clone instance). Grab the configuration name
* (which is preferred when writing history), and if necessary, the instance
* name.
*/
resource_config_name = crm_element_value(action_rsc, PCMK_XA_ID);
if (resource_config_name == NULL) { // Shouldn't be possible
crm_log_xml_err(action->xml, "No ID");
free(node);
return EPROTO;
}
resource = resource_config_name;
if (pe_find_resource(fake_resource_list, resource) == NULL) {
const char *longname = crm_element_value(action_rsc, PCMK__XA_LONG_ID);
if ((longname != NULL)
&& (pe_find_resource(fake_resource_list, longname) != NULL)) {
resource = longname;
}
}
// Certain actions need to be displayed but don't need history entries
if (pcmk__strcase_any_of(operation, PCMK_ACTION_DELETE,
PCMK_ACTION_META_DATA, NULL)) {
out->message(out, "inject-rsc-action", resource, operation, node,
(guint) 0);
goto done; // Confirm action and update graph
}
rclass = crm_element_value(action_rsc, PCMK_XA_CLASS);
rtype = crm_element_value(action_rsc, PCMK_XA_TYPE);
rprovider = crm_element_value(action_rsc, PCMK_XA_PROVIDER);
pcmk__scan_min_int(target_rc_s, &target_outcome, 0);
pcmk__assert(fake_cib->cmds->query(fake_cib, NULL, NULL,
cib_sync_call) == pcmk_ok);
// Ensure the action node is in the CIB
uuid = crm_element_value_copy(action->xml, PCMK__META_ON_NODE_UUID);
cib_node = pcmk__inject_node(fake_cib, node,
((router_node == NULL)? uuid: node));
free(uuid);
pcmk__assert(cib_node != NULL);
// Add a history entry for the action
cib_resource = pcmk__inject_resource_history(out, cib_node, resource,
resource_config_name,
rclass, rtype, rprovider);
if (cib_resource == NULL) {
crm_err("Could not simulate action %d history for resource %s",
action->id, resource);
free(node);
pcmk__xml_free(cib_node);
return EINVAL;
}
// Simulate and display an executor event for the action result
op = pcmk__event_from_graph_action(cib_resource, action, PCMK_EXEC_DONE,
target_outcome, "User-injected result");
out->message(out, "inject-rsc-action", resource, op->op_type, node,
op->interval_ms);
// Check whether action is in a list of desired simulated failures
for (const GList *iter = fake_op_fail_list;
iter != NULL; iter = iter->next) {
const char *spec = (const char *) iter->data;
char *key = NULL;
const char *match_name = NULL;
+ const char *offset = NULL;
// Allow user to specify anonymous clone with or without instance number
key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource, op->op_type,
op->interval_ms, node);
if (strncasecmp(key, spec, strlen(key)) == 0) {
match_name = resource;
}
free(key);
// If not found, try the resource's name in the configuration
if ((match_name == NULL)
&& (strcmp(resource, resource_config_name) != 0)) {
key = crm_strdup_printf(PCMK__OP_FMT "@%s=", resource_config_name,
op->op_type, op->interval_ms, node);
if (strncasecmp(key, spec, strlen(key)) == 0) {
match_name = resource_config_name;
}
free(key);
}
if (match_name == NULL) {
continue; // This failed action entry doesn't match
}
// ${match_name}_${task}_${interval_in_ms}@${node}=${rc}
rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
if (rc != 1) {
out->err(out, "Invalid failed operation '%s' "
"(result code must be integer)", spec);
continue; // Keep checking other list entries
}
out->info(out, "Pretending action %d failed with rc=%d",
action->id, op->rc);
pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
graph->abort_priority = PCMK_SCORE_INFINITY;
+
+ if (pcmk__str_eq(op->op_type, PCMK_ACTION_START, pcmk__str_none)) {
+ offset = pcmk__s(graph->failed_start_offset, PCMK_VALUE_INFINITY);
+
+ } else if (pcmk__str_eq(op->op_type, PCMK_ACTION_STOP,
+ pcmk__str_none)) {
+ offset = pcmk__s(graph->failed_stop_offset, PCMK_VALUE_INFINITY);
+ }
+
pcmk__inject_failcount(out, fake_cib, cib_node, match_name, op->op_type,
- op->interval_ms, op->rc);
+ op->interval_ms, op->rc,
+ pcmk_str_is_infinity(offset));
break;
}
pcmk__inject_action_result(cib_resource, op, node, target_outcome);
lrmd_free_event(op);
rc = fake_cib->cmds->modify(fake_cib, PCMK_XE_STATUS, cib_node,
cib_sync_call);
pcmk__assert(rc == pcmk_ok);
done:
free(node);
pcmk__xml_free(cib_node);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate successfully executing a cluster action
*
* \param[in,out] graph Graph to update with action result
* \param[in,out] action Cluster action to simulate
*
* \return Standard Pacemaker return code
*/
static int
simulate_cluster_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *node = crm_element_value(action->xml, PCMK__META_ON_NODE);
const char *task = crm_element_value(action->xml, PCMK_XA_OPERATION);
xmlNode *rsc = pcmk__xe_first_child(action->xml, PCMK_XE_PRIMITIVE, NULL,
NULL);
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
out->message(out, "inject-cluster-action", node, task, rsc);
pcmk__update_graph(graph, action);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Simulate successfully executing a fencing action
*
* \param[in,out] graph Graph to update with action result
* \param[in,out] action Fencing action to simulate
*
* \return Standard Pacemaker return code
*/
static int
simulate_fencing_action(pcmk__graph_t *graph, pcmk__graph_action_t *action)
{
const char *op = crm_meta_value(action->params, PCMK__META_STONITH_ACTION);
char *target = crm_element_value_copy(action->xml, PCMK__META_ON_NODE);
out->message(out, "inject-fencing-action", target, op);
if (!pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) {
int rc = pcmk_ok;
GString *xpath = g_string_sized_new(512);
// Set node state to offline
xmlNode *cib_node = pcmk__inject_node_state_change(fake_cib, target,
false);
pcmk__assert(cib_node != NULL);
crm_xml_add(cib_node, PCMK_XA_CRM_DEBUG_ORIGIN, __func__);
rc = fake_cib->cmds->replace(fake_cib, PCMK_XE_STATUS, cib_node,
cib_sync_call);
pcmk__assert(rc == pcmk_ok);
// Simulate controller clearing node's resource history and attributes
pcmk__g_strcat(xpath,
"//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='", target, "']/" PCMK__XE_LRM,
NULL);
fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
cib_xpath|cib_sync_call);
g_string_truncate(xpath, 0);
pcmk__g_strcat(xpath,
"//" PCMK__XE_NODE_STATE
"[@" PCMK_XA_UNAME "='", target, "']"
"/" PCMK__XE_TRANSIENT_ATTRIBUTES, NULL);
fake_cib->cmds->remove(fake_cib, (const char *) xpath->str, NULL,
cib_xpath|cib_sync_call);
pcmk__xml_free(cib_node);
g_string_free(xpath, TRUE);
}
pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed);
pcmk__update_graph(graph, action);
free(target);
return pcmk_rc_ok;
}
enum pcmk__graph_status
pcmk__simulate_transition(pcmk_scheduler_t *scheduler, cib_t *cib,
const GList *op_fail_list)
{
pcmk__graph_t *transition = NULL;
enum pcmk__graph_status graph_rc;
pcmk__graph_functions_t simulation_fns = {
simulate_pseudo_action,
simulate_resource_action,
simulate_cluster_action,
simulate_fencing_action,
};
out = scheduler->priv->out;
fake_cib = cib;
fake_op_fail_list = op_fail_list;
if (!out->is_quiet(out)) {
out->begin_list(out, NULL, NULL, "Executing Cluster Transition");
}
pcmk__set_graph_functions(&simulation_fns);
transition = pcmk__unpack_graph(scheduler->priv->graph, crm_system_name);
pcmk__log_graph(LOG_DEBUG, transition);
fake_resource_list = scheduler->priv->resources;
do {
graph_rc = pcmk__execute_graph(transition);
} while (graph_rc == pcmk__graph_active);
fake_resource_list = NULL;
if (graph_rc != pcmk__graph_complete) {
out->err(out, "Transition failed: %s",
pcmk__graph_status2text(graph_rc));
pcmk__log_graph(LOG_ERR, transition);
out->err(out, "An invalid transition was produced");
}
pcmk__free_graph(transition);
if (!out->is_quiet(out)) {
// If not quiet, we'll need the resulting CIB for later display
xmlNode *cib_object = NULL;
int rc = fake_cib->cmds->query(fake_cib, NULL, &cib_object,
cib_sync_call);
pcmk__assert(rc == pcmk_ok);
pe_reset_working_set(scheduler);
scheduler->input = cib_object;
out->end_list(out);
}
return graph_rc;
}
int
pcmk__simulate(pcmk_scheduler_t *scheduler, pcmk__output_t *out,
const pcmk_injections_t *injections, unsigned int flags,
uint32_t section_opts, const char *use_date,
const char *input_file, const char *graph_file,
const char *dot_file)
{
int printed = pcmk_rc_no_output;
int rc = pcmk_rc_ok;
xmlNodePtr input = NULL;
cib_t *cib = NULL;
rc = cib__signon_query(out, &cib, &input);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
reset(scheduler, input, out, use_date, flags);
cluster_status(scheduler);
if (!out->is_quiet(out)) {
const bool show_pending = pcmk_is_set(flags, pcmk_sim_show_pending);
if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
printed = out->message(out, "maint-mode", scheduler->flags);
}
if ((scheduler->priv->disabled_resources > 0)
|| (scheduler->priv->blocked_resources > 0)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
printed = out->info(out,
"%d of %d resource instances DISABLED and "
"%d BLOCKED from further action due to failure",
scheduler->priv->disabled_resources,
scheduler->priv->ninstances,
scheduler->priv->blocked_resources);
}
/* Most formatted output headers use caps for each word, but this one
* only has the first word capitalized for compatibility with pcs.
*/
print_cluster_status(scheduler, (show_pending? pcmk_show_pending : 0),
section_opts, "Current cluster status",
(printed == pcmk_rc_ok));
printed = pcmk_rc_ok;
}
// If the user requested any injections, handle them
if ((injections->node_down != NULL)
|| (injections->node_fail != NULL)
|| (injections->node_up != NULL)
|| (injections->op_inject != NULL)
|| (injections->ticket_activate != NULL)
|| (injections->ticket_grant != NULL)
|| (injections->ticket_revoke != NULL)
|| (injections->ticket_standby != NULL)
|| (injections->watchdog != NULL)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
pcmk__inject_scheduler_input(scheduler, cib, injections);
printed = pcmk_rc_ok;
rc = cib->cmds->query(cib, NULL, &input, cib_sync_call);
if (rc != pcmk_rc_ok) {
rc = pcmk_legacy2rc(rc);
goto simulate_done;
}
cleanup_calculations(scheduler);
reset(scheduler, input, out, use_date, flags);
cluster_status(scheduler);
}
if (input_file != NULL) {
rc = pcmk__xml_write_file(input, input_file, false);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
}
if (pcmk_any_flags_set(flags, pcmk_sim_process | pcmk_sim_simulate)) {
pcmk__output_t *logger_out = NULL;
unsigned long long scheduler_flags = pcmk__sched_none;
if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
scheduler_flags |= pcmk__sched_output_scores;
}
if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
scheduler_flags |= pcmk__sched_show_utilization;
}
if (pcmk_all_flags_set(scheduler->flags,
pcmk__sched_output_scores
|pcmk__sched_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL,
"Assignment Scores and Utilization Information");
printed = pcmk_rc_ok;
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_output_scores)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Assignment Scores");
printed = pcmk_rc_ok;
} else if (pcmk_is_set(scheduler->flags, pcmk__sched_show_utilization)) {
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
out->begin_list(out, NULL, NULL, "Utilization Information");
printed = pcmk_rc_ok;
} else {
rc = pcmk__log_output_new(&logger_out);
if (rc != pcmk_rc_ok) {
goto simulate_done;
}
pe__register_messages(logger_out);
pcmk__register_lib_messages(logger_out);
scheduler->priv->out = logger_out;
}
pcmk__schedule_actions(input, scheduler_flags, scheduler);
if (logger_out == NULL) {
out->end_list(out);
} else {
logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
pcmk__output_free(logger_out);
scheduler->priv->out = out;
}
input = NULL; /* Don't try and free it twice */
if (graph_file != NULL) {
rc = pcmk__xml_write_file(scheduler->priv->graph, graph_file,
false);
if (rc != pcmk_rc_ok) {
rc = pcmk_rc_graph_error;
goto simulate_done;
}
}
if (dot_file != NULL) {
rc = write_sim_dotfile(scheduler, dot_file,
pcmk_is_set(flags, pcmk_sim_all_actions),
pcmk_is_set(flags, pcmk_sim_verbose));
if (rc != pcmk_rc_ok) {
rc = pcmk_rc_dot_error;
goto simulate_done;
}
}
if (!out->is_quiet(out)) {
print_transition_summary(scheduler, printed == pcmk_rc_ok);
}
}
rc = pcmk_rc_ok;
if (!pcmk_is_set(flags, pcmk_sim_simulate)) {
goto simulate_done;
}
PCMK__OUTPUT_SPACER_IF(out, printed == pcmk_rc_ok);
if (pcmk__simulate_transition(scheduler, cib, injections->op_fail)
!= pcmk__graph_complete) {
rc = pcmk_rc_invalid_transition;
}
if (out->is_quiet(out)) {
goto simulate_done;
}
set_effective_date(scheduler, true, use_date);
if (pcmk_is_set(flags, pcmk_sim_show_scores)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_output_scores);
}
if (pcmk_is_set(flags, pcmk_sim_show_utilization)) {
pcmk__set_scheduler_flags(scheduler, pcmk__sched_show_utilization);
}
cluster_status(scheduler);
print_cluster_status(scheduler, 0, section_opts, "Revised Cluster Status",
true);
simulate_done:
cib__clean_up_connection(&cib);
return rc;
}
int
pcmk_simulate(xmlNodePtr *xml, pcmk_scheduler_t *scheduler,
const pcmk_injections_t *injections, unsigned int flags,
unsigned int section_opts, const char *use_date,
const char *input_file, const char *graph_file,
const char *dot_file)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
pe__register_messages(out);
pcmk__register_lib_messages(out);
rc = pcmk__simulate(scheduler, out, injections, flags, section_opts,
use_date, input_file, graph_file, dot_file);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
return rc;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 25, 2:00 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2611353
Default Alt Text
(192 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment