Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h
index 153f4b6015..59f10f9eb8 100644
--- a/include/crm/stonith-ng.h
+++ b/include/crm/stonith-ng.h
@@ -1,775 +1,772 @@
/*
* Copyright 2004-2025 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_STONITH_NG__H
# define PCMK__CRM_STONITH_NG__H
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Fencing aka. STONITH
* \ingroup fencing
*/
/* IMPORTANT: dlm source code includes this file directly. Until dlm v4.2.0
* (commit 5afd9fdc), dlm did not have access to other Pacemaker headers on its
* include path. This file should *not* include any other Pacemaker headers
* until we decide that we no longer need to support dlm versions older than
* v4.2.0.
*
* @COMPAT Remove this restriction and take any opportunities to simplify code
* when possible.
*/
# include <dlfcn.h>
# include <errno.h>
# include <stdbool.h> // bool
# include <stdint.h> // uint32_t
# include <time.h> // time_t
// @TODO Keep this definition but make it internal
/*!
* \brief Fencer API connection state
* \deprecated Do not use
*/
enum stonith_state {
stonith_connected_command,
stonith_connected_query,
stonith_disconnected,
};
//! Flags that can be set in call options for API requests
enum stonith_call_options {
//! No options
st_opt_none = 0,
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
//! \deprecated Unused
st_opt_verbose = (1 << 0),
#endif
//! The fencing target is allowed to execute the request
st_opt_allow_self_fencing = (1 << 1),
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
//! \deprecated Use st_opt_allow_self_fencing instead
st_opt_allow_suicide = st_opt_allow_self_fencing,
#endif
// Used internally to indicate that request is manual fence confirmation
//! \internal Do not use
st_opt_manual_ack = (1 << 3),
//! Do not return any reply from server
st_opt_discard_reply = (1 << 4),
// Used internally to indicate that request requires a fencing topology
//! \internal Do not use
st_opt_topology = (1 << 6),
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
//! \deprecated Unused
st_opt_scope_local = (1 << 8),
#endif
//! Interpret target as node cluster layer ID instead of name
st_opt_cs_nodeid = (1 << 9),
//! Wait for request to be completed before returning
st_opt_sync_call = (1 << 12),
//! Request that server send an update with optimal callback timeout
st_opt_timeout_updates = (1 << 13),
//! Invoke callback only if request succeeded
st_opt_report_only_success = (1 << 14),
//! For a fence history request, request that the history be cleared
st_opt_cleanup = (1 << 19),
//! For a fence history request, broadcast the request to all nodes
st_opt_broadcast = (1 << 20),
};
/*! Order matters here, do not change values */
enum op_state
{
st_query,
st_exec,
st_done,
st_duplicate,
st_failed,
};
// Supported fence agent interface standards
enum stonith_namespace {
st_namespace_invalid,
st_namespace_any,
st_namespace_internal, // Implemented internally by Pacemaker
/* Neither of these projects are active any longer, but the fence agent
* interfaces they created are still in use and supported by Pacemaker.
*/
st_namespace_rhcs, // Red Hat Cluster Suite compatible
st_namespace_lha, // Linux-HA compatible
};
enum stonith_namespace stonith_get_namespace(const char *agent,
const char *namespace_s);
typedef struct stonith_key_value_s {
char *key;
char *value;
struct stonith_key_value_s *next;
} stonith_key_value_t;
typedef struct stonith_history_s {
char *target;
char *action;
char *origin;
char *delegate;
char *client;
int state;
time_t completed;
struct stonith_history_s *next;
long completed_nsec;
char *exit_reason;
} stonith_history_t;
// @TODO Keep this typedef but rename it and make it internal
typedef struct stonith_s stonith_t;
typedef struct stonith_event_s {
char *id;
char *operation;
int result;
char *origin;
char *target;
char *action;
char *executioner;
char *device;
/*! The name of the client that initiated the action. */
char *client_origin;
//! \internal This field should be treated as internal to Pacemaker
void *opaque;
} stonith_event_t;
typedef struct stonith_callback_data_s {
int rc;
int call_id;
void *userdata;
//! \internal This field should be treated as internal to Pacemaker
void *opaque;
} stonith_callback_data_t;
// @TODO Keep this object but make it internal
/*!
* \brief Fencer API operations
* \deprecated Use appropriate functions in libpacemaker instead
*/
typedef struct stonith_api_operations_s {
/*!
* \brief Destroy a fencer connection
*
* \param[in,out] st Fencer connection to destroy
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*free) (stonith_t *st);
/*!
* \brief Connect to the local fencer
*
* \param[in,out] st Fencer connection to connect
* \param[in] name Client name to use
* \param[out] stonith_fd If NULL, use a main loop, otherwise
* store IPC file descriptor here
*
* \return Legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*connect) (stonith_t *st, const char *name, int *stonith_fd);
/*!
* \brief Disconnect from the local stonith daemon.
*
* \param[in,out] st Fencer connection to disconnect
*
* \return Legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*disconnect)(stonith_t *st);
/*!
* \brief Unregister a fence device with the local fencer
*
* \param[in,out] st Fencer connection to disconnect
* \param[in] options Group of enum stonith_call_options
* \param[in] name ID of fence device to unregister
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*remove_device)(stonith_t *st, int options, const char *name);
/*!
* \brief Register a fence device with the local fencer
*
* \param[in,out] st Fencer connection to use
* \param[in] options Group of enum stonith_call_options
* \param[in] id ID of fence device to register
* \param[in] namespace_s Type of fence agent to search for ("redhat"
* or "stonith-ng" for RHCS-style, "internal"
* for Pacemaker-internal devices, "heartbeat"
* for LHA-style, or "any" or NULL for any)
* \param[in] agent Name of fence agent for device
* \param[in] params Fence agent parameters for device
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*register_device)(stonith_t *st, int options, const char *id,
const char *namespace_s, const char *agent,
const stonith_key_value_t *params);
/*!
* \brief Unregister a fencing level for specified node with local fencer
*
* \param[in,out] st Fencer connection to use
* \param[in] options Group of enum stonith_call_options
* \param[in] node Target node to unregister level for
* \param[in] level Topology level number to unregister
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \note Not used internally
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*remove_level)(stonith_t *st, int options, const char *node,
int level);
/*!
* \brief Register a fencing level for specified node with local fencer
*
* \param[in,out] st Fencer connection to use
* \param[in] options Group of enum stonith_call_options
* \param[in] node Target node to register level for
* \param[in] level Topology level number to register
* \param[in] device_list Devices to register in level
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \note Used only by cts-fence-helper.c internally
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*register_level)(stonith_t *st, int options, const char *node,
int level, const stonith_key_value_t *device_list);
/*!
* \brief Retrieve a fence agent's metadata
*
* \param[in,out] stonith Fencer connection
* \param[in] call_options Group of enum stonith_call_options
* (currently ignored)
* \param[in] agent Fence agent to query
- * \param[in] namespace_s Type of fence agent to search for ("redhat"
- * or "stonith-ng" for RHCS-style, "internal"
- * for Pacemaker-internal devices, "heartbeat"
- * for LHA-style, or "any" or NULL for any)
+ * \param[in] namespace_s Ignored
* \param[out] output Where to store metadata
* \param[in] timeout_sec Error if not complete within this time
*
* \return Legacy Pacemaker return code
* \note The caller is responsible for freeing *output using free().
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*metadata)(stonith_t *stonith, int call_options, const char *agent,
const char *namespace_s, char **output, int timeout_sec);
/*!
* \brief Retrieve a list of installed fence agents
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* (currently ignored)
* \param[in] namespace_s Type of fence agents to list ("redhat"
* or "stonith-ng" for RHCS-style, "internal" for
* Pacemaker-internal devices, "heartbeat" for
* LHA-style, or "any" or NULL for all)
* \param[out] devices Where to store agent list
* \param[in] timeout Error if unable to complete within this
* (currently ignored)
*
* \return Number of items in list on success, or negative errno otherwise
* \note The caller is responsible for freeing the returned list with
* \c stonith__key_value_freeall().
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*list_agents)(stonith_t *stonith, int call_options,
const char *namespace_s, stonith_key_value_t **devices,
int timeout);
/*!
* \brief Get the output of a fence device's list action
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] id Fence device ID to run list for
* \param[out] list_info Where to store list output
* \param[in] timeout Error if unable to complete within this
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*list)(stonith_t *stonith, int call_options, const char *id,
char **list_info, int timeout);
/*!
* \brief Check whether a fence device is reachable by monitor action
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] id Fence device ID to run monitor for
* \param[in] timeout Error if unable to complete within this
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*monitor)(stonith_t *stonith, int call_options, const char *id,
int timeout);
/*!
* \brief Check whether a fence device target is reachable by status action
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] id Fence device ID to run status for
* \param[in] port Fence target to run status for
* \param[in] timeout Error if unable to complete within this
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \note Used only by cts-fence-helper.c internally
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*status)(stonith_t *stonith, int call_options, const char *id,
const char *port, int timeout);
/*!
* \brief List registered fence devices
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] target Fence target to run status for
* \param[out] devices Where to store list of fence devices
* \param[in] timeout Error if unable to complete within this
*
* \note If node is provided, only devices that can fence the node id
* will be returned.
*
* \return Number of items in list on success, or negative errno otherwise
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*query)(stonith_t *stonith, int call_options, const char *target,
stonith_key_value_t **devices, int timeout);
/*!
* \brief Request that a target get fenced
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] node Fence target
* \param[in] action "on", "off", or "reboot"
* \param[in] timeout Default per-device timeout to use with
* each executed device
* \param[in] tolerance Accept result of identical fence action
* completed within this time
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \note Used only by cts-fence-helper.c and \c stonith_api_kick()
* internally. The latter might go away eventually if dlm starts using
* \c pcmk_request_fencing().
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*fence)(stonith_t *stonith, int call_options, const char *node,
const char *action, int timeout, int tolerance);
/*!
* \brief Manually confirm that a node has been fenced
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] target Fence target
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*confirm)(stonith_t *stonith, int call_options, const char *target);
/*!
* \brief List fencing actions that have occurred for a target
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] node Fence target
* \param[out] history Where to store list of fencing actions
* \param[in] timeout Error if unable to complete within this
*
* \return Legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*history)(stonith_t *stonith, int call_options, const char *node,
stonith_history_t **history, int timeout);
/*!
* \brief Register a callback for fence notifications
*
* \param[in,out] stonith Fencer connection to use
* \param[in] event Event to register for
* \param[in] callback Callback to register
*
* \return Legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*register_notification)(stonith_t *stonith, const char *event,
void (*callback)(stonith_t *st,
stonith_event_t *e));
/*!
* \brief Unregister callbacks for fence notifications
*
* \param[in,out] stonith Fencer connection to use
* \param[in] event Event to unregister callbacks for (NULL for all)
*
* \return Legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*remove_notification)(stonith_t *stonith, const char *event);
/*!
* \brief Register a callback for an asynchronous fencing result
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_id Call ID to register callback for
* \param[in] timeout Error if result not received in this time
* \param[in] options Group of enum stonith_call_options
* (respects \c st_opt_timeout_updates and
* \c st_opt_report_only_success)
* \param[in,out] user_data Pointer to pass to callback
* \param[in] callback_name Unique identifier for callback
* \param[in] callback Callback to register (may be called
* immediately if \p call_id indicates error)
*
* \return \c TRUE on success, \c FALSE if call_id indicates error,
* or -EINVAL if \p stonith is not valid
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*register_callback)(stonith_t *stonith, int call_id, int timeout,
int options, void *user_data,
const char *callback_name,
void (*callback)(stonith_t *st,
stonith_callback_data_t *data));
/*!
* \brief Unregister callbacks for asynchronous fencing results
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_id If \p all_callbacks is false, call ID
* to unregister callback for
* \param[in] all_callbacks If true, unregister all callbacks
*
* \return pcmk_ok
* \note Not used internally (but perhaps it should be)
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*remove_callback)(stonith_t *stonith, int call_id, bool all_callbacks);
/*!
* \brief Unregister fencing level for specified node, pattern or attribute
*
* \param[in,out] st Fencer connection to use
* \param[in] options Group of enum stonith_call_options
* \param[in] node If not NULL, unregister level targeting this node
* \param[in] pattern If not NULL, unregister level targeting nodes
* whose names match this regular expression
* \param[in] attr If this and \p value are not NULL, unregister
* level targeting nodes with this node attribute
* set to \p value
* \param[in] value If this and \p attr are not NULL, unregister
* level targeting nodes with node attribute \p attr
* set to this
* \param[in] level Topology level number to remove
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \note The caller should set only one of \p node, \p pattern, or \p attr
* and \p value.
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*remove_level_full)(stonith_t *st, int options,
const char *node, const char *pattern,
const char *attr, const char *value, int level);
/*!
* \brief Register fencing level for specified node, pattern or attribute
*
* \param[in,out] st Fencer connection to use
* \param[in] options Group of enum stonith_call_options
* \param[in] node If not NULL, register level targeting this
* node by name
* \param[in] pattern If not NULL, register level targeting nodes
* whose names match this regular expression
* \param[in] attr If this and \p value are not NULL, register
* level targeting nodes with this node
* attribute set to \p value
* \param[in] value If this and \p attr are not NULL, register
* level targeting nodes with node attribute
* \p attr set to this
* \param[in] level Topology level number to remove
* \param[in] device_list Devices to use in level
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
*
* \note The caller should set only one of node, pattern or attr/value.
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*register_level_full)(stonith_t *st, int options,
const char *node, const char *pattern,
const char *attr, const char *value, int level,
const stonith_key_value_t *device_list);
/*!
* \brief Validate an arbitrary stonith device configuration
*
* \param[in,out] st Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] rsc_id ID used to replace CIB secrets in \p params
* \param[in] namespace_s Ignored
* \param[in] agent Fence agent to validate
* \param[in] params Configuration parameters to pass to agent
* \param[in] timeout Fail if no response within this many seconds
* \param[out] output If non-NULL, where to store any agent output
* \param[out] error_output If non-NULL, where to store agent error output
*
* \return pcmk_ok if validation succeeds, -errno otherwise
* \note If pcmk_ok is returned, the caller is responsible for freeing
* the output (if requested) with free().
* \note Not used internally
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*validate)(stonith_t *st, int call_options, const char *rsc_id,
const char *namespace_s, const char *agent,
const stonith_key_value_t *params, int timeout,
char **output, char **error_output);
/*!
* \brief Request delayed fencing of a target
*
* \param[in,out] stonith Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] node Fence target
* \param[in] action "on", "off", or "reboot"
* \param[in] timeout Default per-device timeout to use with
* each executed device
* \param[in] tolerance Accept result of identical fence action
* completed within this time
* \param[in] delay Execute fencing after this delay (-1
* disables any delay from pcmk_delay_base
* and pcmk_delay_max)
*
* \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
* on success, otherwise a negative legacy Pacemaker return code
* \deprecated \c stonith_api_operations_t is deprecated for external use
*/
int (*fence_with_delay)(stonith_t *stonith, int call_options,
const char *node, const char *action, int timeout,
int tolerance, int delay);
} stonith_api_operations_t;
// @TODO Keep this object but make it internal
/*!
* \brief Fencer API connection object
* \deprecated Use appropriate functions in libpacemaker instead
*/
struct stonith_s {
enum stonith_state state;
int call_id;
void *st_private;
stonith_api_operations_t *cmds;
};
/* Basic helpers that allows nodes to be fenced and the history to be
* queried without mainloop or the caller understanding the full API
*
* At least one of nodeid and uname are required
*
* NOTE: dlm (as of at least 4.3.0) uses these (via the helper functions below)
*/
int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off);
time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress);
/*
* Helpers for using the above functions without install-time dependencies
*
* Usage:
* #include <crm/stonith-ng.h>
*
* To turn a node off by corosync nodeid:
* stonith_api_kick_helper(nodeid, 120, 1);
*
* To check the last fence date/time (also by nodeid):
* last = stonith_api_time_helper(nodeid, 0);
*
* To check if fencing is in progress:
* if(stonith_api_time_helper(nodeid, 1) > 0) { ... }
*
* eg.
#include <stdio.h>
#include <time.h>
#include <crm/stonith-ng.h>
int
main(int argc, char ** argv)
{
int rc = 0;
int nodeid = 102;
rc = stonith_api_time_helper(nodeid, 0);
printf("%d last fenced at %s\n", nodeid, ctime(rc));
rc = stonith_api_kick_helper(nodeid, 120, 1);
printf("%d fence result: %d\n", nodeid, rc);
rc = stonith_api_time_helper(nodeid, 0);
printf("%d last fenced at %s\n", nodeid, ctime(rc));
return 0;
}
*/
#define STONITH_LIBRARY "libstonithd.so.56"
// NOTE: dlm (as of at least 4.3.0) uses these (via the helper functions below)
typedef int (*st_api_kick_fn) (int nodeid, const char *uname, int timeout, bool off);
typedef time_t (*st_api_time_fn) (int nodeid, const char *uname, bool in_progress);
// NOTE: dlm (as of at least 4.3.0) uses this
static inline int
stonith_api_kick_helper(uint32_t nodeid, int timeout, bool off)
{
static void *st_library = NULL;
static st_api_kick_fn st_kick_fn;
if (st_library == NULL) {
st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
}
if (st_library && st_kick_fn == NULL) {
st_kick_fn = (st_api_kick_fn) dlsym(st_library, "stonith_api_kick");
}
if (st_kick_fn == NULL) {
#ifdef ELIBACC
return -ELIBACC;
#else
return -ENOSYS;
#endif
}
return (*st_kick_fn) (nodeid, NULL, timeout, off);
}
// NOTE: dlm (as of at least 4.3.0) uses this
static inline time_t
stonith_api_time_helper(uint32_t nodeid, bool in_progress)
{
static void *st_library = NULL;
static st_api_time_fn st_time_fn;
if (st_library == NULL) {
st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
}
if (st_library && st_time_fn == NULL) {
st_time_fn = (st_api_time_fn) dlsym(st_library, "stonith_api_time");
}
if (st_time_fn == NULL) {
return 0;
}
return (*st_time_fn) (nodeid, NULL, in_progress);
}
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
/* Normally we'd put this section in a separate file (crm/fencing/compat.h), but
* we can't do that for the reason noted at the top of this file. That does mean
* we have to duplicate these declarations where they're implemented.
*/
//! \deprecated Use appropriate functions in libpacemaker
stonith_t *stonith_api_new(void);
//! \deprecated Use appropriate functions in libpacemaker
void stonith_api_delete(stonith_t *stonith);
//! \deprecated Do not use
void stonith_dump_pending_callbacks(stonith_t *stonith);
//! \deprecated Do not use
bool stonith_dispatch(stonith_t *stonith_api);
//! \deprecated Do not use
stonith_key_value_t *stonith_key_value_add(stonith_key_value_t *kvp,
const char *key, const char *value);
//! \deprecated Do not use
void stonith_key_value_freeall(stonith_key_value_t *head, int keys, int values);
//! \deprecated Do not use
void stonith_history_free(stonith_history_t *head);
//! \deprecated Do not use
int stonith_api_connect_retry(stonith_t *st, const char *name,
int max_attempts);
//! \deprecated Do not use
const char *stonith_op_state_str(enum op_state state);
//! \deprecated Do not use
bool stonith_agent_exists(const char *agent, int timeout);
//! \deprecated Do not use
const char *stonith_action_str(const char *action);
//! \deprecated Do not use
enum stonith_namespace stonith_text2namespace(const char *namespace_s);
//! \deprecated Do not use
const char *stonith_namespace2text(enum stonith_namespace st_namespace);
#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c
index 34ec0dee0c..277faf6ec9 100644
--- a/lib/fencing/st_client.c
+++ b/lib/fencing/st_client.c
@@ -1,2930 +1,2930 @@
/*
* Copyright 2004-2025 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 <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>
#include <sys/types.h>
#include <glib.h>
#include <libxml/tree.h> // xmlNode
#include <libxml/xpath.h> // xmlXPathObject, etc.
#include <crm/crm.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#include "fencing_private.h"
CRM_TRACE_INIT_DATA(stonith);
// Used as stonith_t:st_private
typedef struct stonith_private_s {
char *token;
crm_ipc_t *ipc;
mainloop_io_t *source;
GHashTable *stonith_op_callback_table;
GList *notify_list;
int notify_refcnt;
bool notify_deletes;
void (*op_callback) (stonith_t * st, stonith_callback_data_t * data);
} stonith_private_t;
// Used as stonith_event_t:opaque
struct event_private {
pcmk__action_result_t result;
};
typedef struct stonith_notify_client_s {
const char *event;
const char *obj_id; /* implement one day */
const char *obj_type; /* implement one day */
void (*notify) (stonith_t * st, stonith_event_t * e);
bool delete;
} stonith_notify_client_t;
typedef struct stonith_callback_client_s {
void (*callback) (stonith_t * st, stonith_callback_data_t * data);
const char *id;
void *user_data;
gboolean only_success;
gboolean allow_timeout_updates;
struct timer_rec_s *timer;
} stonith_callback_client_t;
struct notify_blob_s {
stonith_t *stonith;
xmlNode *xml;
};
struct timer_rec_s {
int call_id;
int timeout;
guint ref;
stonith_t *stonith;
};
typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *,
xmlNode *, xmlNode *, xmlNode **, xmlNode **);
xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
int call_options);
static int stonith_send_command(stonith_t *stonith, const char *op,
xmlNode *data, xmlNode **output_data,
int call_options, int timeout);
static void stonith_connection_destroy(gpointer user_data);
static void stonith_send_notification(gpointer data, gpointer user_data);
static int stonith_api_del_notification(stonith_t *stonith,
const char *event);
/*!
* \internal
* \brief Parse fence agent namespace from a string
*
* \param[in] namespace_s Name of namespace as string
*
* \return enum value parsed from \p namespace_s
*/
static enum stonith_namespace
parse_namespace(const char *namespace_s)
{
if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) {
return st_namespace_any;
}
/* @TODO Is "redhat" still necessary except for stonith_text2namespace()
* backward compatibility?
*/
if (pcmk__str_any_of(namespace_s, "redhat", "stonith-ng", NULL)) {
return st_namespace_rhcs;
}
if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) {
return st_namespace_internal;
}
if (pcmk__str_eq(namespace_s, "heartbeat", pcmk__str_none)) {
return st_namespace_lha;
}
return st_namespace_invalid;
}
/*!
* \internal
* \brief Get name of a fence agent namespace as a string
*
* \param[in] st_namespace Namespace as enum value
*
* \return Name of \p st_namespace as a string
*/
static const char *
namespace_text(enum stonith_namespace st_namespace)
{
switch (st_namespace) {
case st_namespace_any:
return "any";
case st_namespace_rhcs:
return "stonith-ng";
case st_namespace_internal:
return "internal";
case st_namespace_lha:
return "heartbeat";
default:
return "unsupported";
}
}
/*!
* \brief Determine namespace of a fence agent
*
* \param[in] agent Fence agent type
* \param[in] namespace_s Name of agent namespace as string, if known
*
* \return Namespace of specified agent, as enum value
*/
enum stonith_namespace
stonith_get_namespace(const char *agent, const char *namespace_s)
{
if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) {
return st_namespace_internal;
}
if (stonith__agent_is_rhcs(agent)) {
return st_namespace_rhcs;
}
#if HAVE_STONITH_STONITH_H
if (stonith__agent_is_lha(agent)) {
return st_namespace_lha;
}
#endif
return st_namespace_invalid;
}
gboolean
stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node)
{
gboolean rv = FALSE;
stonith_t *stonith_api = (st != NULL)? st : stonith__api_new();
char *list = NULL;
if(stonith_api) {
if (stonith_api->state == stonith_disconnected) {
int rc = stonith_api->cmds->connect(stonith_api, "stonith-api", NULL);
if (rc != pcmk_ok) {
crm_err("Failed connecting to Stonith-API for watchdog-fencing-query.");
}
}
if (stonith_api->state != stonith_disconnected) {
/* caveat!!!
* this might fail when when stonithd is just updating the device-list
* probably something we should fix as well for other api-calls */
int rc = stonith_api->cmds->list(stonith_api, st_opt_sync_call, STONITH_WATCHDOG_ID, &list, 0);
if ((rc != pcmk_ok) || (list == NULL)) {
/* due to the race described above it can happen that
* we drop in here - so as not to make remote nodes
* panic on that answer
*/
if (rc == -ENODEV) {
crm_notice("Cluster does not have watchdog fencing device");
} else {
crm_warn("Could not check for watchdog fencing device: %s",
pcmk_strerror(rc));
}
} else if (list[0] == '\0') {
rv = TRUE;
} else {
GList *targets = stonith__parse_targets(list);
rv = pcmk__str_in_list(node, targets, pcmk__str_casei);
g_list_free_full(targets, free);
}
free(list);
if (!st) {
/* if we're provided the api we still might have done the
* connection - but let's assume the caller won't bother
*/
stonith_api->cmds->disconnect(stonith_api);
}
}
if (!st) {
stonith__api_free(stonith_api);
}
} else {
crm_err("Stonith-API for watchdog-fencing-query couldn't be created.");
}
crm_trace("Pacemaker assumes node %s %sto do watchdog-fencing.",
node, rv?"":"not ");
return rv;
}
gboolean
stonith__watchdog_fencing_enabled_for_node(const char *node)
{
return stonith__watchdog_fencing_enabled_for_node_api(NULL, node);
}
/* when cycling through the list we don't want to delete items
so just mark them and when we know nobody is using the list
loop over it to remove the marked items
*/
static void
foreach_notify_entry (stonith_private_t *private,
GFunc func,
gpointer user_data)
{
private->notify_refcnt++;
g_list_foreach(private->notify_list, func, user_data);
private->notify_refcnt--;
if ((private->notify_refcnt == 0) &&
private->notify_deletes) {
GList *list_item = private->notify_list;
private->notify_deletes = FALSE;
while (list_item != NULL)
{
stonith_notify_client_t *list_client = list_item->data;
GList *next = g_list_next(list_item);
if (list_client->delete) {
free(list_client);
private->notify_list =
g_list_delete_link(private->notify_list, list_item);
}
list_item = next;
}
}
}
static void
stonith_connection_destroy(gpointer user_data)
{
stonith_t *stonith = user_data;
stonith_private_t *native = NULL;
struct notify_blob_s blob;
crm_trace("Sending destroyed notification");
blob.stonith = stonith;
blob.xml = pcmk__xe_create(NULL, PCMK__XE_NOTIFY);
native = stonith->st_private;
native->ipc = NULL;
native->source = NULL;
free(native->token); native->token = NULL;
stonith->state = stonith_disconnected;
crm_xml_add(blob.xml, PCMK__XA_T, PCMK__VALUE_ST_NOTIFY);
crm_xml_add(blob.xml, PCMK__XA_SUBT, PCMK__VALUE_ST_NOTIFY_DISCONNECT);
foreach_notify_entry(native, stonith_send_notification, &blob);
pcmk__xml_free(blob.xml);
}
xmlNode *
create_device_registration_xml(const char *id, enum stonith_namespace standard,
const char *agent,
const stonith_key_value_t *params,
const char *rsc_provides)
{
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
#if HAVE_STONITH_STONITH_H
if (standard == st_namespace_any) {
standard = stonith_get_namespace(agent, NULL);
}
if (standard == st_namespace_lha) {
hash2field((gpointer) "plugin", (gpointer) agent, args);
agent = "fence_legacy";
}
#endif
crm_xml_add(data, PCMK_XA_ID, id);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add(data, PCMK_XA_AGENT, agent);
if ((standard != st_namespace_any) && (standard != st_namespace_invalid)) {
crm_xml_add(data, PCMK__XA_NAMESPACE, namespace_text(standard));
}
if (rsc_provides) {
crm_xml_add(data, PCMK__XA_RSC_PROVIDES, rsc_provides);
}
for (; params; params = params->next) {
hash2field((gpointer) params->key, (gpointer) params->value, args);
}
return data;
}
static int
stonith_api_register_device(stonith_t *st, int call_options,
const char *id, const char *namespace_s,
const char *agent,
const stonith_key_value_t *params)
{
int rc = 0;
xmlNode *data = NULL;
data = create_device_registration_xml(id, parse_namespace(namespace_s),
agent, params, NULL);
rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_remove_device(stonith_t * st, int call_options, const char *name)
{
int rc = 0;
xmlNode *data = NULL;
data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add(data, PCMK_XA_ID, name);
rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_remove_level_full(stonith_t *st, int options,
const char *node, const char *pattern,
const char *attr, const char *value, int level)
{
int rc = 0;
xmlNode *data = NULL;
CRM_CHECK(node || pattern || (attr && value), return -EINVAL);
data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
if (node) {
crm_xml_add(data, PCMK_XA_TARGET, node);
} else if (pattern) {
crm_xml_add(data, PCMK_XA_TARGET_PATTERN, pattern);
} else {
crm_xml_add(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
crm_xml_add(data, PCMK_XA_TARGET_VALUE, value);
}
crm_xml_add_int(data, PCMK_XA_INDEX, level);
rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_remove_level(stonith_t * st, int options, const char *node, int level)
{
return stonith_api_remove_level_full(st, options, node,
NULL, NULL, NULL, level);
}
/*!
* \internal
* \brief Create XML for fence topology level registration request
*
* \param[in] node If not NULL, target level by this node name
* \param[in] pattern If not NULL, target by node name using this regex
* \param[in] attr If not NULL, target by this node attribute
* \param[in] value If not NULL, target by this node attribute value
* \param[in] level Index number of level to register
* \param[in] device_list List of devices in level
*
* \return Newly allocated XML tree on success, NULL otherwise
*
* \note The caller should set only one of node, pattern or attr/value.
*/
xmlNode *
create_level_registration_xml(const char *node, const char *pattern,
const char *attr, const char *value,
int level, const stonith_key_value_t *device_list)
{
GString *list = NULL;
xmlNode *data;
CRM_CHECK(node || pattern || (attr && value), return NULL);
data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add_int(data, PCMK_XA_ID, level);
crm_xml_add_int(data, PCMK_XA_INDEX, level);
if (node) {
crm_xml_add(data, PCMK_XA_TARGET, node);
} else if (pattern) {
crm_xml_add(data, PCMK_XA_TARGET_PATTERN, pattern);
} else {
crm_xml_add(data, PCMK_XA_TARGET_ATTRIBUTE, attr);
crm_xml_add(data, PCMK_XA_TARGET_VALUE, value);
}
for (; device_list; device_list = device_list->next) {
pcmk__add_separated_word(&list, 1024, device_list->value, ",");
}
if (list != NULL) {
crm_xml_add(data, PCMK_XA_DEVICES, (const char *) list->str);
g_string_free(list, TRUE);
}
return data;
}
static int
stonith_api_register_level_full(stonith_t *st, int options, const char *node,
const char *pattern, const char *attr,
const char *value, int level,
const stonith_key_value_t *device_list)
{
int rc = 0;
xmlNode *data = create_level_registration_xml(node, pattern, attr, value,
level, device_list);
CRM_CHECK(data != NULL, return -EINVAL);
rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_register_level(stonith_t * st, int options, const char *node, int level,
const stonith_key_value_t * device_list)
{
return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL,
level, device_list);
}
static int
stonith_api_device_list(stonith_t *stonith, int call_options,
const char *namespace_s, stonith_key_value_t **devices,
int timeout)
{
int count = 0;
enum stonith_namespace ns = parse_namespace(namespace_s);
if (devices == NULL) {
crm_err("Parameter error: stonith_api_device_list");
return -EFAULT;
}
#if HAVE_STONITH_STONITH_H
// Include Linux-HA agents if requested
if ((ns == st_namespace_any) || (ns == st_namespace_lha)) {
count += stonith__list_lha_agents(devices);
}
#endif
// Include Red Hat agents if requested
if ((ns == st_namespace_any) || (ns == st_namespace_rhcs)) {
count += stonith__list_rhcs_agents(devices);
}
return count;
}
// See stonith_api_operations_t:metadata() documentation
static int
stonith_api_device_metadata(stonith_t *stonith, int call_options,
const char *agent, const char *namespace_s,
char **output, int timeout_sec)
{
/* By executing meta-data directly, we can get it from stonith_admin when
* the cluster is not running, which is important for higher-level tools.
*/
- enum stonith_namespace ns = stonith_get_namespace(agent, namespace_s);
+ enum stonith_namespace ns = stonith_get_namespace(agent, NULL);
if (timeout_sec <= 0) {
timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
crm_trace("Looking up metadata for %s agent %s", namespace_text(ns), agent);
switch (ns) {
case st_namespace_rhcs:
return stonith__rhcs_metadata(agent, timeout_sec, output);
#if HAVE_STONITH_STONITH_H
case st_namespace_lha:
return stonith__lha_metadata(agent, timeout_sec, output);
#endif
default:
crm_err("Can't get fence agent '%s' meta-data: No such agent",
agent);
break;
}
return -ENODEV;
}
static int
stonith_api_query(stonith_t * stonith, int call_options, const char *target,
stonith_key_value_t ** devices, int timeout)
{
int rc = 0, lpc = 0, max = 0;
xmlNode *data = NULL;
xmlNode *output = NULL;
xmlXPathObject *xpathObj = NULL;
CRM_CHECK(devices != NULL, return -EINVAL);
data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_ST_TARGET, target);
crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, PCMK_ACTION_OFF);
rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout);
if (rc < 0) {
return rc;
}
xpathObj = pcmk__xpath_search(output->doc, "//*[@" PCMK_XA_AGENT "]");
if (xpathObj) {
max = pcmk__xpath_num_results(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match != NULL) {
const char *match_id = crm_element_value(match, PCMK_XA_ID);
xmlChar *match_path = xmlGetNodePath(match);
crm_info("//*[@" PCMK_XA_AGENT "][%d] = %s", lpc, match_path);
free(match_path);
*devices = stonith__key_value_add(*devices, NULL, match_id);
}
}
xmlXPathFreeObject(xpathObj);
}
pcmk__xml_free(output);
pcmk__xml_free(data);
return max;
}
/*!
* \internal
* \brief Make a STONITH_OP_EXEC request
*
* \param[in,out] stonith Fencer connection
* \param[in] call_options Bitmask of \c stonith_call_options
* \param[in] id Fence device ID that request is for
* \param[in] action Agent action to request (list, status, monitor)
* \param[in] target Name of target node for requested action
* \param[in] timeout_sec Error if not completed within this many seconds
* \param[out] output Where to set agent output
*/
static int
stonith_api_call(stonith_t *stonith, int call_options, const char *id,
const char *action, const char *target, int timeout_sec,
xmlNode **output)
{
int rc = 0;
xmlNode *data = NULL;
data = pcmk__xe_create(NULL, PCMK__XE_ST_DEVICE_ID);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_ST_DEVICE_ID, id);
crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, action);
crm_xml_add(data, PCMK__XA_ST_TARGET, target);
rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output,
call_options, timeout_sec);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info,
int timeout)
{
int rc;
xmlNode *output = NULL;
rc = stonith_api_call(stonith, call_options, id, PCMK_ACTION_LIST, NULL,
timeout, &output);
if (output && list_info) {
const char *list_str;
list_str = crm_element_value(output, PCMK__XA_ST_OUTPUT);
if (list_str) {
*list_info = strdup(list_str);
}
}
if (output) {
pcmk__xml_free(output);
}
return rc;
}
static int
stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout)
{
return stonith_api_call(stonith, call_options, id, PCMK_ACTION_MONITOR,
NULL, timeout, NULL);
}
static int
stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port,
int timeout)
{
return stonith_api_call(stonith, call_options, id, PCMK_ACTION_STATUS, port,
timeout, NULL);
}
static int
stonith_api_fence_with_delay(stonith_t * stonith, int call_options, const char *node,
const char *action, int timeout, int tolerance, int delay)
{
int rc = 0;
xmlNode *data = NULL;
data = pcmk__xe_create(NULL, __func__);
crm_xml_add(data, PCMK__XA_ST_TARGET, node);
crm_xml_add(data, PCMK__XA_ST_DEVICE_ACTION, action);
crm_xml_add_int(data, PCMK__XA_ST_TIMEOUT, timeout);
crm_xml_add_int(data, PCMK__XA_ST_TOLERANCE, tolerance);
crm_xml_add_int(data, PCMK__XA_ST_DELAY, delay);
rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout);
pcmk__xml_free(data);
return rc;
}
static int
stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action,
int timeout, int tolerance)
{
return stonith_api_fence_with_delay(stonith, call_options, node, action,
timeout, tolerance, 0);
}
static int
stonith_api_confirm(stonith_t * stonith, int call_options, const char *target)
{
stonith__set_call_options(call_options, target, st_opt_manual_ack);
return stonith_api_fence(stonith, call_options, target, PCMK_ACTION_OFF, 0,
0);
}
static int
stonith_api_history(stonith_t * stonith, int call_options, const char *node,
stonith_history_t ** history, int timeout)
{
int rc = 0;
xmlNode *data = NULL;
xmlNode *output = NULL;
stonith_history_t *last = NULL;
*history = NULL;
if (node) {
data = pcmk__xe_create(NULL, __func__);
crm_xml_add(data, PCMK__XA_ST_TARGET, node);
}
stonith__set_call_options(call_options, node, st_opt_sync_call);
rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output,
call_options, timeout);
pcmk__xml_free(data);
if (rc == 0) {
xmlNode *op = NULL;
xmlNode *reply = pcmk__xpath_find_one(output->doc,
"//" PCMK__XE_ST_HISTORY,
LOG_NEVER);
for (op = pcmk__xe_first_child(reply, NULL, NULL, NULL); op != NULL;
op = pcmk__xe_next(op, NULL)) {
stonith_history_t *kvp;
long long completed;
long long completed_nsec = 0L;
kvp = pcmk__assert_alloc(1, sizeof(stonith_history_t));
kvp->target = crm_element_value_copy(op, PCMK__XA_ST_TARGET);
kvp->action = crm_element_value_copy(op, PCMK__XA_ST_DEVICE_ACTION);
kvp->origin = crm_element_value_copy(op, PCMK__XA_ST_ORIGIN);
kvp->delegate = crm_element_value_copy(op, PCMK__XA_ST_DELEGATE);
kvp->client = crm_element_value_copy(op, PCMK__XA_ST_CLIENTNAME);
crm_element_value_ll(op, PCMK__XA_ST_DATE, &completed);
kvp->completed = (time_t) completed;
crm_element_value_ll(op, PCMK__XA_ST_DATE_NSEC, &completed_nsec);
kvp->completed_nsec = completed_nsec;
crm_element_value_int(op, PCMK__XA_ST_STATE, &kvp->state);
kvp->exit_reason = crm_element_value_copy(op, PCMK_XA_EXIT_REASON);
if (last) {
last->next = kvp;
} else {
*history = kvp;
}
last = kvp;
}
}
pcmk__xml_free(output);
return rc;
}
/*!
* \internal
* \brief Free a list of fencing history objects and all members of each object
*
* \param[in,out] head Head of fencing history object list
*/
void
stonith__history_free(stonith_history_t *head)
{
/* @COMPAT Drop "next" member of stonith_history_t, use a GList or GSList,
* and use the appropriate free function (while ensuring the members get
* freed)
*/
while (head != NULL) {
stonith_history_t *next = head->next;
free(head->target);
free(head->action);
free(head->origin);
free(head->delegate);
free(head->client);
free(head->exit_reason);
free(head);
head = next;
}
}
static gint
stonithlib_GCompareFunc(gconstpointer a, gconstpointer b)
{
int rc = 0;
const stonith_notify_client_t *a_client = a;
const stonith_notify_client_t *b_client = b;
if (a_client->delete || b_client->delete) {
/* make entries marked for deletion not findable */
return -1;
}
CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
rc = strcmp(a_client->event, b_client->event);
if (rc == 0) {
if (a_client->notify == NULL || b_client->notify == NULL) {
return 0;
} else if (a_client->notify == b_client->notify) {
return 0;
} else if (((long)a_client->notify) < ((long)b_client->notify)) {
crm_err("callbacks for %s are not equal: %p vs. %p",
a_client->event, a_client->notify, b_client->notify);
return -1;
}
crm_err("callbacks for %s are not equal: %p vs. %p",
a_client->event, a_client->notify, b_client->notify);
return 1;
}
return rc;
}
xmlNode *
stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options)
{
xmlNode *op_msg = NULL;
CRM_CHECK(token != NULL, return NULL);
op_msg = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
crm_xml_add(op_msg, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
crm_xml_add(op_msg, PCMK__XA_ST_OP, op);
crm_xml_add_int(op_msg, PCMK__XA_ST_CALLID, call_id);
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(op_msg, PCMK__XA_ST_CALLOPT, call_options);
if (data != NULL) {
xmlNode *wrapper = pcmk__xe_create(op_msg, PCMK__XE_ST_CALLDATA);
pcmk__xml_copy(wrapper, data);
}
return op_msg;
}
static void
stonith_destroy_op_callback(gpointer data)
{
stonith_callback_client_t *blob = data;
if (blob->timer && blob->timer->ref > 0) {
g_source_remove(blob->timer->ref);
}
free(blob->timer);
free(blob);
}
static int
stonith_api_signoff(stonith_t * stonith)
{
stonith_private_t *native = stonith->st_private;
crm_debug("Disconnecting from the fencer");
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
native->ipc = NULL;
} else if (native->ipc) {
/* Not attached to mainloop */
crm_ipc_t *ipc = native->ipc;
native->ipc = NULL;
crm_ipc_close(ipc);
crm_ipc_destroy(ipc);
}
free(native->token); native->token = NULL;
stonith->state = stonith_disconnected;
return pcmk_ok;
}
static int
stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks)
{
stonith_private_t *private = stonith->st_private;
if (all_callbacks) {
private->op_callback = NULL;
g_hash_table_destroy(private->stonith_op_callback_table);
private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
} else if (call_id == 0) {
private->op_callback = NULL;
} else {
pcmk__intkey_table_remove(private->stonith_op_callback_table, call_id);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Invoke a (single) specified fence action callback
*
* \param[in,out] st Fencer API connection
* \param[in] call_id If positive, call ID of completed fence action,
* otherwise legacy return code for early failure
* \param[in,out] result Full result for action
* \param[in,out] userdata User data to pass to callback
* \param[in] callback Fence action callback to invoke
*/
static void
invoke_fence_action_callback(stonith_t *st, int call_id,
pcmk__action_result_t *result,
void *userdata,
void (*callback) (stonith_t *st,
stonith_callback_data_t *data))
{
stonith_callback_data_t data = { 0, };
data.call_id = call_id;
data.rc = pcmk_rc2legacy(stonith__result2rc(result));
data.userdata = userdata;
data.opaque = (void *) result;
callback(st, &data);
}
/*!
* \internal
* \brief Invoke any callbacks registered for a specified fence action result
*
* Given a fence action result from the fencer, invoke any callback registered
* for that action, as well as any global callback registered.
*
* \param[in,out] stonith Fencer API connection
* \param[in] msg If non-NULL, fencer reply
* \param[in] call_id If \p msg is NULL, call ID of action that timed out
*/
static void
invoke_registered_callbacks(stonith_t *stonith, const xmlNode *msg, int call_id)
{
stonith_private_t *private = NULL;
stonith_callback_client_t *cb_info = NULL;
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
CRM_CHECK(stonith != NULL, return);
CRM_CHECK(stonith->st_private != NULL, return);
private = stonith->st_private;
if (msg == NULL) {
// Fencer didn't reply in time
pcmk__set_result(&result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT,
"Fencer accepted request but did not reply in time");
CRM_LOG_ASSERT(call_id > 0);
} else {
// We have the fencer reply
if ((crm_element_value_int(msg, PCMK__XA_ST_CALLID, &call_id) != 0)
|| (call_id <= 0)) {
crm_log_xml_warn(msg, "Bad fencer reply");
}
stonith__xe_get_result(msg, &result);
}
if (call_id > 0) {
cb_info = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
call_id);
}
if ((cb_info != NULL) && (cb_info->callback != NULL)
&& (pcmk__result_ok(&result) || !(cb_info->only_success))) {
crm_trace("Invoking callback %s for call %d",
pcmk__s(cb_info->id, "without ID"), call_id);
invoke_fence_action_callback(stonith, call_id, &result,
cb_info->user_data, cb_info->callback);
} else if ((private->op_callback == NULL) && !pcmk__result_ok(&result)) {
crm_warn("Fencing action without registered callback failed: %d (%s%s%s)",
result.exit_status,
pcmk_exec_status_str(result.execution_status),
((result.exit_reason == NULL)? "" : ": "),
((result.exit_reason == NULL)? "" : result.exit_reason));
crm_log_xml_debug(msg, "Failed fence update");
}
if (private->op_callback != NULL) {
crm_trace("Invoking global callback for call %d", call_id);
invoke_fence_action_callback(stonith, call_id, &result, NULL,
private->op_callback);
}
if (cb_info != NULL) {
stonith_api_del_callback(stonith, call_id, FALSE);
}
pcmk__reset_result(&result);
}
static gboolean
stonith_async_timeout_handler(gpointer data)
{
struct timer_rec_s *timer = data;
crm_err("Async call %d timed out after %dms", timer->call_id, timer->timeout);
invoke_registered_callbacks(timer->stonith, NULL, timer->call_id);
/* Always return TRUE, never remove the handler
* We do that in stonith_del_callback()
*/
return TRUE;
}
static void
set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id,
int timeout)
{
struct timer_rec_s *async_timer = callback->timer;
if (timeout <= 0) {
return;
}
if (!async_timer) {
async_timer = pcmk__assert_alloc(1, sizeof(struct timer_rec_s));
callback->timer = async_timer;
}
async_timer->stonith = stonith;
async_timer->call_id = call_id;
/* Allow a fair bit of grace to allow the server to tell us of a timeout
* This is only a fallback
*/
async_timer->timeout = (timeout + 60) * 1000;
if (async_timer->ref) {
g_source_remove(async_timer->ref);
}
async_timer->ref =
pcmk__create_timer(async_timer->timeout, stonith_async_timeout_handler,
async_timer);
}
static void
update_callback_timeout(int call_id, int timeout, stonith_t * st)
{
stonith_callback_client_t *callback = NULL;
stonith_private_t *private = st->st_private;
callback = pcmk__intkey_table_lookup(private->stonith_op_callback_table,
call_id);
if (!callback || !callback->allow_timeout_updates) {
return;
}
set_callback_timeout(callback, st, call_id, timeout);
}
static int
stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata)
{
const char *type = NULL;
struct notify_blob_s blob;
stonith_t *st = userdata;
stonith_private_t *private = NULL;
pcmk__assert(st != NULL);
private = st->st_private;
blob.stonith = st;
blob.xml = pcmk__xml_parse(buffer);
if (blob.xml == NULL) {
crm_warn("Received malformed message from fencer: %s", buffer);
return 0;
}
/* do callbacks */
type = crm_element_value(blob.xml, PCMK__XA_T);
crm_trace("Activating %s callbacks...", type);
if (pcmk__str_eq(type, PCMK__VALUE_STONITH_NG, pcmk__str_none)) {
invoke_registered_callbacks(st, blob.xml, 0);
} else if (pcmk__str_eq(type, PCMK__VALUE_ST_NOTIFY, pcmk__str_none)) {
foreach_notify_entry(private, stonith_send_notification, &blob);
} else if (pcmk__str_eq(type, PCMK__VALUE_ST_ASYNC_TIMEOUT_VALUE,
pcmk__str_none)) {
int call_id = 0;
int timeout = 0;
crm_element_value_int(blob.xml, PCMK__XA_ST_TIMEOUT, &timeout);
crm_element_value_int(blob.xml, PCMK__XA_ST_CALLID, &call_id);
update_callback_timeout(call_id, timeout, st);
} else {
crm_err("Unknown message type: %s", type);
crm_log_xml_warn(blob.xml, "BadReply");
}
pcmk__xml_free(blob.xml);
return 1;
}
static int
stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd)
{
int rc = pcmk_ok;
stonith_private_t *native = NULL;
const char *display_name = name? name : "client";
struct ipc_client_callbacks st_callbacks = {
.dispatch = stonith_dispatch_internal,
.destroy = stonith_connection_destroy
};
CRM_CHECK(stonith != NULL, return -EINVAL);
native = stonith->st_private;
pcmk__assert(native != NULL);
crm_debug("Attempting fencer connection by %s with%s mainloop",
display_name, (stonith_fd? "out" : ""));
stonith->state = stonith_connected_command;
if (stonith_fd) {
/* No mainloop */
native->ipc = crm_ipc_new("stonith-ng", 0);
if (native->ipc != NULL) {
rc = pcmk__connect_generic_ipc(native->ipc);
if (rc == pcmk_rc_ok) {
rc = pcmk__ipc_fd(native->ipc, stonith_fd);
if (rc != pcmk_rc_ok) {
crm_debug("Couldn't get file descriptor for IPC: %s",
pcmk_rc_str(rc));
}
}
if (rc != pcmk_rc_ok) {
crm_ipc_close(native->ipc);
crm_ipc_destroy(native->ipc);
native->ipc = NULL;
}
}
} else {
/* With mainloop */
native->source =
mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks);
native->ipc = mainloop_get_ipc_client(native->source);
}
if (native->ipc == NULL) {
rc = -ENOTCONN;
} else {
xmlNode *reply = NULL;
xmlNode *hello = pcmk__xe_create(NULL, PCMK__XE_STONITH_COMMAND);
crm_xml_add(hello, PCMK__XA_T, PCMK__VALUE_STONITH_NG);
crm_xml_add(hello, PCMK__XA_ST_OP, CRM_OP_REGISTER);
crm_xml_add(hello, PCMK__XA_ST_CLIENTNAME, name);
rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply);
if (rc < 0) {
crm_debug("Couldn't register with the fencer: %s "
QB_XS " rc=%d", pcmk_strerror(rc), rc);
rc = -ECOMM;
} else if (reply == NULL) {
crm_debug("Couldn't register with the fencer: no reply");
rc = -EPROTO;
} else {
const char *msg_type = crm_element_value(reply, PCMK__XA_ST_OP);
native->token = crm_element_value_copy(reply, PCMK__XA_ST_CLIENTID);
if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_none)) {
crm_debug("Couldn't register with the fencer: invalid reply type '%s'",
(msg_type? msg_type : "(missing)"));
crm_log_xml_debug(reply, "Invalid fencer reply");
rc = -EPROTO;
} else if (native->token == NULL) {
crm_debug("Couldn't register with the fencer: no token in reply");
crm_log_xml_debug(reply, "Invalid fencer reply");
rc = -EPROTO;
} else {
crm_debug("Connection to fencer by %s succeeded (registration token: %s)",
display_name, native->token);
rc = pcmk_ok;
}
}
pcmk__xml_free(reply);
pcmk__xml_free(hello);
}
if (rc != pcmk_ok) {
crm_debug("Connection attempt to fencer by %s failed: %s "
QB_XS " rc=%d", display_name, pcmk_strerror(rc), rc);
stonith->cmds->disconnect(stonith);
}
return rc;
}
static int
stonith_set_notification(stonith_t * stonith, const char *callback, int enabled)
{
int rc = pcmk_ok;
xmlNode *notify_msg = pcmk__xe_create(NULL, __func__);
stonith_private_t *native = stonith->st_private;
if (stonith->state != stonith_disconnected) {
crm_xml_add(notify_msg, PCMK__XA_ST_OP, STONITH_OP_NOTIFY);
if (enabled) {
crm_xml_add(notify_msg, PCMK__XA_ST_NOTIFY_ACTIVATE, callback);
} else {
crm_xml_add(notify_msg, PCMK__XA_ST_NOTIFY_DEACTIVATE, callback);
}
rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't register for fencing notifications: %d", rc);
rc = -ECOMM;
} else {
rc = pcmk_ok;
}
}
pcmk__xml_free(notify_msg);
return rc;
}
static int
stonith_api_add_notification(stonith_t * stonith, const char *event,
void (*callback) (stonith_t * stonith, stonith_event_t * e))
{
GList *list_item = NULL;
stonith_notify_client_t *new_client = NULL;
stonith_private_t *private = NULL;
private = stonith->st_private;
crm_trace("Adding callback for %s events (%d)", event, g_list_length(private->notify_list));
new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
new_client->event = event;
new_client->notify = callback;
list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
if (list_item != NULL) {
crm_warn("Callback already present");
free(new_client);
return -ENOTUNIQ;
} else {
private->notify_list = g_list_append(private->notify_list, new_client);
stonith_set_notification(stonith, event, 1);
crm_trace("Callback added (%d)", g_list_length(private->notify_list));
}
return pcmk_ok;
}
static void
del_notify_entry(gpointer data, gpointer user_data)
{
stonith_notify_client_t *entry = data;
stonith_t * stonith = user_data;
if (!entry->delete) {
crm_debug("Removing callback for %s events", entry->event);
stonith_api_del_notification(stonith, entry->event);
}
}
static int
stonith_api_del_notification(stonith_t * stonith, const char *event)
{
GList *list_item = NULL;
stonith_notify_client_t *new_client = NULL;
stonith_private_t *private = stonith->st_private;
if (event == NULL) {
foreach_notify_entry(private, del_notify_entry, stonith);
crm_trace("Removed callback");
return pcmk_ok;
}
crm_debug("Removing callback for %s events", event);
new_client = pcmk__assert_alloc(1, sizeof(stonith_notify_client_t));
new_client->event = event;
new_client->notify = NULL;
list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
stonith_set_notification(stonith, event, 0);
if (list_item != NULL) {
stonith_notify_client_t *list_client = list_item->data;
if (private->notify_refcnt) {
list_client->delete = TRUE;
private->notify_deletes = TRUE;
} else {
private->notify_list = g_list_remove(private->notify_list, list_client);
free(list_client);
}
crm_trace("Removed callback");
} else {
crm_trace("Callback not present");
}
free(new_client);
return pcmk_ok;
}
static int
stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options,
void *user_data, const char *callback_name,
void (*callback) (stonith_t * st, stonith_callback_data_t * data))
{
stonith_callback_client_t *blob = NULL;
stonith_private_t *private = NULL;
CRM_CHECK(stonith != NULL, return -EINVAL);
CRM_CHECK(stonith->st_private != NULL, return -EINVAL);
private = stonith->st_private;
if (call_id == 0) { // Add global callback
private->op_callback = callback;
} else if (call_id < 0) { // Call failed immediately, so call callback now
if (!(options & st_opt_report_only_success)) {
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
crm_trace("Call failed, calling %s: %s", callback_name, pcmk_strerror(call_id));
pcmk__set_result(&result, CRM_EX_ERROR,
stonith__legacy2status(call_id), NULL);
invoke_fence_action_callback(stonith, call_id, &result,
user_data, callback);
} else {
crm_warn("Fencer call failed: %s", pcmk_strerror(call_id));
}
return FALSE;
}
blob = pcmk__assert_alloc(1, sizeof(stonith_callback_client_t));
blob->id = callback_name;
blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE;
blob->user_data = user_data;
blob->callback = callback;
blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE;
if (timeout > 0) {
set_callback_timeout(blob, stonith, call_id, timeout);
}
pcmk__intkey_table_insert(private->stonith_op_callback_table, call_id,
blob);
crm_trace("Added callback to %s for call %d", callback_name, call_id);
return TRUE;
}
/*!
* \internal
* \brief Get the data section of a fencer notification
*
* \param[in] msg Notification XML
* \param[in] ntype Notification type
*/
static xmlNode *
get_event_data_xml(xmlNode *msg, const char *ntype)
{
char *data_addr = crm_strdup_printf("//%s", ntype);
xmlNode *data = pcmk__xpath_find_one(msg->doc, data_addr, LOG_DEBUG);
free(data_addr);
return data;
}
/*
<notify t="st_notify" subt="st_device_register" st_op="st_device_register" st_rc="0" >
<st_calldata >
<stonith_command t="stonith-ng" st_async_id="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_op="st_device_register" st_callid="2" st_callopt="4096" st_timeout="0" st_clientid="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_clientname="cts-fence-helper" >
<st_calldata >
<st_device_id id="test-id" origin="create_device_registration_xml" agent="fence_virsh" namespace="stonith-ng" >
<attributes ipaddr="localhost" pcmk-portmal="some-host=pcmk-1 pcmk-3=3,4" login="root" identity_file="/root/.ssh/id_dsa" />
</st_device_id>
</st_calldata>
</stonith_command>
</st_calldata>
</notify>
<notify t="st_notify" subt="st_notify_fence" st_op="st_notify_fence" st_rc="0" >
<st_calldata >
<st_notify_fence st_rc="0" st_target="some-host" st_op="st_fence" st_delegate="test-id" st_origin="61dd7759-e229-4be7-b1f8-ef49dd14d9f0" />
</st_calldata>
</notify>
*/
static stonith_event_t *
xml_to_event(xmlNode *msg)
{
stonith_event_t *event = pcmk__assert_alloc(1, sizeof(stonith_event_t));
struct event_private *event_private = NULL;
event->opaque = pcmk__assert_alloc(1, sizeof(struct event_private));
event_private = (struct event_private *) event->opaque;
crm_log_xml_trace(msg, "stonith_notify");
// All notification types have the operation result and notification subtype
stonith__xe_get_result(msg, &event_private->result);
event->operation = crm_element_value_copy(msg, PCMK__XA_ST_OP);
// @COMPAT The API originally provided the result as a legacy return code
event->result = pcmk_rc2legacy(stonith__result2rc(&event_private->result));
// Some notification subtypes have additional information
if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_FENCE,
pcmk__str_none)) {
xmlNode *data = get_event_data_xml(msg, event->operation);
if (data == NULL) {
crm_err("No data for %s event", event->operation);
crm_log_xml_notice(msg, "BadEvent");
} else {
event->origin = crm_element_value_copy(data, PCMK__XA_ST_ORIGIN);
event->action = crm_element_value_copy(data,
PCMK__XA_ST_DEVICE_ACTION);
event->target = crm_element_value_copy(data, PCMK__XA_ST_TARGET);
event->executioner = crm_element_value_copy(data,
PCMK__XA_ST_DELEGATE);
event->id = crm_element_value_copy(data, PCMK__XA_ST_REMOTE_OP);
event->client_origin =
crm_element_value_copy(data, PCMK__XA_ST_CLIENTNAME);
event->device = crm_element_value_copy(data, PCMK__XA_ST_DEVICE_ID);
}
} else if (pcmk__str_any_of(event->operation,
STONITH_OP_DEVICE_ADD, STONITH_OP_DEVICE_DEL,
STONITH_OP_LEVEL_ADD, STONITH_OP_LEVEL_DEL,
NULL)) {
xmlNode *data = get_event_data_xml(msg, event->operation);
if (data == NULL) {
crm_err("No data for %s event", event->operation);
crm_log_xml_notice(msg, "BadEvent");
} else {
event->device = crm_element_value_copy(data, PCMK__XA_ST_DEVICE_ID);
}
}
return event;
}
static void
event_free(stonith_event_t * event)
{
struct event_private *event_private = event->opaque;
free(event->id);
free(event->operation);
free(event->origin);
free(event->action);
free(event->target);
free(event->executioner);
free(event->device);
free(event->client_origin);
pcmk__reset_result(&event_private->result);
free(event->opaque);
free(event);
}
static void
stonith_send_notification(gpointer data, gpointer user_data)
{
struct notify_blob_s *blob = user_data;
stonith_notify_client_t *entry = data;
stonith_event_t *st_event = NULL;
const char *event = NULL;
if (blob->xml == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(blob->xml, PCMK__XA_SUBT);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->delete) {
crm_trace("Skipping callback - marked for deletion");
return;
} else if (entry->notify == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (!pcmk__str_eq(entry->event, event, pcmk__str_none)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
st_event = xml_to_event(blob->xml);
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->notify(blob->stonith, st_event);
crm_trace("Callback invoked...");
event_free(st_event);
}
/*!
* \internal
* \brief Create and send an API request
*
* \param[in,out] stonith Stonith connection
* \param[in] op API operation to request
* \param[in] data Data to attach to request
* \param[out] output_data If not NULL, will be set to reply if synchronous
* \param[in] call_options Bitmask of stonith_call_options to use
* \param[in] timeout Error if not completed within this many seconds
*
* \return pcmk_ok (for synchronous requests) or positive call ID
* (for asynchronous requests) on success, -errno otherwise
*/
static int
stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data,
int call_options, int timeout)
{
int rc = 0;
int reply_id = -1;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
stonith_private_t *native = NULL;
pcmk__assert((stonith != NULL) && (stonith->st_private != NULL)
&& (op != NULL));
native = stonith->st_private;
if (output_data != NULL) {
*output_data = NULL;
}
if ((stonith->state == stonith_disconnected) || (native->token == NULL)) {
return -ENOTCONN;
}
/* Increment the call ID, which must be positive to avoid conflicting with
* error codes. This shouldn't be a problem unless the client mucked with
* it or the counter wrapped around.
*/
stonith->call_id++;
if (stonith->call_id < 1) {
stonith->call_id = 1;
}
op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options);
if (op_msg == NULL) {
return -EINVAL;
}
crm_xml_add_int(op_msg, PCMK__XA_ST_TIMEOUT, timeout);
crm_trace("Sending %s message to fencer with timeout %ds", op, timeout);
if (data) {
const char *delay_s = crm_element_value(data, PCMK__XA_ST_DELAY);
if (delay_s) {
crm_xml_add(op_msg, PCMK__XA_ST_DELAY, delay_s);
}
}
{
enum crm_ipc_flags ipc_flags = crm_ipc_flags_none;
if (call_options & st_opt_sync_call) {
pcmk__set_ipc_flags(ipc_flags, "stonith command",
crm_ipc_client_response);
}
rc = crm_ipc_send(native->ipc, op_msg, ipc_flags,
1000 * (timeout + 60), &op_reply);
}
pcmk__xml_free(op_msg);
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%ds): %d", op, timeout, rc);
rc = -ECOMM;
goto done;
}
crm_log_xml_trace(op_reply, "Reply");
if (!(call_options & st_opt_sync_call)) {
crm_trace("Async call %d, returning", stonith->call_id);
pcmk__xml_free(op_reply);
return stonith->call_id;
}
crm_element_value_int(op_reply, PCMK__XA_ST_CALLID, &reply_id);
if (reply_id == stonith->call_id) {
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
crm_trace("Synchronous reply %d received", reply_id);
stonith__xe_get_result(op_reply, &result);
rc = pcmk_rc2legacy(stonith__result2rc(&result));
pcmk__reset_result(&result);
if ((call_options & st_opt_discard_reply) || output_data == NULL) {
crm_trace("Discarding reply");
} else {
*output_data = op_reply;
op_reply = NULL; /* Prevent subsequent free */
}
} else if (reply_id <= 0) {
crm_err("Received bad reply: No id set");
crm_log_xml_err(op_reply, "Bad reply");
pcmk__xml_free(op_reply);
op_reply = NULL;
rc = -ENOMSG;
} else {
crm_err("Received bad reply: %d (wanted %d)", reply_id, stonith->call_id);
crm_log_xml_err(op_reply, "Old reply");
pcmk__xml_free(op_reply);
op_reply = NULL;
rc = -ENOMSG;
}
done:
if (!crm_ipc_connected(native->ipc)) {
crm_err("Fencer disconnected");
free(native->token); native->token = NULL;
stonith->state = stonith_disconnected;
}
pcmk__xml_free(op_reply);
return rc;
}
/*!
* \internal
* \brief Process IPC messages for a fencer API connection
*
* This is used for testing purposes in scenarios that don't use a mainloop to
* dispatch messages automatically.
*
* \param[in,out] stonith_api Fencer API connetion object
*
* \return Standard Pacemaker return code
*/
int
stonith__api_dispatch(stonith_t *stonith_api)
{
stonith_private_t *private = NULL;
pcmk__assert(stonith_api != NULL);
private = stonith_api->st_private;
while (crm_ipc_ready(private->ipc)) {
if (crm_ipc_read(private->ipc) > 0) {
const char *msg = crm_ipc_buffer(private->ipc);
stonith_dispatch_internal(msg, strlen(msg), stonith_api);
}
if (!crm_ipc_connected(private->ipc)) {
crm_err("Connection closed");
return ENOTCONN;
}
}
return pcmk_rc_ok;
}
static int
free_stonith_api(stonith_t *stonith)
{
int rc = pcmk_ok;
crm_trace("Destroying %p", stonith);
if (stonith->state != stonith_disconnected) {
crm_trace("Unregistering notifications and disconnecting %p first",
stonith);
stonith->cmds->remove_notification(stonith, NULL);
rc = stonith->cmds->disconnect(stonith);
}
if (stonith->state == stonith_disconnected) {
stonith_private_t *private = stonith->st_private;
crm_trace("Removing %d callbacks", g_hash_table_size(private->stonith_op_callback_table));
g_hash_table_destroy(private->stonith_op_callback_table);
crm_trace("Destroying %d notification clients", g_list_length(private->notify_list));
g_list_free_full(private->notify_list, free);
free(stonith->st_private);
free(stonith->cmds);
free(stonith);
} else {
crm_err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc), rc);
}
return rc;
}
static gboolean
is_stonith_param(gpointer key, gpointer value, gpointer user_data)
{
return pcmk_stonith_param(key);
}
int
stonith__validate(stonith_t *st, int call_options, const char *rsc_id,
const char *agent, GHashTable *params, int timeout_sec,
char **output, char **error_output)
{
int rc = pcmk_rc_ok;
/* Use a dummy node name in case the agent requires a target. We assume the
* actual target doesn't matter for validation purposes (if in practice,
* that is incorrect, we will need to allow the caller to pass the target).
*/
const char *target = "node1";
char *host_arg = NULL;
if (params != NULL) {
host_arg = pcmk__str_copy(g_hash_table_lookup(params, PCMK_STONITH_HOST_ARGUMENT));
/* Remove special stonith params from the table before doing anything else */
g_hash_table_foreach_remove(params, is_stonith_param, NULL);
}
#if PCMK__ENABLE_CIBSECRETS
rc = pcmk__substitute_secrets(rsc_id, params);
if (rc != pcmk_rc_ok) {
crm_warn("Could not replace secret parameters for validation of %s: %s",
agent, pcmk_rc_str(rc));
// rc is standard return value, don't return it in this function
}
#endif
if (output) {
*output = NULL;
}
if (error_output) {
*error_output = NULL;
}
if (timeout_sec <= 0) {
timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
switch (stonith_get_namespace(agent, NULL)) {
case st_namespace_rhcs:
rc = stonith__rhcs_validate(st, call_options, target, agent,
params, host_arg, timeout_sec,
output, error_output);
rc = pcmk_legacy2rc(rc);
break;
#if HAVE_STONITH_STONITH_H
case st_namespace_lha:
rc = stonith__lha_validate(st, call_options, target, agent,
params, timeout_sec, output,
error_output);
rc = pcmk_legacy2rc(rc);
break;
#endif
case st_namespace_invalid:
errno = ENOENT;
rc = errno;
if (error_output) {
*error_output = crm_strdup_printf("Agent %s not found", agent);
} else {
crm_err("Agent %s not found", agent);
}
break;
default:
errno = EOPNOTSUPP;
rc = errno;
if (error_output) {
*error_output = crm_strdup_printf("Agent %s does not support validation",
agent);
} else {
crm_err("Agent %s does not support validation", agent);
}
break;
}
free(host_arg);
return rc;
}
static int
stonith_api_validate(stonith_t *st, int call_options, const char *rsc_id,
const char *namespace_s, const char *agent,
const stonith_key_value_t *params, int timeout_sec,
char **output, char **error_output)
{
/* Validation should be done directly via the agent, so we can get it from
* stonith_admin when the cluster is not running, which is important for
* higher-level tools.
*/
int rc = pcmk_ok;
GHashTable *params_table = pcmk__strkey_table(free, free);
// Convert parameter list to a hash table
for (; params; params = params->next) {
if (!pcmk_stonith_param(params->key)) {
pcmk__insert_dup(params_table, params->key, params->value);
}
}
rc = stonith__validate(st, call_options, rsc_id, agent, params_table,
timeout_sec, output, error_output);
g_hash_table_destroy(params_table);
return rc;
}
/*!
* \internal
* \brief Create a new fencer API connection object
*
* \return Newly allocated fencer API connection object, or \c NULL on
* allocation failure
*/
stonith_t *
stonith__api_new(void)
{
stonith_t *new_stonith = NULL;
stonith_private_t *private = NULL;
new_stonith = calloc(1, sizeof(stonith_t));
if (new_stonith == NULL) {
return NULL;
}
private = calloc(1, sizeof(stonith_private_t));
if (private == NULL) {
free(new_stonith);
return NULL;
}
new_stonith->st_private = private;
private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback);
private->notify_list = NULL;
private->notify_refcnt = 0;
private->notify_deletes = FALSE;
new_stonith->call_id = 1;
new_stonith->state = stonith_disconnected;
new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t));
if (new_stonith->cmds == NULL) {
free(new_stonith->st_private);
free(new_stonith);
return NULL;
}
new_stonith->cmds->free = free_stonith_api;
new_stonith->cmds->connect = stonith_api_signon;
new_stonith->cmds->disconnect = stonith_api_signoff;
new_stonith->cmds->list = stonith_api_list;
new_stonith->cmds->monitor = stonith_api_monitor;
new_stonith->cmds->status = stonith_api_status;
new_stonith->cmds->fence = stonith_api_fence;
new_stonith->cmds->fence_with_delay = stonith_api_fence_with_delay;
new_stonith->cmds->confirm = stonith_api_confirm;
new_stonith->cmds->history = stonith_api_history;
new_stonith->cmds->list_agents = stonith_api_device_list;
new_stonith->cmds->metadata = stonith_api_device_metadata;
new_stonith->cmds->query = stonith_api_query;
new_stonith->cmds->remove_device = stonith_api_remove_device;
new_stonith->cmds->register_device = stonith_api_register_device;
new_stonith->cmds->remove_level = stonith_api_remove_level;
new_stonith->cmds->remove_level_full = stonith_api_remove_level_full;
new_stonith->cmds->register_level = stonith_api_register_level;
new_stonith->cmds->register_level_full = stonith_api_register_level_full;
new_stonith->cmds->remove_callback = stonith_api_del_callback;
new_stonith->cmds->register_callback = stonith_api_add_callback;
new_stonith->cmds->remove_notification = stonith_api_del_notification;
new_stonith->cmds->register_notification = stonith_api_add_notification;
new_stonith->cmds->validate = stonith_api_validate;
return new_stonith;
}
/*!
* \internal
* \brief Free a fencer API connection object
*
* \param[in,out] stonith_api Fencer API connection object
*/
void
stonith__api_free(stonith_t *stonith_api)
{
crm_trace("Destroying %p", stonith_api);
if (stonith_api != NULL) {
stonith_api->cmds->free(stonith_api);
}
}
/*!
* \internal
* \brief Connect to the fencer, retrying on failure
*
* \param[in,out] stonith Fencer API connection object
* \param[in] name Client name to use with fencer
* \param[in] max_attempts Maximum number of attempts
*
* \return \c pcmk_rc_ok on success, or result of last attempt otherwise
*/
int
stonith__api_connect_retry(stonith_t *stonith_api, const char *name,
int max_attempts)
{
int rc = EINVAL; // if max_attempts is not positive
for (int attempt = 1; attempt <= max_attempts; attempt++) {
rc = stonith_api->cmds->connect(stonith_api, name, NULL);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
return rc;
}
if (attempt < max_attempts) {
crm_notice("Fencer connection attempt %d of %d failed "
"(retrying in 2s): %s " QB_XS " rc=%d",
attempt, max_attempts, pcmk_rc_str(rc), rc);
sleep(2);
}
}
crm_notice("Could not connect to fencer: %s " QB_XS " rc=%d",
pcmk_rc_str(rc), rc);
return rc;
}
/*!
* \internal
* \brief Append a newly allocated STONITH key-value pair to a list
*
* \param[in,out] head Head of key-value pair list (\c NULL for new list)
* \param[in] key Key to add
* \param[in] value Value to add
*
* \return Head of appended-to list (equal to \p head if \p head is not \c NULL)
* \note The caller is responsible for freeing the return value using
* \c stonith__key_value_freeall().
*/
stonith_key_value_t *
stonith__key_value_add(stonith_key_value_t *head, const char *key,
const char *value)
{
/* @COMPAT Replace this function with pcmk_prepend_nvpair(), and reverse the
* list when finished adding to it; or with a hash table where order does
* not matter
*/
stonith_key_value_t *pair = pcmk__assert_alloc(1,
sizeof(stonith_key_value_t));
pair->key = pcmk__str_copy(key);
pair->value = pcmk__str_copy(value);
if (head != NULL) {
stonith_key_value_t *end = head;
for (; end->next != NULL; end = end->next);
end->next = pair;
} else {
head = pair;
}
return head;
}
/*!
* \internal
* \brief Free all items in a \c stonith_key_value_t list
*
* This means freeing the list itself with all of its nodes. Keys and values may
* be freed depending on arguments.
*
* \param[in,out] head Head of list
* \param[in] keys If \c true, free all keys
* \param[in] values If \c true, free all values
*/
void
stonith__key_value_freeall(stonith_key_value_t *head, bool keys, bool values)
{
while (head != NULL) {
stonith_key_value_t *next = head->next;
if (keys) {
free(head->key);
}
if (values) {
free(head->value);
}
free(head);
head = next;
}
}
#define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON)
#define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __func__, args)
int
stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off)
{
int rc = pcmk_ok;
stonith_t *st = stonith__api_new();
const char *action = off? PCMK_ACTION_OFF : PCMK_ACTION_REBOOT;
api_log_open();
if (st == NULL) {
api_log(LOG_ERR, "API initialization failed, could not kick (%s) node %u/%s",
action, nodeid, uname);
return -EPROTO;
}
rc = st->cmds->connect(st, "stonith-api", NULL);
if (rc != pcmk_ok) {
api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)",
action, nodeid, uname, pcmk_strerror(rc), rc);
} else {
char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
int opts = 0;
stonith__set_call_options(opts, name,
st_opt_sync_call|st_opt_allow_self_fencing);
if ((uname == NULL) && (nodeid > 0)) {
stonith__set_call_options(opts, name, st_opt_cs_nodeid);
}
rc = st->cmds->fence(st, opts, name, action, timeout, 0);
free(name);
if (rc != pcmk_ok) {
api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)",
action, nodeid, uname, pcmk_strerror(rc), rc);
} else {
api_log(LOG_NOTICE, "Node %u/%s kicked: %s", nodeid, uname, action);
}
}
stonith__api_free(st);
return rc;
}
time_t
stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress)
{
int rc = pcmk_ok;
time_t when = 0;
stonith_t *st = stonith__api_new();
stonith_history_t *history = NULL, *hp = NULL;
if (st == NULL) {
api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: "
"API initialization failed", nodeid, uname);
return when;
}
rc = st->cmds->connect(st, "stonith-api", NULL);
if (rc != pcmk_ok) {
api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc);
} else {
int entries = 0;
int progress = 0;
int completed = 0;
int opts = 0;
char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname);
stonith__set_call_options(opts, name, st_opt_sync_call);
if ((uname == NULL) && (nodeid > 0)) {
stonith__set_call_options(opts, name, st_opt_cs_nodeid);
}
rc = st->cmds->history(st, opts, name, &history, 120);
free(name);
for (hp = history; hp; hp = hp->next) {
entries++;
if (in_progress) {
progress++;
if (hp->state != st_done && hp->state != st_failed) {
when = time(NULL);
}
} else if (hp->state == st_done) {
completed++;
if (hp->completed > when) {
when = hp->completed;
}
}
}
stonith__history_free(history);
if(rc == pcmk_ok) {
api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed);
} else {
api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc);
}
}
stonith__api_free(st);
if(when) {
api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when);
}
return when;
}
/*!
* \internal
* \brief Check whether a fence agent with a given name exists
*
* \param[in] name Agent name
*
* \retval \c true If a fence agent named \p name exists
* \retval \c false Otherwise
*/
bool
stonith__agent_exists(const char *name)
{
stonith_t *stonith_api = NULL;
stonith_key_value_t *agents = NULL;
bool rc = false;
if (name == NULL) {
return false;
}
stonith_api = stonith__api_new();
if (stonith_api == NULL) {
crm_err("Could not list fence agents: API memory allocation failed");
return false;
}
// The list_agents method ignores its timeout argument
stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL, &agents,
0);
for (const stonith_key_value_t *iter = agents; iter != NULL;
iter = iter->next) {
if (pcmk__str_eq(iter->value, name, pcmk__str_none)) {
rc = true;
break;
}
}
stonith__key_value_freeall(agents, true, true);
stonith__api_free(stonith_api);
return rc;
}
/*!
* \internal
* \brief Parse a target name from one line of a target list string
*
* \param[in] line One line of a target list string
* \param[in] len String length of line
* \param[in,out] output List to add newly allocated target name to
*/
static void
parse_list_line(const char *line, int len, GList **output)
{
size_t i = 0;
size_t entry_start = 0;
/* Skip complaints about additional parameters device doesn't understand
*
* @TODO Document or eliminate the implied restriction of target names
*/
if (strstr(line, "invalid") || strstr(line, "variable")) {
crm_debug("Skipping list output line: %s", line);
return;
}
// Process line content, character by character
for (i = 0; i <= len; i++) {
if (isspace(line[i]) || (line[i] == ',') || (line[i] == ';')
|| (line[i] == '\0')) {
// We've found a separator (i.e. the end of an entry)
int rc = 0;
char *entry = NULL;
if (i == entry_start) {
// Skip leading and sequential separators
entry_start = i + 1;
continue;
}
entry = pcmk__assert_alloc(i - entry_start + 1, sizeof(char));
/* Read entry, stopping at first separator
*
* @TODO Document or eliminate these character restrictions
*/
rc = sscanf(line + entry_start, "%[a-zA-Z0-9_-.]", entry);
if (rc != 1) {
crm_warn("Could not parse list output entry: %s "
QB_XS " entry_start=%d position=%d",
line + entry_start, entry_start, i);
free(entry);
} else if (pcmk__strcase_any_of(entry, PCMK_ACTION_ON,
PCMK_ACTION_OFF, NULL)) {
/* Some agents print the target status in the list output,
* though none are known now (the separate list-status command
* is used for this, but it can also print "UNKNOWN"). To handle
* this possibility, skip such entries.
*
* @TODO Document or eliminate the implied restriction of target
* names.
*/
free(entry);
} else {
// We have a valid entry
*output = g_list_append(*output, entry);
}
entry_start = i + 1;
}
}
}
/*!
* \internal
* \brief Parse a list of targets from a string
*
* \param[in] list_output Target list as a string
*
* \return List of target names
* \note The target list string format is flexible, to allow for user-specified
* lists such pcmk_host_list and the output of an agent's list action
* (whether direct or via the API, which escapes newlines). There may be
* multiple lines, separated by either a newline or an escaped newline
* (backslash n). Each line may have one or more target names, separated
* by any combination of whitespace, commas, and semi-colons. Lines
* containing "invalid" or "variable" will be ignored entirely. Target
* names "on" or "off" (case-insensitive) will be ignored. Target names
* may contain only alphanumeric characters, underbars (_), dashes (-),
* and dots (.) (if any other character occurs in the name, it and all
* subsequent characters in the name will be ignored).
* \note The caller is responsible for freeing the result with
* g_list_free_full(result, free).
*/
GList *
stonith__parse_targets(const char *target_spec)
{
GList *targets = NULL;
if (target_spec != NULL) {
size_t out_len = strlen(target_spec);
size_t line_start = 0; // Starting index of line being processed
for (size_t i = 0; i <= out_len; ++i) {
if ((target_spec[i] == '\n') || (target_spec[i] == '\0')
|| ((target_spec[i] == '\\') && (target_spec[i + 1] == 'n'))) {
// We've reached the end of one line of output
int len = i - line_start;
if (len > 0) {
char *line = strndup(target_spec + line_start, len);
line[len] = '\0'; // Because it might be a newline
parse_list_line(line, len, &targets);
free(line);
}
if (target_spec[i] == '\\') {
++i; // backslash-n takes up two positions
}
line_start = i + 1;
}
}
}
return targets;
}
/*!
* \internal
* \brief Check whether a fencing failure was followed by an equivalent success
*
* \param[in] event Fencing failure
* \param[in] top_history Complete fencing history (must be sorted by
* stonith__sort_history() beforehand)
*
* \return The name of the node that executed the fencing if a later successful
* event exists, or NULL if no such event exists
*/
const char *
stonith__later_succeeded(const stonith_history_t *event,
const stonith_history_t *top_history)
{
const char *other = NULL;
for (const stonith_history_t *prev_hp = top_history;
prev_hp != NULL; prev_hp = prev_hp->next) {
if (prev_hp == event) {
break;
}
if ((prev_hp->state == st_done) &&
pcmk__str_eq(event->target, prev_hp->target, pcmk__str_casei) &&
pcmk__str_eq(event->action, prev_hp->action, pcmk__str_none) &&
((event->completed < prev_hp->completed) ||
((event->completed == prev_hp->completed) && (event->completed_nsec < prev_hp->completed_nsec)))) {
if ((event->delegate == NULL)
|| pcmk__str_eq(event->delegate, prev_hp->delegate,
pcmk__str_casei)) {
// Prefer equivalent fencing by same executioner
return prev_hp->delegate;
} else if (other == NULL) {
// Otherwise remember first successful executioner
other = (prev_hp->delegate == NULL)? "some node" : prev_hp->delegate;
}
}
}
return other;
}
/*!
* \internal
* \brief Sort fencing history, pending first then by most recently completed
*
* \param[in,out] history List of stonith actions
*
* \return New head of sorted \p history
*/
stonith_history_t *
stonith__sort_history(stonith_history_t *history)
{
stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp;
for (hp = history; hp; ) {
tmp = hp->next;
if ((hp->state == st_done) || (hp->state == st_failed)) {
/* sort into new */
if ((!new) || (hp->completed > new->completed) ||
((hp->completed == new->completed) && (hp->completed_nsec > new->completed_nsec))) {
hp->next = new;
new = hp;
} else {
np = new;
do {
if ((!np->next) || (hp->completed > np->next->completed) ||
((hp->completed == np->next->completed) && (hp->completed_nsec > np->next->completed_nsec))) {
hp->next = np->next;
np->next = hp;
break;
}
np = np->next;
} while (1);
}
} else {
/* put into pending */
hp->next = pending;
pending = hp;
}
hp = tmp;
}
/* pending actions don't have a completed-stamp so make them go front */
if (pending) {
stonith_history_t *last_pending = pending;
while (last_pending->next) {
last_pending = last_pending->next;
}
last_pending->next = new;
new = pending;
}
return new;
}
/*!
* \internal
* \brief Return string equivalent of a fencing operation state value
*
* \param[in] state Fencing operation state value
*
* \return Human-friendly string equivalent of \p state
*/
const char *
stonith__op_state_text(enum op_state state)
{
// @COMPAT Move this to the fencer after dropping stonith_op_state_str()
switch (state) {
case st_query:
return "querying";
case st_exec:
return "executing";
case st_done:
return "completed";
case st_duplicate:
return "duplicate";
case st_failed:
return "failed";
default:
return "unknown";
}
}
stonith_history_t *
stonith__first_matching_event(stonith_history_t *history,
bool (*matching_fn)(stonith_history_t *, void *),
void *user_data)
{
for (stonith_history_t *hp = history; hp; hp = hp->next) {
if (matching_fn(hp, user_data)) {
return hp;
}
}
return NULL;
}
bool
stonith__event_state_pending(stonith_history_t *history, void *user_data)
{
return history->state != st_failed && history->state != st_done;
}
bool
stonith__event_state_eq(stonith_history_t *history, void *user_data)
{
return history->state == GPOINTER_TO_INT(user_data);
}
bool
stonith__event_state_neq(stonith_history_t *history, void *user_data)
{
return history->state != GPOINTER_TO_INT(user_data);
}
/*!
* \internal
* \brief Check whether a given parameter exists in a fence agent's metadata
*
* \param[in] metadata Agent metadata
* \param[in] name Parameter name
*
* \retval \c true If \p name exists as a parameter in \p metadata
* \retval \c false Otherwise
*/
static bool
param_is_supported(xmlNode *metadata, const char *name)
{
char *xpath_s = crm_strdup_printf("//" PCMK_XE_PARAMETER
"[@" PCMK_XA_NAME "='%s']",
name);
xmlXPathObject *xpath = pcmk__xpath_search(metadata->doc, xpath_s);
bool supported = (pcmk__xpath_num_results(xpath) > 0);
free(xpath_s);
xmlXPathFreeObject(xpath);
return supported;
}
/*!
* \internal
* \brief Get the default host argument based on a device's agent metadata
*
* If an agent supports the "plug" parameter, default to that. Otherwise default
* to the "port" parameter if supported. Otherwise return \c NULL.
*
* \param[in] metadata Agent metadata
*
* \return Parameter name for default host argument
*/
const char *
stonith__default_host_arg(xmlNode *metadata)
{
CRM_CHECK(metadata != NULL, return NULL);
if (param_is_supported(metadata, "plug")) {
return "plug";
}
if (param_is_supported(metadata, "port")) {
return "port";
}
return NULL;
}
/*!
* \internal
* \brief Retrieve fence agent meta-data asynchronously
*
* \param[in] agent Agent to execute
* \param[in] timeout_sec Error if not complete within this time
* \param[in] callback Function to call with result (this will always be
* called, whether by this function directly or
* later via the main loop, and on success the
* metadata will be in its result argument's
* action_stdout)
* \param[in,out] user_data User data to pass to callback
*
* \return Standard Pacemaker return code
* \note The caller must use a main loop. This function is not a
* stonith_api_operations_t method because it does not need a stonith_t
* object and does not go through the fencer, but executes the agent
* directly.
*/
int
stonith__metadata_async(const char *agent, int timeout_sec,
void (*callback)(int pid,
const pcmk__action_result_t *result,
void *user_data),
void *user_data)
{
switch (stonith_get_namespace(agent, NULL)) {
case st_namespace_rhcs:
{
stonith_action_t *action = NULL;
int rc = pcmk_ok;
action = stonith__action_create(agent, PCMK_ACTION_METADATA,
NULL, timeout_sec, NULL, NULL,
NULL);
rc = stonith__execute_async(action, user_data, callback, NULL);
if (rc != pcmk_ok) {
callback(0, stonith__action_result(action), user_data);
stonith__destroy_action(action);
}
return pcmk_legacy2rc(rc);
}
#if HAVE_STONITH_STONITH_H
case st_namespace_lha:
// LHA metadata is simply synthesized, so simulate async
{
pcmk__action_result_t result = {
.exit_status = CRM_EX_OK,
.execution_status = PCMK_EXEC_DONE,
.exit_reason = NULL,
.action_stdout = NULL,
.action_stderr = NULL,
};
stonith__lha_metadata(agent, timeout_sec,
&result.action_stdout);
callback(0, &result, user_data);
pcmk__reset_result(&result);
return pcmk_rc_ok;
}
#endif
default:
{
pcmk__action_result_t result = {
.exit_status = CRM_EX_NOSUCH,
.execution_status = PCMK_EXEC_ERROR_HARD,
.exit_reason = crm_strdup_printf("No such agent '%s'",
agent),
.action_stdout = NULL,
.action_stderr = NULL,
};
callback(0, &result, user_data);
pcmk__reset_result(&result);
return ENOENT;
}
}
}
/*!
* \internal
* \brief Return the exit status from an async action callback
*
* \param[in] data Callback data
*
* \return Exit status from callback data
*/
int
stonith__exit_status(const stonith_callback_data_t *data)
{
if ((data == NULL) || (data->opaque == NULL)) {
return CRM_EX_ERROR;
}
return ((pcmk__action_result_t *) data->opaque)->exit_status;
}
/*!
* \internal
* \brief Return the execution status from an async action callback
*
* \param[in] data Callback data
*
* \return Execution status from callback data
*/
int
stonith__execution_status(const stonith_callback_data_t *data)
{
if ((data == NULL) || (data->opaque == NULL)) {
return PCMK_EXEC_UNKNOWN;
}
return ((pcmk__action_result_t *) data->opaque)->execution_status;
}
/*!
* \internal
* \brief Return the exit reason from an async action callback
*
* \param[in] data Callback data
*
* \return Exit reason from callback data
*/
const char *
stonith__exit_reason(const stonith_callback_data_t *data)
{
if ((data == NULL) || (data->opaque == NULL)) {
return NULL;
}
return ((pcmk__action_result_t *) data->opaque)->exit_reason;
}
/*!
* \internal
* \brief Return the exit status from an event notification
*
* \param[in] event Event
*
* \return Exit status from event
*/
int
stonith__event_exit_status(const stonith_event_t *event)
{
if ((event == NULL) || (event->opaque == NULL)) {
return CRM_EX_ERROR;
} else {
struct event_private *event_private = event->opaque;
return event_private->result.exit_status;
}
}
/*!
* \internal
* \brief Return the execution status from an event notification
*
* \param[in] event Event
*
* \return Execution status from event
*/
int
stonith__event_execution_status(const stonith_event_t *event)
{
if ((event == NULL) || (event->opaque == NULL)) {
return PCMK_EXEC_UNKNOWN;
} else {
struct event_private *event_private = event->opaque;
return event_private->result.execution_status;
}
}
/*!
* \internal
* \brief Return the exit reason from an event notification
*
* \param[in] event Event
*
* \return Exit reason from event
*/
const char *
stonith__event_exit_reason(const stonith_event_t *event)
{
if ((event == NULL) || (event->opaque == NULL)) {
return NULL;
} else {
struct event_private *event_private = event->opaque;
return event_private->result.exit_reason;
}
}
/*!
* \internal
* \brief Return a human-friendly description of a fencing event
*
* \param[in] event Event to describe
*
* \return Newly allocated string with description of \p event
* \note The caller is responsible for freeing the return value.
* This function asserts on memory errors and never returns NULL.
*/
char *
stonith__event_description(const stonith_event_t *event)
{
// Use somewhat readable defaults
const char *origin = pcmk__s(event->client_origin, "a client");
const char *origin_node = pcmk__s(event->origin, "a node");
const char *executioner = pcmk__s(event->executioner, "the cluster");
const char *device = pcmk__s(event->device, "unknown");
const char *action = pcmk__s(event->action, event->operation);
const char *target = pcmk__s(event->target, "no node");
const char *reason = stonith__event_exit_reason(event);
const char *status;
if (action == NULL) {
action = "(unknown)";
}
if (stonith__event_execution_status(event) != PCMK_EXEC_DONE) {
status = pcmk_exec_status_str(stonith__event_execution_status(event));
} else if (stonith__event_exit_status(event) != CRM_EX_OK) {
status = pcmk_exec_status_str(PCMK_EXEC_ERROR);
} else {
status = crm_exit_str(CRM_EX_OK);
}
if (pcmk__str_eq(event->operation, PCMK__VALUE_ST_NOTIFY_HISTORY,
pcmk__str_none)) {
return crm_strdup_printf("Fencing history may have changed");
} else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_ADD,
pcmk__str_none)) {
return crm_strdup_printf("A fencing device (%s) was added", device);
} else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_DEL,
pcmk__str_none)) {
return crm_strdup_printf("A fencing device (%s) was removed", device);
} else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_ADD,
pcmk__str_none)) {
return crm_strdup_printf("A fencing topology level (%s) was added",
device);
} else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_DEL,
pcmk__str_none)) {
return crm_strdup_printf("A fencing topology level (%s) was removed",
device);
}
// event->operation should be PCMK__VALUE_ST_NOTIFY_FENCE at this point
return crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s%s%s%s (ref=%s)",
action, target, executioner, origin, origin_node,
status,
((reason == NULL)? "" : " ("), pcmk__s(reason, ""),
((reason == NULL)? "" : ")"),
pcmk__s(event->id, "(none)"));
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
// See comments in stonith-ng.h for why we re-declare before defining
stonith_t *stonith_api_new(void);
stonith_t *
stonith_api_new(void)
{
return stonith__api_new();
}
void stonith_api_delete(stonith_t *stonith);
void
stonith_api_delete(stonith_t *stonith)
{
stonith__api_free(stonith);
}
static void
stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
{
int call = GPOINTER_TO_INT(key);
stonith_callback_client_t *blob = value;
crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "no ID"));
}
void stonith_dump_pending_callbacks(stonith_t *stonith);
void
stonith_dump_pending_callbacks(stonith_t *stonith)
{
stonith_private_t *private = stonith->st_private;
if (private->stonith_op_callback_table == NULL) {
return;
}
return g_hash_table_foreach(private->stonith_op_callback_table,
stonith_dump_pending_op, NULL);
}
bool stonith_dispatch(stonith_t *stonith_api);
bool
stonith_dispatch(stonith_t *stonith_api)
{
return (stonith__api_dispatch(stonith_api) == pcmk_rc_ok);
}
stonith_key_value_t *stonith_key_value_add(stonith_key_value_t *head,
const char *key, const char *value);
stonith_key_value_t *
stonith_key_value_add(stonith_key_value_t *head, const char *key,
const char *value)
{
return stonith__key_value_add(head, key, value);
}
void stonith_key_value_freeall(stonith_key_value_t *head, int keys, int values);
void
stonith_key_value_freeall(stonith_key_value_t *head, int keys, int values)
{
stonith__key_value_freeall(head, (keys != 0), (values != 0));
}
void stonith_history_free(stonith_history_t *head);
void
stonith_history_free(stonith_history_t *head)
{
stonith__history_free(head);
}
int stonith_api_connect_retry(stonith_t *st, const char *name,
int max_attempts);
int
stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts)
{
return pcmk_rc2legacy(stonith__api_connect_retry(st, name, max_attempts));
}
const char *stonith_op_state_str(enum op_state state);
const char *
stonith_op_state_str(enum op_state state)
{
return stonith__op_state_text(state);
}
bool stonith_agent_exists(const char *agent, int timeout);
bool
stonith_agent_exists(const char *agent, int timeout)
{
return stonith__agent_exists(agent);
}
const char *stonith_action_str(const char *action);
const char *
stonith_action_str(const char *action)
{
if (action == NULL) {
return "fencing";
} else if (strcmp(action, PCMK_ACTION_ON) == 0) {
return "unfencing";
} else if (strcmp(action, PCMK_ACTION_OFF) == 0) {
return "turning off";
} else {
return action;
}
}
enum stonith_namespace stonith_text2namespace(const char *namespace_s);
enum stonith_namespace
stonith_text2namespace(const char *namespace_s)
{
return parse_namespace(namespace_s);
}
const char *stonith_namespace2text(enum stonith_namespace st_namespace);
const char *
stonith_namespace2text(enum stonith_namespace st_namespace)
{
return namespace_text(st_namespace);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 6:04 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1661308
Default Alt Text
(124 KB)

Event Timeline