Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c
index 047af3cc5a..a59dee2356 100644
--- a/daemons/execd/execd_commands.c
+++ b/daemons/execd/execd_commands.c
@@ -1,2010 +1,2010 @@
/*
* Copyright 2012-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 <crm/fencing/internal.h>
#include <glib.h>
#include <libxml/tree.h> // xmlNode
// Check whether we have a high-resolution monotonic clock
#undef PCMK__TIME_USE_CGT
#if HAVE_DECL_CLOCK_MONOTONIC && defined(CLOCK_MONOTONIC)
# define PCMK__TIME_USE_CGT
# include <time.h> /* clock_gettime */
#endif
#include <unistd.h>
#include <crm/crm.h>
#include <crm/fencing/internal.h>
#include <crm/services.h>
#include <crm/services_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/ipc.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/xml.h>
#include "pacemaker-execd.h"
GHashTable *rsc_list = NULL;
typedef struct lrmd_cmd_s {
int timeout;
guint interval_ms;
int start_delay;
int timeout_orig;
int call_id;
int call_opts;
/* Timer ids, must be removed on cmd destruction. */
int delay_id;
int stonith_recurring_id;
int rsc_deleted;
int service_flags;
char *client_id;
char *origin;
char *rsc_id;
char *action;
char *real_action;
char *userdata_str;
pcmk__action_result_t result;
/* We can track operation queue time and run time, to be saved with the CIB
* resource history (and displayed in cluster status). We need
* high-resolution monotonic time for this purpose, so we use
* clock_gettime(CLOCK_MONOTONIC, ...) (if available, otherwise this feature
* is disabled).
*
* However, we also need epoch timestamps for recording the time the command
* last ran and the time its return value last changed, for use in time
* displays (as opposed to interval calculations). We keep time_t values for
* this purpose.
*
* The last run time is used for both purposes, so we keep redundant
* monotonic and epoch values for this. Technically the two could represent
* different times, but since time_t has only second resolution and the
* values are used for distinct purposes, that is not significant.
*/
#ifdef PCMK__TIME_USE_CGT
/* Recurring and systemd operations may involve more than one executor
* command per operation, so they need info about the original and the most
* recent.
*/
struct timespec t_first_run; // When op first ran
struct timespec t_run; // When op most recently ran
struct timespec t_first_queue; // When op was first queued
struct timespec t_queue; // When op was most recently queued
#endif
time_t epoch_last_run; // Epoch timestamp of when op last ran
time_t epoch_rcchange; // Epoch timestamp of when rc last changed
bool first_notify_sent;
int last_notify_rc;
int last_notify_op_status;
int last_pid;
GHashTable *params;
} lrmd_cmd_t;
static void cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc);
static gboolean execute_resource_action(gpointer user_data);
static void cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id);
#ifdef PCMK__TIME_USE_CGT
/*!
* \internal
* \brief Check whether a struct timespec has been set
*
* \param[in] timespec Time to check
*
* \return true if timespec has been set (i.e. is nonzero), false otherwise
*/
static inline bool
time_is_set(const struct timespec *timespec)
{
return (timespec != NULL) &&
((timespec->tv_sec != 0) || (timespec->tv_nsec != 0));
}
/*
* \internal
* \brief Set a timespec (and its original if unset) to the current time
*
* \param[out] t_current Where to store current time
* \param[out] t_orig Where to copy t_current if unset
*/
static void
get_current_time(struct timespec *t_current, struct timespec *t_orig)
{
clock_gettime(CLOCK_MONOTONIC, t_current);
if ((t_orig != NULL) && !time_is_set(t_orig)) {
*t_orig = *t_current;
}
}
/*!
* \internal
* \brief Return difference between two times in milliseconds
*
* \param[in] now More recent time (or NULL to use current time)
* \param[in] old Earlier time
*
* \return milliseconds difference (or 0 if old is NULL or unset)
*
* \note Can overflow on 32bit machines when the differences is around
* 24 days or more.
*/
static int
time_diff_ms(const struct timespec *now, const struct timespec *old)
{
int diff_ms = 0;
if (time_is_set(old)) {
struct timespec local_now = { 0, };
if (now == NULL) {
clock_gettime(CLOCK_MONOTONIC, &local_now);
now = &local_now;
}
diff_ms = (now->tv_sec - old->tv_sec) * 1000
+ (now->tv_nsec - old->tv_nsec) / 1000000;
}
return diff_ms;
}
/*!
* \internal
* \brief Reset a command's operation times to their original values.
*
* Reset a command's run and queued timestamps to the timestamps of the original
* command, so we report the entire time since then and not just the time since
* the most recent command (for recurring and systemd operations).
*
* \param[in,out] cmd Executor command object to reset
*
* \note It's not obvious what the queued time should be for a systemd
* start/stop operation, which might go like this:
* initial command queued 5ms, runs 3s
* monitor command queued 10ms, runs 10s
* monitor command queued 10ms, runs 10s
* Is the queued time for that operation 5ms, 10ms or 25ms? The current
* implementation will report 5ms. If it's 25ms, then we need to
* subtract 20ms from the total exec time so as not to count it twice.
* We can implement that later if it matters to anyone ...
*/
static void
cmd_original_times(lrmd_cmd_t * cmd)
{
cmd->t_run = cmd->t_first_run;
cmd->t_queue = cmd->t_first_queue;
}
#endif
static inline bool
action_matches(const lrmd_cmd_t *cmd, const char *action, guint interval_ms)
{
return (cmd->interval_ms == interval_ms)
&& pcmk__str_eq(cmd->action, action, pcmk__str_casei);
}
/*!
* \internal
* \brief Log the result of an asynchronous command
*
* \param[in] cmd Command to log result for
* \param[in] exec_time_ms Execution time in milliseconds, if known
* \param[in] queue_time_ms Queue time in milliseconds, if known
*/
static void
log_finished(const lrmd_cmd_t *cmd, int exec_time_ms, int queue_time_ms)
{
int log_level = LOG_INFO;
GString *str = g_string_sized_new(100); // reasonable starting size
if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
log_level = LOG_DEBUG;
}
g_string_append_printf(str, "%s %s (call %d",
cmd->rsc_id, cmd->action, cmd->call_id);
if (cmd->last_pid != 0) {
g_string_append_printf(str, ", PID %d", cmd->last_pid);
}
switch (cmd->result.execution_status) {
case PCMK_EXEC_DONE:
g_string_append_printf(str, ") exited with status %d",
cmd->result.exit_status);
break;
case PCMK_EXEC_CANCELLED:
g_string_append_printf(str, ") cancelled");
break;
default:
pcmk__g_strcat(str, ") could not be executed: ",
pcmk_exec_status_str(cmd->result.execution_status),
NULL);
break;
}
if (cmd->result.exit_reason != NULL) {
pcmk__g_strcat(str, " (", cmd->result.exit_reason, ")", NULL);
}
#ifdef PCMK__TIME_USE_CGT
pcmk__g_strcat(str, " (execution time ",
pcmk__readable_interval(exec_time_ms), NULL);
if (queue_time_ms > 0) {
pcmk__g_strcat(str, " after being queued ",
pcmk__readable_interval(queue_time_ms), NULL);
}
g_string_append_c(str, ')');
#endif
do_crm_log(log_level, "%s", str->str);
g_string_free(str, TRUE);
}
static void
log_execute(lrmd_cmd_t * cmd)
{
int log_level = LOG_INFO;
if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)) {
log_level = LOG_DEBUG;
}
do_crm_log(log_level, "executing - rsc:%s action:%s call_id:%d",
cmd->rsc_id, cmd->action, cmd->call_id);
}
static const char *
normalize_action_name(lrmd_rsc_t * rsc, const char *action)
{
if (pcmk__str_eq(action, PCMK_ACTION_MONITOR, pcmk__str_casei) &&
pcmk_is_set(pcmk_get_ra_caps(rsc->class), pcmk_ra_cap_status)) {
return PCMK_ACTION_STATUS;
}
return action;
}
static lrmd_rsc_t *
build_rsc_from_xml(xmlNode * msg)
{
xmlNode *rsc_xml = pcmk__xpath_find_one(msg->doc, "//" PCMK__XE_LRMD_RSC,
LOG_ERR);
lrmd_rsc_t *rsc = NULL;
rsc = pcmk__assert_alloc(1, sizeof(lrmd_rsc_t));
crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &rsc->call_opts);
rsc->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
rsc->class = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_CLASS);
rsc->provider = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_PROVIDER);
rsc->type = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_TYPE);
rsc->work = mainloop_add_trigger(G_PRIORITY_HIGH, execute_resource_action,
rsc);
// Initialize fence device probes (to return "not running")
pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
PCMK_EXEC_NO_FENCE_DEVICE, NULL);
return rsc;
}
static lrmd_cmd_t *
create_lrmd_cmd(xmlNode *msg, pcmk__client_t *client)
{
int call_options = 0;
xmlNode *rsc_xml = pcmk__xpath_find_one(msg->doc, "//" PCMK__XE_LRMD_RSC,
LOG_ERR);
lrmd_cmd_t *cmd = NULL;
cmd = pcmk__assert_alloc(1, sizeof(lrmd_cmd_t));
crm_element_value_int(msg, PCMK__XA_LRMD_CALLOPT, &call_options);
cmd->call_opts = call_options;
cmd->client_id = pcmk__str_copy(client->id);
crm_element_value_int(msg, PCMK__XA_LRMD_CALLID, &cmd->call_id);
crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL,
&cmd->interval_ms);
crm_element_value_int(rsc_xml, PCMK__XA_LRMD_TIMEOUT, &cmd->timeout);
crm_element_value_int(rsc_xml, PCMK__XA_LRMD_RSC_START_DELAY,
&cmd->start_delay);
cmd->timeout_orig = cmd->timeout;
cmd->origin = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_ORIGIN);
cmd->action = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
cmd->userdata_str = crm_element_value_copy(rsc_xml,
PCMK__XA_LRMD_RSC_USERDATA_STR);
cmd->rsc_id = crm_element_value_copy(rsc_xml, PCMK__XA_LRMD_RSC_ID);
cmd->params = xml2list(rsc_xml);
if (pcmk__str_eq(g_hash_table_lookup(cmd->params, "CRM_meta_on_fail"),
PCMK_VALUE_BLOCK, pcmk__str_casei)) {
crm_debug("Setting flag to leave pid group on timeout and "
"only kill action pid for " PCMK__OP_FMT,
cmd->rsc_id, cmd->action, cmd->interval_ms);
cmd->service_flags = pcmk__set_flags_as(__func__, __LINE__,
LOG_TRACE, "Action",
cmd->action, 0,
SVC_ACTION_LEAVE_GROUP,
"SVC_ACTION_LEAVE_GROUP");
}
return cmd;
}
static void
stop_recurring_timer(lrmd_cmd_t *cmd)
{
if (cmd) {
if (cmd->stonith_recurring_id) {
g_source_remove(cmd->stonith_recurring_id);
}
cmd->stonith_recurring_id = 0;
}
}
static void
free_lrmd_cmd(lrmd_cmd_t * cmd)
{
stop_recurring_timer(cmd);
if (cmd->delay_id) {
g_source_remove(cmd->delay_id);
}
if (cmd->params) {
g_hash_table_destroy(cmd->params);
}
pcmk__reset_result(&(cmd->result));
free(cmd->origin);
free(cmd->action);
free(cmd->real_action);
free(cmd->userdata_str);
free(cmd->rsc_id);
free(cmd->client_id);
free(cmd);
}
static gboolean
stonith_recurring_op_helper(gpointer data)
{
lrmd_cmd_t *cmd = data;
lrmd_rsc_t *rsc;
cmd->stonith_recurring_id = 0;
if (!cmd->rsc_id) {
return FALSE;
}
rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
pcmk__assert(rsc != NULL);
/* take it out of recurring_ops list, and put it in the pending ops
* to be executed */
rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
#ifdef PCMK__TIME_USE_CGT
get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
#endif
mainloop_set_trigger(rsc->work);
return FALSE;
}
static inline void
start_recurring_timer(lrmd_cmd_t *cmd)
{
if (!cmd || (cmd->interval_ms <= 0)) {
return;
}
cmd->stonith_recurring_id = pcmk__create_timer(cmd->interval_ms,
stonith_recurring_op_helper,
cmd);
}
static gboolean
start_delay_helper(gpointer data)
{
lrmd_cmd_t *cmd = data;
lrmd_rsc_t *rsc = NULL;
cmd->delay_id = 0;
rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
if (rsc) {
mainloop_set_trigger(rsc->work);
}
return FALSE;
}
/*!
* \internal
* \brief Check whether a list already contains the equivalent of a given action
*
* \param[in] action_list List to search
* \param[in] cmd Action to search for
*/
static lrmd_cmd_t *
find_duplicate_action(const GList *action_list, const lrmd_cmd_t *cmd)
{
for (const GList *item = action_list; item != NULL; item = item->next) {
lrmd_cmd_t *dup = item->data;
if (action_matches(cmd, dup->action, dup->interval_ms)) {
return dup;
}
}
return NULL;
}
static bool
merge_recurring_duplicate(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
{
lrmd_cmd_t * dup = NULL;
bool dup_pending = true;
if (cmd->interval_ms == 0) {
return false;
}
// Search for a duplicate of this action (in-flight or not)
dup = find_duplicate_action(rsc->pending_ops, cmd);
if (dup == NULL) {
dup_pending = false;
dup = find_duplicate_action(rsc->recurring_ops, cmd);
if (dup == NULL) {
return false;
}
}
/* Do not merge fencing monitors marked for cancellation, so we can reply to
* the cancellation separately.
*/
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
pcmk__str_casei)
&& (dup->result.execution_status == PCMK_EXEC_CANCELLED)) {
return false;
}
/* This should not occur. If it does, we need to investigate how something
* like this is possible in the controller.
*/
crm_warn("Duplicate recurring op entry detected (" PCMK__OP_FMT
"), merging with previous op entry",
rsc->rsc_id, normalize_action_name(rsc, dup->action),
dup->interval_ms);
// Merge new action's call ID and user data into existing action
dup->first_notify_sent = false;
free(dup->userdata_str);
dup->userdata_str = cmd->userdata_str;
cmd->userdata_str = NULL;
dup->call_id = cmd->call_id;
free_lrmd_cmd(cmd);
cmd = NULL;
/* If dup is not pending, that means it has already executed at least once
* and is waiting in the interval. In that case, stop waiting and initiate
* a new instance now.
*/
if (!dup_pending) {
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
pcmk__str_casei)) {
stop_recurring_timer(dup);
stonith_recurring_op_helper(dup);
} else {
services_action_kick(rsc->rsc_id,
normalize_action_name(rsc, dup->action),
dup->interval_ms);
}
}
return true;
}
static void
schedule_lrmd_cmd(lrmd_rsc_t * rsc, lrmd_cmd_t * cmd)
{
CRM_CHECK(cmd != NULL, return);
CRM_CHECK(rsc != NULL, return);
crm_trace("Scheduling %s on %s", cmd->action, rsc->rsc_id);
if (merge_recurring_duplicate(rsc, cmd)) {
// Equivalent of cmd has already been scheduled
return;
}
/* The controller expects the executor to automatically cancel
* recurring operations before a resource stops.
*/
if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
cancel_all_recurring(rsc, NULL);
}
rsc->pending_ops = g_list_append(rsc->pending_ops, cmd);
#ifdef PCMK__TIME_USE_CGT
get_current_time(&(cmd->t_queue), &(cmd->t_first_queue));
#endif
mainloop_set_trigger(rsc->work);
if (cmd->start_delay) {
cmd->delay_id = pcmk__create_timer(cmd->start_delay, start_delay_helper, cmd);
}
}
static xmlNode *
create_lrmd_reply(const char *origin, int rc, int call_id)
{
xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_LRMD_REPLY);
crm_xml_add(reply, PCMK__XA_LRMD_ORIGIN, origin);
crm_xml_add_int(reply, PCMK__XA_LRMD_RC, rc);
crm_xml_add_int(reply, PCMK__XA_LRMD_CALLID, call_id);
return reply;
}
static void
send_client_notify(gpointer key, gpointer value, gpointer user_data)
{
xmlNode *update_msg = user_data;
pcmk__client_t *client = value;
int rc;
int log_level = LOG_WARNING;
const char *msg = NULL;
CRM_CHECK(client != NULL, return);
if (client->name == NULL) {
crm_trace("Skipping notification to client without name");
return;
}
if (pcmk_is_set(client->flags, pcmk__client_to_proxy)) {
/* We only want to notify clients of the executor IPC API. If we are
* running as Pacemaker Remote, we may have clients proxied to other
* IPC services in the cluster, so skip those.
*/
crm_trace("Skipping executor API notification to client %s",
pcmk__client_name(client));
return;
}
rc = lrmd_server_send_notify(client, update_msg);
if (rc == pcmk_rc_ok) {
return;
}
switch (rc) {
case ENOTCONN:
case EPIPE: // Client exited without waiting for notification
log_level = LOG_INFO;
msg = "Disconnected";
break;
default:
msg = pcmk_rc_str(rc);
break;
}
do_crm_log(log_level, "Could not notify client %s: %s " QB_XS " rc=%d",
pcmk__client_name(client), msg, rc);
}
static void
send_cmd_complete_notify(lrmd_cmd_t * cmd)
{
xmlNode *notify = NULL;
int exec_time = 0;
int queue_time = 0;
#ifdef PCMK__TIME_USE_CGT
exec_time = time_diff_ms(NULL, &(cmd->t_run));
queue_time = time_diff_ms(&cmd->t_run, &(cmd->t_queue));
#endif
log_finished(cmd, exec_time, queue_time);
/* If the originator requested to be notified only for changes in recurring
* operation results, skip the notification if the result hasn't changed.
*/
if (cmd->first_notify_sent
&& pcmk_is_set(cmd->call_opts, lrmd_opt_notify_changes_only)
&& (cmd->last_notify_rc == cmd->result.exit_status)
&& (cmd->last_notify_op_status == cmd->result.execution_status)) {
return;
}
cmd->first_notify_sent = true;
cmd->last_notify_rc = cmd->result.exit_status;
cmd->last_notify_op_status = cmd->result.execution_status;
notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add_int(notify, PCMK__XA_LRMD_TIMEOUT, cmd->timeout);
crm_xml_add_ms(notify, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_START_DELAY, cmd->start_delay);
crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_RC, cmd->result.exit_status);
crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_OP_STATUS,
cmd->result.execution_status);
crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, cmd->call_id);
crm_xml_add_int(notify, PCMK__XA_LRMD_RSC_DELETED, cmd->rsc_deleted);
crm_xml_add_ll(notify, PCMK__XA_LRMD_RUN_TIME,
(long long) cmd->epoch_last_run);
crm_xml_add_ll(notify, PCMK__XA_LRMD_RCCHANGE_TIME,
(long long) cmd->epoch_rcchange);
#ifdef PCMK__TIME_USE_CGT
crm_xml_add_int(notify, PCMK__XA_LRMD_EXEC_TIME, exec_time);
crm_xml_add_int(notify, PCMK__XA_LRMD_QUEUE_TIME, queue_time);
#endif
crm_xml_add(notify, PCMK__XA_LRMD_OP, LRMD_OP_RSC_EXEC);
crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, cmd->rsc_id);
if(cmd->real_action) {
crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->real_action);
} else {
crm_xml_add(notify, PCMK__XA_LRMD_RSC_ACTION, cmd->action);
}
crm_xml_add(notify, PCMK__XA_LRMD_RSC_USERDATA_STR, cmd->userdata_str);
crm_xml_add(notify, PCMK__XA_LRMD_RSC_EXIT_REASON, cmd->result.exit_reason);
if (cmd->result.action_stderr != NULL) {
crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
cmd->result.action_stderr);
} else if (cmd->result.action_stdout != NULL) {
crm_xml_add(notify, PCMK__XA_LRMD_RSC_OUTPUT,
cmd->result.action_stdout);
}
if (cmd->params) {
char *key = NULL;
char *value = NULL;
GHashTableIter iter;
xmlNode *args = pcmk__xe_create(notify, PCMK__XE_ATTRIBUTES);
g_hash_table_iter_init(&iter, cmd->params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
hash2smartfield((gpointer) key, (gpointer) value, args);
}
}
if ((cmd->client_id != NULL)
&& pcmk_is_set(cmd->call_opts, lrmd_opt_notify_orig_only)) {
pcmk__client_t *client = pcmk__find_client_by_id(cmd->client_id);
if (client != NULL) {
send_client_notify(client->id, client, notify);
}
} else {
pcmk__foreach_ipc_client(send_client_notify, notify);
}
pcmk__xml_free(notify);
}
static void
send_generic_notify(int rc, xmlNode * request)
{
if (pcmk__ipc_client_count() != 0) {
int call_id = 0;
xmlNode *notify = NULL;
xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
"//" PCMK__XE_LRMD_RSC,
LOG_ERR);
const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
crm_xml_add(notify, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add_int(notify, PCMK__XA_LRMD_RC, rc);
crm_xml_add_int(notify, PCMK__XA_LRMD_CALLID, call_id);
crm_xml_add(notify, PCMK__XA_LRMD_OP, op);
crm_xml_add(notify, PCMK__XA_LRMD_RSC_ID, rsc_id);
pcmk__foreach_ipc_client(send_client_notify, notify);
pcmk__xml_free(notify);
}
}
static void
cmd_reset(lrmd_cmd_t * cmd)
{
cmd->last_pid = 0;
#ifdef PCMK__TIME_USE_CGT
memset(&cmd->t_run, 0, sizeof(cmd->t_run));
memset(&cmd->t_queue, 0, sizeof(cmd->t_queue));
#endif
cmd->epoch_last_run = 0;
pcmk__reset_result(&(cmd->result));
cmd->result.execution_status = PCMK_EXEC_DONE;
}
static void
cmd_finalize(lrmd_cmd_t * cmd, lrmd_rsc_t * rsc)
{
crm_trace("Resource operation rsc:%s action:%s completed (%p %p)", cmd->rsc_id, cmd->action,
rsc ? rsc->active : NULL, cmd);
if (rsc && (rsc->active == cmd)) {
rsc->active = NULL;
mainloop_set_trigger(rsc->work);
}
if (!rsc) {
cmd->rsc_deleted = 1;
}
/* reset original timeout so client notification has correct information */
cmd->timeout = cmd->timeout_orig;
send_cmd_complete_notify(cmd);
if ((cmd->interval_ms != 0)
&& (cmd->result.execution_status == PCMK_EXEC_CANCELLED)) {
if (rsc) {
rsc->recurring_ops = g_list_remove(rsc->recurring_ops, cmd);
rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
}
free_lrmd_cmd(cmd);
} else if (cmd->interval_ms == 0) {
if (rsc) {
rsc->pending_ops = g_list_remove(rsc->pending_ops, cmd);
}
free_lrmd_cmd(cmd);
} else {
/* Clear all the values pertaining just to the last iteration of a recurring op. */
cmd_reset(cmd);
}
}
struct notify_new_client_data {
xmlNode *notify;
pcmk__client_t *new_client;
};
static void
notify_one_client(gpointer key, gpointer value, gpointer user_data)
{
pcmk__client_t *client = value;
struct notify_new_client_data *data = user_data;
if (!pcmk__str_eq(client->id, data->new_client->id, pcmk__str_casei)) {
send_client_notify(key, (gpointer) client, (gpointer) data->notify);
}
}
void
notify_of_new_client(pcmk__client_t *new_client)
{
struct notify_new_client_data data;
data.new_client = new_client;
data.notify = pcmk__xe_create(NULL, PCMK__XE_LRMD_NOTIFY);
crm_xml_add(data.notify, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data.notify, PCMK__XA_LRMD_OP, LRMD_OP_NEW_CLIENT);
pcmk__foreach_ipc_client(notify_one_client, &data);
pcmk__xml_free(data.notify);
}
void
client_disconnect_cleanup(const char *client_id)
{
GHashTableIter iter;
lrmd_rsc_t *rsc = NULL;
char *key = NULL;
g_hash_table_iter_init(&iter, rsc_list);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & rsc)) {
if (pcmk_all_flags_set(rsc->call_opts, lrmd_opt_drop_recurring)) {
/* This client is disconnecting, drop any recurring operations
* it may have initiated on the resource */
cancel_all_recurring(rsc, client_id);
}
}
}
static void
action_complete(svc_action_t * action)
{
lrmd_rsc_t *rsc;
lrmd_cmd_t *cmd = action->cb_data;
enum ocf_exitcode code;
#ifdef PCMK__TIME_USE_CGT
const char *rclass = NULL;
bool goagain = false;
int time_sum = 0;
int timeout_left = 0;
int delay = 0;
#endif
if (!cmd) {
crm_err("Completed executor action (%s) does not match any known operations",
action->id);
return;
}
#ifdef PCMK__TIME_USE_CGT
if (cmd->result.exit_status != action->rc) {
cmd->epoch_rcchange = time(NULL);
}
#endif
cmd->last_pid = action->pid;
// Cast variable instead of function return to keep compilers happy
code = services_result2ocf(action->standard, cmd->action, action->rc);
pcmk__set_result(&(cmd->result), (int) code,
action->status, services__exit_reason(action));
rsc = cmd->rsc_id ? g_hash_table_lookup(rsc_list, cmd->rsc_id) : NULL;
#ifdef PCMK__TIME_USE_CGT
if (rsc != NULL) {
rclass = rsc->class;
#if PCMK__ENABLE_SERVICE
if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SERVICE,
pcmk__str_casei)) {
rclass = resources_find_service_class(rsc->type);
}
#endif
}
if (!pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_SYSTEMD, pcmk__str_casei)) {
goto finalize;
}
if (pcmk__result_ok(&(cmd->result))
&& pcmk__strcase_any_of(cmd->action, PCMK_ACTION_START,
PCMK_ACTION_STOP, NULL)) {
/* Getting results for when a start or stop action completes is now
* handled by watching for JobRemoved() signals from systemd and
* reacting to them. So, we can bypass the rest of the code in this
* function for those actions, and simply finalize cmd.
*
* @TODO When monitors are handled in the same way, this function
* can either be drastically simplified or done away with entirely.
*/
services__copy_result(action, &(cmd->result));
goto finalize;
} else if (cmd->result.execution_status == PCMK_EXEC_PENDING &&
pcmk__str_any_of(cmd->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS, NULL) &&
cmd->interval_ms == 0 &&
cmd->real_action == NULL) {
/* If the state is Pending at the time of probe, execute follow-up monitor. */
goagain = true;
cmd->real_action = cmd->action;
cmd->action = pcmk__str_copy(PCMK_ACTION_MONITOR);
} else if (cmd->real_action != NULL) {
// This is follow-up monitor to check whether start/stop/probe(monitor) completed
if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
goagain = true;
} else if (pcmk__result_ok(&(cmd->result))
&& pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
pcmk__str_casei)) {
goagain = true;
} else {
int time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
int timeout_left = cmd->timeout_orig - time_sum;
crm_debug("%s systemd %s is now complete (elapsed=%dms, "
"remaining=%dms): %s (%d)",
cmd->rsc_id, cmd->real_action, time_sum, timeout_left,
crm_exit_str(cmd->result.exit_status),
cmd->result.exit_status);
cmd_original_times(cmd);
// Monitors may return "not running", but start/stop shouldn't
if ((cmd->result.execution_status == PCMK_EXEC_DONE)
&& (cmd->result.exit_status == PCMK_OCF_NOT_RUNNING)) {
if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_START,
pcmk__str_casei)) {
cmd->result.exit_status = PCMK_OCF_UNKNOWN_ERROR;
} else if (pcmk__str_eq(cmd->real_action, PCMK_ACTION_STOP,
pcmk__str_casei)) {
cmd->result.exit_status = PCMK_OCF_OK;
}
}
}
} else if (pcmk__str_any_of(cmd->action, PCMK_ACTION_MONITOR, PCMK_ACTION_STATUS, NULL)
&& (cmd->interval_ms > 0)) {
/* For monitors, excluding follow-up monitors, */
/* if the pending state persists from the first notification until its timeout, */
/* it will be treated as a timeout. */
if ((cmd->result.execution_status == PCMK_EXEC_PENDING) &&
(cmd->last_notify_op_status == PCMK_EXEC_PENDING)) {
int time_left = time(NULL) - (cmd->epoch_rcchange + (cmd->timeout_orig/1000));
if (time_left >= 0) {
crm_notice("Giving up on %s %s (rc=%d): monitor pending timeout "
"(first pending notification=%s timeout=%ds)",
cmd->rsc_id, cmd->action, cmd->result.exit_status,
pcmk__trim(ctime(&cmd->epoch_rcchange)), cmd->timeout_orig);
pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
PCMK_EXEC_TIMEOUT,
"Investigate reason for timeout, and adjust "
"configured operation timeout if necessary");
cmd_original_times(cmd);
}
}
}
if (!goagain) {
goto finalize;
}
time_sum = time_diff_ms(NULL, &(cmd->t_first_run));
timeout_left = cmd->timeout_orig - time_sum;
delay = cmd->timeout_orig / 10;
if (delay >= timeout_left && timeout_left > 20) {
delay = timeout_left/2;
}
delay = QB_MIN(2000, delay);
if (delay < timeout_left) {
cmd->start_delay = delay;
cmd->timeout = timeout_left;
if (pcmk__result_ok(&(cmd->result))) {
crm_debug("%s %s may still be in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
cmd->rsc_id, cmd->real_action, time_sum, timeout_left, delay);
} else if (cmd->result.execution_status == PCMK_EXEC_PENDING) {
crm_info("%s %s is still in progress: re-scheduling (elapsed=%dms, remaining=%dms, start_delay=%dms)",
cmd->rsc_id, cmd->action, time_sum, timeout_left, delay);
} else {
crm_notice("%s %s failed: %s: Re-scheduling (remaining "
"timeout %s) " QB_XS
" exitstatus=%d elapsed=%dms start_delay=%dms)",
cmd->rsc_id, cmd->action,
crm_exit_str(cmd->result.exit_status),
pcmk__readable_interval(timeout_left),
cmd->result.exit_status, time_sum, delay);
}
cmd_reset(cmd);
if (rsc) {
rsc->active = NULL;
}
schedule_lrmd_cmd(rsc, cmd);
/* Don't finalize cmd, we're not done with it yet */
return;
} else {
crm_notice("Giving up on %s %s (rc=%d): timeout (elapsed=%dms, remaining=%dms)",
cmd->rsc_id,
(cmd->real_action? cmd->real_action : cmd->action),
cmd->result.exit_status, time_sum, timeout_left);
pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
PCMK_EXEC_TIMEOUT,
"Investigate reason for timeout, and adjust "
"configured operation timeout if necessary");
cmd_original_times(cmd);
}
#endif
finalize:
pcmk__set_result_output(&(cmd->result), services__grab_stdout(action),
services__grab_stderr(action));
cmd_finalize(cmd, rsc);
}
/*!
* \internal
* \brief Process the result of a fence device action (start, stop, or monitor)
*
* \param[in,out] cmd Fence device action that completed
* \param[in] exit_status Fencer API exit status for action
* \param[in] execution_status Fencer API execution status for action
* \param[in] exit_reason Human-friendly detail, if action failed
*/
static void
stonith_action_complete(lrmd_cmd_t *cmd, int exit_status,
enum pcmk_exec_status execution_status,
const char *exit_reason)
{
// This can be NULL if resource was removed before command completed
lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id);
// Simplify fencer exit status to uniform exit status
if (exit_status != CRM_EX_OK) {
exit_status = PCMK_OCF_UNKNOWN_ERROR;
}
if (cmd->result.execution_status == PCMK_EXEC_CANCELLED) {
/* An in-flight fence action was cancelled. The execution status is
* already correct, so don't overwrite it.
*/
execution_status = PCMK_EXEC_CANCELLED;
} else {
/* Some execution status codes have specific meanings for the fencer
* that executor clients may not expect, so map them to a simple error
* status.
*/
switch (execution_status) {
case PCMK_EXEC_NOT_CONNECTED:
case PCMK_EXEC_INVALID:
execution_status = PCMK_EXEC_ERROR;
break;
case PCMK_EXEC_NO_FENCE_DEVICE:
/* This should be possible only for probes in practice, but
* interpret for all actions to be safe.
*/
if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
pcmk__str_none)) {
exit_status = PCMK_OCF_NOT_RUNNING;
} else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
pcmk__str_none)) {
exit_status = PCMK_OCF_OK;
} else {
exit_status = PCMK_OCF_NOT_INSTALLED;
}
execution_status = PCMK_EXEC_ERROR;
break;
case PCMK_EXEC_NOT_SUPPORTED:
exit_status = PCMK_OCF_UNIMPLEMENT_FEATURE;
break;
default:
break;
}
}
pcmk__set_result(&cmd->result, exit_status, execution_status, exit_reason);
// Certain successful actions change the known state of the resource
if ((rsc != NULL) && pcmk__result_ok(&(cmd->result))) {
if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
pcmk__set_result(&rsc->fence_probe_result, CRM_EX_OK,
PCMK_EXEC_DONE, NULL); // "running"
} else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP,
pcmk__str_casei)) {
pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
PCMK_EXEC_NO_FENCE_DEVICE, NULL); // "not running"
}
}
/* The recurring timer should not be running at this point in any case, but
* as a failsafe, stop it if it is.
*/
stop_recurring_timer(cmd);
/* Reschedule this command if appropriate. If a recurring command is *not*
* rescheduled, its status must be PCMK_EXEC_CANCELLED, otherwise it will
* not be removed from recurring_ops by cmd_finalize().
*/
if (rsc && (cmd->interval_ms > 0)
&& (cmd->result.execution_status != PCMK_EXEC_CANCELLED)) {
start_recurring_timer(cmd);
}
cmd_finalize(cmd, rsc);
}
static void
lrmd_stonith_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
if ((data == NULL) || (data->userdata == NULL)) {
crm_err("Ignoring fence action result: "
"Invalid callback arguments (bug?)");
} else {
stonith_action_complete((lrmd_cmd_t *) data->userdata,
stonith__exit_status(data),
stonith__execution_status(data),
stonith__exit_reason(data));
}
}
void
stonith_connection_failed(void)
{
GHashTableIter iter;
lrmd_rsc_t *rsc = NULL;
crm_warn("Connection to fencer lost (any pending operations for "
"fence devices will be considered failed)");
g_hash_table_iter_init(&iter, rsc_list);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &rsc)) {
if (!pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
pcmk__str_none)) {
continue;
}
/* If we registered this fence device, we don't know whether the
* fencer still has the registration or not. Cause future probes to
* return an error until the resource is stopped or started
* successfully. This is especially important if the controller also
* went away (possibly due to a cluster layer restart) and won't
* receive our client notification of any monitors finalized below.
*/
if (rsc->fence_probe_result.execution_status == PCMK_EXEC_DONE) {
pcmk__set_result(&rsc->fence_probe_result, CRM_EX_ERROR,
PCMK_EXEC_NOT_CONNECTED,
"Lost connection to fencer");
}
// Consider any active, pending, or recurring operations as failed
for (GList *op = rsc->recurring_ops; op != NULL; op = op->next) {
lrmd_cmd_t *cmd = op->data;
/* This won't free a recurring op but instead restart its timer.
* If cmd is rsc->active, this will set rsc->active to NULL, so we
* don't have to worry about finalizing it a second time below.
*/
stonith_action_complete(cmd,
CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
"Lost connection to fencer");
}
if (rsc->active != NULL) {
rsc->pending_ops = g_list_prepend(rsc->pending_ops, rsc->active);
}
while (rsc->pending_ops != NULL) {
// This will free the op and remove it from rsc->pending_ops
stonith_action_complete((lrmd_cmd_t *) rsc->pending_ops->data,
CRM_EX_ERROR, PCMK_EXEC_NOT_CONNECTED,
"Lost connection to fencer");
}
}
}
/*!
* \internal
* \brief Execute a stonith resource "start" action
*
* Start a stonith resource by registering it with the fencer.
* (Stonith agents don't have a start command.)
*
* \param[in,out] stonith_api Connection to fencer
* \param[in] rsc Stonith resource to start
* \param[in] cmd Start command to execute
*
* \return pcmk_ok on success, -errno otherwise
*/
static int
execd_stonith_start(stonith_t *stonith_api, const lrmd_rsc_t *rsc,
const lrmd_cmd_t *cmd)
{
char *key = NULL;
char *value = NULL;
stonith_key_value_t *device_params = NULL;
int rc = pcmk_ok;
// Convert command parameters to stonith API key/values
if (cmd->params) {
GHashTableIter iter;
g_hash_table_iter_init(&iter, cmd->params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
device_params = stonith__key_value_add(device_params, key, value);
}
}
/* The fencer will automatically register devices via CIB notifications
* when the CIB changes, but to avoid a possible race condition between
* the fencer receiving the notification and the executor requesting that
* resource, the executor registers the device as well. The fencer knows how
* to handle duplicate registrations.
*/
rc = stonith_api->cmds->register_device(stonith_api, st_opt_sync_call,
cmd->rsc_id, rsc->provider,
rsc->type, device_params);
- stonith_key_value_freeall(device_params, 1, 1);
+ stonith__key_value_freeall(device_params, true, true);
return rc;
}
/*!
* \internal
* \brief Execute a stonith resource "stop" action
*
* Stop a stonith resource by unregistering it with the fencer.
* (Stonith agents don't have a stop command.)
*
* \param[in,out] stonith_api Connection to fencer
* \param[in] rsc Stonith resource to stop
*
* \return pcmk_ok on success, -errno otherwise
*/
static inline int
execd_stonith_stop(stonith_t *stonith_api, const lrmd_rsc_t *rsc)
{
/* @TODO Failure would indicate a problem communicating with fencer;
* perhaps we should try reconnecting and retrying a few times?
*/
return stonith_api->cmds->remove_device(stonith_api, st_opt_sync_call,
rsc->rsc_id);
}
/*!
* \internal
* \brief Initiate a stonith resource agent recurring "monitor" action
*
* \param[in,out] stonith_api Connection to fencer
* \param[in,out] rsc Stonith resource to monitor
* \param[in] cmd Monitor command being executed
*
* \return pcmk_ok if monitor was successfully initiated, -errno otherwise
*/
static inline int
execd_stonith_monitor(stonith_t *stonith_api, lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
{
int rc = stonith_api->cmds->monitor(stonith_api, 0, cmd->rsc_id,
pcmk__timeout_ms2s(cmd->timeout));
rc = stonith_api->cmds->register_callback(stonith_api, rc, 0, 0, cmd,
"lrmd_stonith_callback",
lrmd_stonith_callback);
if (rc == TRUE) {
rsc->active = cmd;
rc = pcmk_ok;
} else {
rc = -pcmk_err_generic;
}
return rc;
}
static void
execute_stonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
{
int rc = pcmk_ok;
const char *rc_s = NULL;
bool do_monitor = false;
// Don't free; belongs to pacemaker-execd.c
stonith_t *stonith_api = get_stonith_connection();
if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR, pcmk__str_casei)
&& (cmd->interval_ms == 0)) {
// Probes don't require a fencer connection
stonith_action_complete(cmd, rsc->fence_probe_result.exit_status,
rsc->fence_probe_result.execution_status,
rsc->fence_probe_result.exit_reason);
return;
}
if (stonith_api == NULL) {
stonith_action_complete(cmd, PCMK_OCF_UNKNOWN_ERROR,
PCMK_EXEC_NOT_CONNECTED,
"No connection to fencer");
return;
}
if (pcmk__str_eq(cmd->action, PCMK_ACTION_START, pcmk__str_casei)) {
rc = execd_stonith_start(stonith_api, rsc, cmd);
if (rc == pcmk_ok) {
do_monitor = true;
}
} else if (pcmk__str_eq(cmd->action, PCMK_ACTION_STOP, pcmk__str_casei)) {
rc = execd_stonith_stop(stonith_api, rsc);
} else if (pcmk__str_eq(cmd->action, PCMK_ACTION_MONITOR,
pcmk__str_casei)) {
do_monitor = true;
} else {
stonith_action_complete(cmd, PCMK_OCF_UNIMPLEMENT_FEATURE,
PCMK_EXEC_ERROR,
"Invalid fence device action (bug?)");
return;
}
if (do_monitor) {
rc = execd_stonith_monitor(stonith_api, rsc, cmd);
if (rc == pcmk_ok) {
// Don't clean up yet. We will get the result of the monitor later.
return;
}
}
if (rc != -pcmk_err_generic) {
rc_s = pcmk_strerror(rc);
}
stonith_action_complete(cmd,
((rc == pcmk_rc_ok)? CRM_EX_OK : CRM_EX_ERROR),
stonith__legacy2status(rc), rc_s);
}
static void
execute_nonstonith_action(lrmd_rsc_t *rsc, lrmd_cmd_t *cmd)
{
svc_action_t *action = NULL;
GHashTable *params_copy = NULL;
pcmk__assert((rsc != NULL) && (cmd != NULL));
crm_trace("Creating action, resource:%s action:%s class:%s provider:%s agent:%s",
rsc->rsc_id, cmd->action, rsc->class, rsc->provider, rsc->type);
params_copy = pcmk__str_table_dup(cmd->params);
action = services__create_resource_action(rsc->rsc_id, rsc->class, rsc->provider,
rsc->type,
normalize_action_name(rsc, cmd->action),
cmd->interval_ms, cmd->timeout,
params_copy, cmd->service_flags);
if (action == NULL) {
pcmk__set_result(&(cmd->result), PCMK_OCF_UNKNOWN_ERROR,
PCMK_EXEC_ERROR, strerror(ENOMEM));
cmd_finalize(cmd, rsc);
return;
}
if (action->rc != PCMK_OCF_UNKNOWN) {
services__copy_result(action, &(cmd->result));
services_action_free(action);
cmd_finalize(cmd, rsc);
return;
}
action->cb_data = cmd;
if (services_action_async(action, action_complete)) {
/* The services library has taken responsibility for the action. It
* could be pending, blocked, or merged into a duplicate recurring
* action, in which case the action callback (action_complete())
* will be called when the action completes, otherwise the callback has
* already been called.
*
* action_complete() calls cmd_finalize() which can free cmd, so cmd
* cannot be used here.
*/
} else {
/* This is a recurring action that is not being cancelled and could not
* be initiated. It has been rescheduled, and the action callback
* (action_complete()) has been called, which in this case has already
* called cmd_finalize(), which in this case should only reset (not
* free) cmd.
*/
services__copy_result(action, &(cmd->result));
services_action_free(action);
}
}
static gboolean
execute_resource_action(gpointer user_data)
{
lrmd_rsc_t *rsc = (lrmd_rsc_t *) user_data;
lrmd_cmd_t *cmd = NULL;
CRM_CHECK(rsc != NULL, return FALSE);
if (rsc->active) {
crm_trace("%s is still active", rsc->rsc_id);
return TRUE;
}
if (rsc->pending_ops) {
GList *first = rsc->pending_ops;
cmd = first->data;
if (cmd->delay_id) {
crm_trace
("Command %s %s was asked to run too early, waiting for start_delay timeout of %dms",
cmd->rsc_id, cmd->action, cmd->start_delay);
return TRUE;
}
rsc->pending_ops = g_list_remove_link(rsc->pending_ops, first);
g_list_free_1(first);
#ifdef PCMK__TIME_USE_CGT
get_current_time(&(cmd->t_run), &(cmd->t_first_run));
#endif
cmd->epoch_last_run = time(NULL);
}
if (!cmd) {
crm_trace("Nothing further to do for %s", rsc->rsc_id);
return TRUE;
}
rsc->active = cmd; /* only one op at a time for a rsc */
if (cmd->interval_ms) {
rsc->recurring_ops = g_list_append(rsc->recurring_ops, cmd);
}
log_execute(cmd);
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
execute_stonith_action(rsc, cmd);
} else {
execute_nonstonith_action(rsc, cmd);
}
return TRUE;
}
void
free_rsc(gpointer data)
{
GList *gIter = NULL;
lrmd_rsc_t *rsc = data;
int is_stonith = pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH,
pcmk__str_casei);
gIter = rsc->pending_ops;
while (gIter != NULL) {
GList *next = gIter->next;
lrmd_cmd_t *cmd = gIter->data;
/* command was never executed */
cmd->result.execution_status = PCMK_EXEC_CANCELLED;
cmd_finalize(cmd, NULL);
gIter = next;
}
/* frees list, but not list elements. */
g_list_free(rsc->pending_ops);
gIter = rsc->recurring_ops;
while (gIter != NULL) {
GList *next = gIter->next;
lrmd_cmd_t *cmd = gIter->data;
if (is_stonith) {
cmd->result.execution_status = PCMK_EXEC_CANCELLED;
/* If a stonith command is in-flight, just mark it as cancelled;
* it is not safe to finalize/free the cmd until the stonith api
* says it has either completed or timed out.
*/
if (rsc->active != cmd) {
cmd_finalize(cmd, NULL);
}
} else {
/* This command is already handed off to service library,
* let service library cancel it and tell us via the callback
* when it is cancelled. The rsc can be safely destroyed
* even if we are waiting for the cancel result */
services_action_cancel(rsc->rsc_id,
normalize_action_name(rsc, cmd->action),
cmd->interval_ms);
}
gIter = next;
}
/* frees list, but not list elements. */
g_list_free(rsc->recurring_ops);
free(rsc->rsc_id);
free(rsc->class);
free(rsc->provider);
free(rsc->type);
mainloop_destroy_trigger(rsc->work);
free(rsc);
}
static int
process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id,
xmlNode **reply)
{
int rc = pcmk_ok;
time_t now = time(NULL);
const char *protocol_version =
crm_element_value(request, PCMK__XA_LRMD_PROTOCOL_VERSION);
const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE);
if (compare_version(protocol_version, LRMD_COMPATIBLE_PROTOCOL) < 0) {
crm_err("Cluster API version must be greater than or equal to %s, not %s",
LRMD_COMPATIBLE_PROTOCOL, protocol_version);
rc = -EPROTO;
}
if (pcmk__xe_attr_is_true(request, PCMK__XA_LRMD_IS_IPC_PROVIDER)) {
#ifdef PCMK__COMPILE_REMOTE
if ((client->remote != NULL)
&& pcmk_is_set(client->flags,
pcmk__client_tls_handshake_complete)) {
const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
// This is a remote connection from a cluster node's controller
ipc_proxy_add_provider(client);
/* @TODO Allowing multiple proxies makes no sense given that clients
* have no way to choose between them. Maybe always use the most
* recent one and switch any existing IPC connections to use it,
* by iterating over ipc_clients here, and if client->id doesn't
* match the client's userdata, replace the userdata with the new
* ID. After the iteration, call lrmd_remote_client_destroy() on any
* of the replaced values in ipc_providers.
*/
/* If this was a register operation, also ask for new schema files but
* only if it's supported by the protocol version.
*/
if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none) &&
LRMD_SUPPORTS_SCHEMA_XFER(protocol_version)) {
remoted_request_cib_schema_files();
}
} else {
rc = -EACCES;
}
#else
rc = -EPROTONOSUPPORT;
#endif
}
*reply = create_lrmd_reply(__func__, rc, call_id);
crm_xml_add(*reply, PCMK__XA_LRMD_OP, CRM_OP_REGISTER);
crm_xml_add(*reply, PCMK__XA_LRMD_CLIENTID, client->id);
crm_xml_add(*reply, PCMK__XA_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
crm_xml_add_ll(*reply, PCMK__XA_UPTIME, now - start_time);
if (start_state) {
crm_xml_add(*reply, PCMK__XA_NODE_START_STATE, start_state);
}
return rc;
}
static int
process_lrmd_rsc_register(pcmk__client_t *client, uint32_t id, xmlNode *request)
{
int rc = pcmk_ok;
lrmd_rsc_t *rsc = build_rsc_from_xml(request);
lrmd_rsc_t *dup = g_hash_table_lookup(rsc_list, rsc->rsc_id);
if (dup &&
pcmk__str_eq(rsc->class, dup->class, pcmk__str_casei) &&
pcmk__str_eq(rsc->provider, dup->provider, pcmk__str_casei) && pcmk__str_eq(rsc->type, dup->type, pcmk__str_casei)) {
crm_notice("Ignoring duplicate registration of '%s'", rsc->rsc_id);
free_rsc(rsc);
return rc;
}
g_hash_table_replace(rsc_list, rsc->rsc_id, rsc);
crm_info("Cached agent information for '%s'", rsc->rsc_id);
return rc;
}
static xmlNode *
process_lrmd_get_rsc_info(xmlNode *request, int call_id)
{
int rc = pcmk_ok;
xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
"//" PCMK__XE_LRMD_RSC,
LOG_ERR);
const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
xmlNode *reply = NULL;
lrmd_rsc_t *rsc = NULL;
if (rsc_id == NULL) {
rc = -ENODEV;
} else {
rsc = g_hash_table_lookup(rsc_list, rsc_id);
if (rsc == NULL) {
crm_info("Agent information for '%s' not in cache", rsc_id);
rc = -ENODEV;
}
}
reply = create_lrmd_reply(__func__, rc, call_id);
if (rsc) {
crm_xml_add(reply, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
crm_xml_add(reply, PCMK__XA_LRMD_CLASS, rsc->class);
crm_xml_add(reply, PCMK__XA_LRMD_PROVIDER, rsc->provider);
crm_xml_add(reply, PCMK__XA_LRMD_TYPE, rsc->type);
}
return reply;
}
static int
process_lrmd_rsc_unregister(pcmk__client_t *client, uint32_t id,
xmlNode *request)
{
int rc = pcmk_ok;
lrmd_rsc_t *rsc = NULL;
xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
"//" PCMK__XE_LRMD_RSC,
LOG_ERR);
const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
if (!rsc_id) {
return -ENODEV;
}
rsc = g_hash_table_lookup(rsc_list, rsc_id);
if (rsc == NULL) {
crm_info("Ignoring unregistration of resource '%s', which is not registered",
rsc_id);
return pcmk_ok;
}
if (rsc->active) {
/* let the caller know there are still active ops on this rsc to watch for */
crm_trace("Operation (%p) still in progress for unregistered resource %s",
rsc->active, rsc_id);
rc = -EINPROGRESS;
}
g_hash_table_remove(rsc_list, rsc_id);
return rc;
}
static int
process_lrmd_rsc_exec(pcmk__client_t *client, uint32_t id, xmlNode *request)
{
lrmd_rsc_t *rsc = NULL;
lrmd_cmd_t *cmd = NULL;
xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
"//" PCMK__XE_LRMD_RSC,
LOG_ERR);
const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
int call_id;
if (!rsc_id) {
return -EINVAL;
}
if (!(rsc = g_hash_table_lookup(rsc_list, rsc_id))) {
crm_info("Resource '%s' not found (%d active resources)",
rsc_id, g_hash_table_size(rsc_list));
return -ENODEV;
}
cmd = create_lrmd_cmd(request, client);
call_id = cmd->call_id;
/* Don't reference cmd after handing it off to be scheduled.
* The cmd could get merged and freed. */
schedule_lrmd_cmd(rsc, cmd);
return call_id;
}
static int
cancel_op(const char *rsc_id, const char *action, guint interval_ms)
{
GList *gIter = NULL;
lrmd_rsc_t *rsc = g_hash_table_lookup(rsc_list, rsc_id);
/* How to cancel an action.
* 1. Check pending ops list, if it hasn't been handed off
* to the service library or stonith recurring list remove
* it there and that will stop it.
* 2. If it isn't in the pending ops list, then it's either a
* recurring op in the stonith recurring list, or the service
* library's recurring list. Stop it there
* 3. If not found in any lists, then this operation has either
* been executed already and is not a recurring operation, or
* never existed.
*/
if (!rsc) {
return -ENODEV;
}
for (gIter = rsc->pending_ops; gIter != NULL; gIter = gIter->next) {
lrmd_cmd_t *cmd = gIter->data;
if (action_matches(cmd, action, interval_ms)) {
cmd->result.execution_status = PCMK_EXEC_CANCELLED;
cmd_finalize(cmd, rsc);
return pcmk_ok;
}
}
if (pcmk__str_eq(rsc->class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
/* The service library does not handle stonith operations.
* We have to handle recurring stonith operations ourselves. */
for (gIter = rsc->recurring_ops; gIter != NULL; gIter = gIter->next) {
lrmd_cmd_t *cmd = gIter->data;
if (action_matches(cmd, action, interval_ms)) {
cmd->result.execution_status = PCMK_EXEC_CANCELLED;
if (rsc->active != cmd) {
cmd_finalize(cmd, rsc);
}
return pcmk_ok;
}
}
} else if (services_action_cancel(rsc_id,
normalize_action_name(rsc, action),
interval_ms) == TRUE) {
/* The service library will tell the action_complete callback function
* this action was cancelled, which will destroy the cmd and remove
* it from the recurring_op list. Do not do that in this function
* if the service library says it cancelled it. */
return pcmk_ok;
}
return -EOPNOTSUPP;
}
static void
cancel_all_recurring(lrmd_rsc_t * rsc, const char *client_id)
{
GList *cmd_list = NULL;
GList *cmd_iter = NULL;
/* Notice a copy of each list is created when concat is called.
* This prevents odd behavior from occurring when the cmd_list
* is iterated through later on. It is possible the cancel_op
* function may end up modifying the recurring_ops and pending_ops
* lists. If we did not copy those lists, our cmd_list iteration
* could get messed up.*/
if (rsc->recurring_ops) {
cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->recurring_ops));
}
if (rsc->pending_ops) {
cmd_list = g_list_concat(cmd_list, g_list_copy(rsc->pending_ops));
}
if (!cmd_list) {
return;
}
for (cmd_iter = cmd_list; cmd_iter; cmd_iter = cmd_iter->next) {
lrmd_cmd_t *cmd = cmd_iter->data;
if (cmd->interval_ms == 0) {
continue;
}
if (client_id && !pcmk__str_eq(cmd->client_id, client_id, pcmk__str_casei)) {
continue;
}
cancel_op(rsc->rsc_id, cmd->action, cmd->interval_ms);
}
/* frees only the copied list data, not the cmds */
g_list_free(cmd_list);
}
static int
process_lrmd_rsc_cancel(pcmk__client_t *client, uint32_t id, xmlNode *request)
{
xmlNode *rsc_xml = pcmk__xpath_find_one(request->doc,
"//" PCMK__XE_LRMD_RSC,
LOG_ERR);
const char *rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
const char *action = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ACTION);
guint interval_ms = 0;
crm_element_value_ms(rsc_xml, PCMK__XA_LRMD_RSC_INTERVAL, &interval_ms);
if (!rsc_id || !action) {
return -EINVAL;
}
return cancel_op(rsc_id, action, interval_ms);
}
static void
add_recurring_op_xml(xmlNode *reply, lrmd_rsc_t *rsc)
{
xmlNode *rsc_xml = pcmk__xe_create(reply, PCMK__XE_LRMD_RSC);
crm_xml_add(rsc_xml, PCMK__XA_LRMD_RSC_ID, rsc->rsc_id);
for (GList *item = rsc->recurring_ops; item != NULL; item = item->next) {
lrmd_cmd_t *cmd = item->data;
xmlNode *op_xml = pcmk__xe_create(rsc_xml, PCMK__XE_LRMD_RSC_OP);
crm_xml_add(op_xml, PCMK__XA_LRMD_RSC_ACTION,
pcmk__s(cmd->real_action, cmd->action));
crm_xml_add_ms(op_xml, PCMK__XA_LRMD_RSC_INTERVAL, cmd->interval_ms);
crm_xml_add_int(op_xml, PCMK__XA_LRMD_TIMEOUT, cmd->timeout_orig);
}
}
static xmlNode *
process_lrmd_get_recurring(xmlNode *request, int call_id)
{
int rc = pcmk_ok;
const char *rsc_id = NULL;
lrmd_rsc_t *rsc = NULL;
xmlNode *reply = NULL;
xmlNode *rsc_xml = NULL;
// Resource ID is optional
rsc_xml = pcmk__xe_first_child(request, PCMK__XE_LRMD_CALLDATA, NULL, NULL);
if (rsc_xml) {
rsc_xml = pcmk__xe_first_child(rsc_xml, PCMK__XE_LRMD_RSC, NULL, NULL);
}
if (rsc_xml) {
rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
}
// If resource ID is specified, resource must exist
if (rsc_id != NULL) {
rsc = g_hash_table_lookup(rsc_list, rsc_id);
if (rsc == NULL) {
crm_info("Resource '%s' not found (%d active resources)",
rsc_id, g_hash_table_size(rsc_list));
rc = -ENODEV;
}
}
reply = create_lrmd_reply(__func__, rc, call_id);
// If resource ID is not specified, check all resources
if (rsc_id == NULL) {
GHashTableIter iter;
char *key = NULL;
g_hash_table_iter_init(&iter, rsc_list);
while (g_hash_table_iter_next(&iter, (gpointer *) &key,
(gpointer *) &rsc)) {
add_recurring_op_xml(reply, rsc);
}
} else if (rsc) {
add_recurring_op_xml(reply, rsc);
}
return reply;
}
void
process_lrmd_message(pcmk__client_t *client, uint32_t id, xmlNode *request)
{
int rc = pcmk_ok;
int call_id = 0;
const char *op = crm_element_value(request, PCMK__XA_LRMD_OP);
int do_reply = 0;
int do_notify = 0;
xmlNode *reply = NULL;
/* Certain IPC commands may be done only by privileged users (i.e. root or
* hacluster), because they would otherwise provide a means of bypassing
* ACLs.
*/
bool allowed = pcmk_is_set(client->flags, pcmk__client_privileged);
crm_trace("Processing %s operation from %s", op, client->id);
crm_element_value_int(request, PCMK__XA_LRMD_CALLID, &call_id);
if (pcmk__str_eq(op, CRM_OP_IPC_FWD, pcmk__str_none)) {
#ifdef PCMK__COMPILE_REMOTE
if (allowed) {
ipc_proxy_forward_client(client, request);
} else {
rc = -EACCES;
}
#else
rc = -EPROTONOSUPPORT;
#endif
do_reply = 1;
} else if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) {
rc = process_lrmd_signon(client, request, call_id, &reply);
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_RSC_REG, pcmk__str_none)) {
if (allowed) {
rc = process_lrmd_rsc_register(client, id, request);
do_notify = 1;
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_RSC_INFO, pcmk__str_none)) {
if (allowed) {
reply = process_lrmd_get_rsc_info(request, call_id);
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
if (allowed) {
rc = process_lrmd_rsc_unregister(client, id, request);
/* don't notify anyone about failed un-registers */
if (rc == pcmk_ok || rc == -EINPROGRESS) {
do_notify = 1;
}
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_RSC_EXEC, pcmk__str_none)) {
if (allowed) {
rc = process_lrmd_rsc_exec(client, id, request);
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_RSC_CANCEL, pcmk__str_none)) {
if (allowed) {
rc = process_lrmd_rsc_cancel(client, id, request);
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_POKE, pcmk__str_none)) {
do_notify = 1;
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_CHECK, pcmk__str_none)) {
if (allowed) {
xmlNode *wrapper = pcmk__xe_first_child(request,
PCMK__XE_LRMD_CALLDATA,
NULL, NULL);
xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
const char *timeout = NULL;
CRM_LOG_ASSERT(data != NULL);
timeout = crm_element_value(data, PCMK__XA_LRMD_WATCHDOG);
pcmk__valid_stonith_watchdog_timeout(timeout);
} else {
rc = -EACCES;
}
} else if (pcmk__str_eq(op, LRMD_OP_ALERT_EXEC, pcmk__str_none)) {
if (allowed) {
rc = process_lrmd_alert_exec(client, id, request);
} else {
rc = -EACCES;
}
do_reply = 1;
} else if (pcmk__str_eq(op, LRMD_OP_GET_RECURRING, pcmk__str_none)) {
if (allowed) {
reply = process_lrmd_get_recurring(request, call_id);
} else {
rc = -EACCES;
}
do_reply = 1;
} else {
rc = -EOPNOTSUPP;
do_reply = 1;
crm_err("Unknown IPC request '%s' from client %s",
op, pcmk__client_name(client));
}
if (rc == -EACCES) {
crm_warn("Rejecting IPC request '%s' from unprivileged client %s",
op, pcmk__client_name(client));
}
crm_debug("Processed %s operation from %s: rc=%d, reply=%d, notify=%d",
op, client->id, rc, do_reply, do_notify);
if (do_reply) {
int send_rc = pcmk_rc_ok;
if (reply == NULL) {
reply = create_lrmd_reply(__func__, rc, call_id);
}
send_rc = lrmd_server_send_reply(client, id, reply);
pcmk__xml_free(reply);
if (send_rc != pcmk_rc_ok) {
crm_warn("Reply to client %s failed: %s " QB_XS " rc=%d",
pcmk__client_name(client), pcmk_rc_str(send_rc), send_rc);
}
}
if (do_notify) {
send_generic_notify(rc, request);
}
}
diff --git a/daemons/fenced/cts-fence-helper.c b/daemons/fenced/cts-fence-helper.c
index d05eb09e73..5d69f1444b 100644
--- a/daemons/fenced/cts-fence-helper.c
+++ b/daemons/fenced/cts-fence-helper.c
@@ -1,664 +1,664 @@
/*
* Copyright 2009-2025 the Pacemaker project contributors
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/common/ipc.h>
#include <crm/cluster/internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/common/agents.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#define SUMMARY "cts-fence-helper - inject commands into the Pacemaker fencer and watch for events"
static GMainLoop *mainloop = NULL;
static crm_trigger_t *trig = NULL;
static int mainloop_iter = 0;
static pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
typedef void (*mainloop_test_iteration_cb) (int check_event);
#define MAINLOOP_DEFAULT_TIMEOUT 2
enum test_modes {
test_standard = 0, // test using a specific developer environment
test_api_sanity, // sanity-test stonith client API using fence_dummy
test_api_mainloop, // sanity-test mainloop code with async responses
};
struct {
enum test_modes mode;
} options = {
.mode = test_standard
};
static gboolean
mode_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
if (pcmk__str_any_of(option_name, "--mainloop_api_test", "-m", NULL)) {
options.mode = test_api_mainloop;
} else if (pcmk__str_any_of(option_name, "--api_test", "-t", NULL)) {
options.mode = test_api_sanity;
}
return TRUE;
}
static GOptionEntry entries[] = {
{ "mainloop_api_test", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
NULL, NULL,
},
{ "api_test", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, mode_cb,
NULL, NULL,
},
{ NULL }
};
static stonith_t *st = NULL;
static struct pollfd pollfd;
static const int st_opts = st_opt_sync_call;
static int expected_notifications = 0;
static int verbose = 0;
static void
mainloop_test_done(const char *origin, bool pass)
{
if (pass) {
crm_info("SUCCESS - %s", origin);
mainloop_iter++;
mainloop_set_trigger(trig);
result.execution_status = PCMK_EXEC_DONE;
result.exit_status = CRM_EX_OK;
} else {
crm_err("FAILURE - %s (%d: %s)", origin, result.exit_status,
pcmk_exec_status_str(result.execution_status));
crm_exit(CRM_EX_ERROR);
}
}
static void
dispatch_helper(int timeout)
{
int rc;
crm_debug("Looking for notification");
pollfd.events = POLLIN;
while (true) {
rc = poll(&pollfd, 1, timeout); /* wait 10 minutes, -1 forever */
if (rc > 0) {
if (stonith__api_dispatch(st) != pcmk_rc_ok) {
break;
}
} else {
break;
}
}
}
static void
st_callback(stonith_t * st, stonith_event_t * e)
{
char *desc = NULL;
if (st->state == stonith_disconnected) {
crm_exit(CRM_EX_DISCONNECT);
}
desc = stonith__event_description(e);
crm_notice("%s", desc);
free(desc);
if (expected_notifications) {
expected_notifications--;
}
}
static void
st_global_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
crm_notice("Call %d exited %d: %s (%s)",
data->call_id, stonith__exit_status(data),
stonith__execution_status(data),
pcmk__s(stonith__exit_reason(data), "unspecified reason"));
}
#define single_test(cmd, str, num_notifications, expected_rc) \
{ \
int rc = 0; \
rc = cmd; \
expected_notifications = 0; \
if (num_notifications) { \
expected_notifications = num_notifications; \
dispatch_helper(500); \
} \
if (rc != expected_rc) { \
crm_err("FAILURE - expected rc %d != %d(%s) for cmd - %s", expected_rc, rc, pcmk_strerror(rc), str); \
crm_exit(CRM_EX_ERROR); \
} else if (expected_notifications) { \
crm_err("FAILURE - expected %d notifications, got only %d for cmd - %s", \
num_notifications, num_notifications - expected_notifications, str); \
crm_exit(CRM_EX_ERROR); \
} else { \
if (verbose) { \
crm_info("SUCCESS - %s: %d", str, rc); \
} else { \
crm_debug("SUCCESS - %s: %d", str, rc); \
} \
} \
}\
static void
run_fence_failure_test(void)
{
stonith_key_value_t *params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"false_1_node1=1,2 false_1_node2=3,4");
params = stonith__key_value_add(params, "mode", "fail");
single_test(st->
cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params),
"Register device1 for failure test", 1, 0);
single_test(st->cmds->fence(st, st_opts, "false_1_node2", PCMK_ACTION_OFF,
3, 0),
"Fence failure results off", 1, -ENODATA);
single_test(st->cmds->fence(st, st_opts, "false_1_node2",
PCMK_ACTION_REBOOT, 3, 0),
"Fence failure results reboot", 1, -ENODATA);
single_test(st->cmds->remove_device(st, st_opts, "test-id1"),
"Remove device1 for failure test", 1, 0);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
}
static void
run_fence_failure_rollover_test(void)
{
stonith_key_value_t *params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"false_1_node1=1,2 false_1_node2=3,4");
params = stonith__key_value_add(params, "mode", "fail");
single_test(st->
cmds->register_device(st, st_opts, "test-id1", "stonith-ng", "fence_dummy", params),
"Register device1 for rollover test", 1, 0);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"false_1_node1=1,2 false_1_node2=3,4");
params = stonith__key_value_add(params, "mode", "pass");
single_test(st->
cmds->register_device(st, st_opts, "test-id2", "stonith-ng", "fence_dummy", params),
"Register device2 for rollover test", 1, 0);
single_test(st->cmds->fence(st, st_opts, "false_1_node2", PCMK_ACTION_OFF,
3, 0),
"Fence rollover results off", 1, 0);
/* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */
single_test(st->cmds->fence(st, st_opts, "false_1_node2", PCMK_ACTION_ON, 3,
0),
"Fence rollover results on", 1, -ENODEV);
single_test(st->cmds->remove_device(st, st_opts, "test-id1"),
"Remove device1 for rollover tests", 1, 0);
single_test(st->cmds->remove_device(st, st_opts, "test-id2"),
"Remove device2 for rollover tests", 1, 0);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
}
static void
run_standard_test(void)
{
stonith_key_value_t *params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"false_1_node1=1,2 false_1_node2=3,4");
params = stonith__key_value_add(params, "mode", "pass");
params = stonith__key_value_add(params, "mock_dynamic_hosts",
"false_1_node1 false_1_node2");
single_test(st->
cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_dummy", params),
"Register", 1, 0);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
params = NULL;
single_test(st->cmds->list(st, st_opts, "test-id", NULL, 1),
PCMK_ACTION_LIST, 0, 0);
single_test(st->cmds->monitor(st, st_opts, "test-id", 1), "Monitor", 0, 0);
single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node2", 1),
"Status false_1_node2", 0, 0);
single_test(st->cmds->status(st, st_opts, "test-id", "false_1_node1", 1),
"Status false_1_node1", 0, 0);
single_test(st->cmds->fence(st, st_opts, "unknown-host", PCMK_ACTION_OFF,
1, 0),
"Fence unknown-host (expected failure)", 0, -ENODEV);
single_test(st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_OFF,
1, 0),
"Fence false_1_node1", 1, 0);
/* Expect -ENODEV because fence_dummy requires 'on' to be executed on target */
single_test(st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_ON, 1,
0),
"Unfence false_1_node1", 1, -ENODEV);
/* Confirm that an invalid level index is rejected */
single_test(st->cmds->register_level(st, st_opts, "node1", 999, params),
"Attempt to register an invalid level index", 0, -EINVAL);
single_test(st->cmds->remove_device(st, st_opts, "test-id"), "Remove test-id", 1, 0);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
}
static void
sanity_tests(void)
{
int rc = 0;
rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
if (rc != pcmk_ok) {
stonith__api_free(st);
crm_exit(CRM_EX_DISCONNECT);
}
st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_DISCONNECT,
st_callback);
st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_FENCE,
st_callback);
st->cmds->register_notification(st, STONITH_OP_DEVICE_ADD, st_callback);
st->cmds->register_notification(st, STONITH_OP_DEVICE_DEL, st_callback);
st->cmds->register_callback(st, 0, 120, st_opt_timeout_updates, NULL, "st_global_callback",
st_global_callback);
crm_info("Starting API Sanity Tests");
run_standard_test();
run_fence_failure_test();
run_fence_failure_rollover_test();
crm_info("Sanity Tests Passed");
}
static void
standard_dev_test(void)
{
int rc = 0;
char *tmp = NULL;
stonith_key_value_t *params = NULL;
rc = st->cmds->connect(st, crm_system_name, &pollfd.fd);
if (rc != pcmk_ok) {
stonith__api_free(st);
crm_exit(CRM_EX_DISCONNECT);
}
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"some-host=pcmk-7 true_1_node1=3,4");
rc = st->cmds->register_device(st, st_opts, "test-id", "stonith-ng", "fence_xvm", params);
crm_debug("Register: %d", rc);
rc = st->cmds->list(st, st_opts, "test-id", &tmp, 10);
crm_debug("List: %d output: %s", rc, tmp ? tmp : "<none>");
rc = st->cmds->monitor(st, st_opts, "test-id", 10);
crm_debug("Monitor: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "false_1_node2", 10);
crm_debug("Status false_1_node2: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
crm_debug("Status false_1_node1: %d", rc);
rc = st->cmds->fence(st, st_opts, "unknown-host", PCMK_ACTION_OFF, 60, 0);
crm_debug("Fence unknown-host: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
crm_debug("Status false_1_node1: %d", rc);
rc = st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_OFF, 60, 0);
crm_debug("Fence false_1_node1: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
crm_debug("Status false_1_node1: %d", rc);
rc = st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_ON, 10, 0);
crm_debug("Unfence false_1_node1: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "false_1_node1", 10);
crm_debug("Status false_1_node1: %d", rc);
rc = st->cmds->fence(st, st_opts, "some-host", PCMK_ACTION_OFF, 10, 0);
crm_debug("Fence alias: %d", rc);
rc = st->cmds->status(st, st_opts, "test-id", "some-host", 10);
crm_debug("Status alias: %d", rc);
rc = st->cmds->fence(st, st_opts, "false_1_node1", PCMK_ACTION_ON, 10, 0);
crm_debug("Unfence false_1_node1: %d", rc);
rc = st->cmds->remove_device(st, st_opts, "test-id");
crm_debug("Remove test-id: %d", rc);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
}
static void
iterate_mainloop_tests(gboolean event_ready);
static void
mainloop_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
pcmk__set_result(&result, stonith__exit_status(data),
stonith__execution_status(data),
stonith__exit_reason(data));
iterate_mainloop_tests(TRUE);
}
static int
register_callback_helper(int callid)
{
return st->cmds->register_callback(st,
callid,
MAINLOOP_DEFAULT_TIMEOUT,
st_opt_timeout_updates, NULL, "callback", mainloop_callback);
}
static void
test_async_fence_pass(int check_event)
{
int rc = 0;
if (check_event) {
mainloop_test_done(__func__, (result.exit_status == CRM_EX_OK));
return;
}
rc = st->cmds->fence(st, 0, "true_1_node1", PCMK_ACTION_OFF,
MAINLOOP_DEFAULT_TIMEOUT, 0);
if (rc < 0) {
crm_err("fence failed with rc %d", rc);
mainloop_test_done(__func__, false);
}
register_callback_helper(rc);
/* wait for event */
}
#define CUSTOM_TIMEOUT_ADDITION 10
static void
test_async_fence_custom_timeout(int check_event)
{
int rc = 0;
static time_t begin = 0;
if (check_event) {
uint32_t diff = (time(NULL) - begin);
if (result.execution_status != PCMK_EXEC_TIMEOUT) {
mainloop_test_done(__func__, false);
} else if (diff < CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT) {
crm_err
("Custom timeout test failed, callback expiration should be updated to %d, actual timeout was %d",
CUSTOM_TIMEOUT_ADDITION + MAINLOOP_DEFAULT_TIMEOUT, diff);
mainloop_test_done(__func__, false);
} else {
mainloop_test_done(__func__, true);
}
return;
}
begin = time(NULL);
rc = st->cmds->fence(st, 0, "custom_timeout_node1", PCMK_ACTION_OFF,
MAINLOOP_DEFAULT_TIMEOUT, 0);
if (rc < 0) {
crm_err("fence failed with rc %d", rc);
mainloop_test_done(__func__, false);
}
register_callback_helper(rc);
/* wait for event */
}
static void
test_async_fence_timeout(int check_event)
{
int rc = 0;
if (check_event) {
mainloop_test_done(__func__,
(result.execution_status == PCMK_EXEC_NO_FENCE_DEVICE));
return;
}
rc = st->cmds->fence(st, 0, "false_1_node2", PCMK_ACTION_OFF,
MAINLOOP_DEFAULT_TIMEOUT, 0);
if (rc < 0) {
crm_err("fence failed with rc %d", rc);
mainloop_test_done(__func__, false);
}
register_callback_helper(rc);
/* wait for event */
}
static void
test_async_monitor(int check_event)
{
int rc = 0;
if (check_event) {
mainloop_test_done(__func__, (result.exit_status == CRM_EX_OK));
return;
}
rc = st->cmds->monitor(st, 0, "false_1", MAINLOOP_DEFAULT_TIMEOUT);
if (rc < 0) {
crm_err("monitor failed with rc %d", rc);
mainloop_test_done(__func__, false);
}
register_callback_helper(rc);
/* wait for event */
}
static void
test_register_async_devices(int check_event)
{
char buf[16] = { 0, };
stonith_key_value_t *params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"false_1_node1=1,2");
params = stonith__key_value_add(params, "mode", "fail");
st->cmds->register_device(st, st_opts, "false_1", "stonith-ng", "fence_dummy", params);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"true_1_node1=1,2");
params = stonith__key_value_add(params, "mode", "pass");
st->cmds->register_device(st, st_opts, "true_1", "stonith-ng", "fence_dummy", params);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
params = NULL;
params = stonith__key_value_add(params, PCMK_STONITH_HOST_MAP,
"custom_timeout_node1=1,2");
params = stonith__key_value_add(params, "mode", "fail");
params = stonith__key_value_add(params, "delay", "1000");
snprintf(buf, sizeof(buf) - 1, "%d", MAINLOOP_DEFAULT_TIMEOUT + CUSTOM_TIMEOUT_ADDITION);
params = stonith__key_value_add(params, "pcmk_off_timeout", buf);
st->cmds->register_device(st, st_opts, "false_custom_timeout", "stonith-ng", "fence_dummy",
params);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
mainloop_test_done(__func__, true);
}
static void
try_mainloop_connect(int check_event)
{
int rc = stonith_api_connect_retry(st, crm_system_name, 10);
if (rc == pcmk_ok) {
mainloop_test_done(__func__, true);
return;
}
crm_err("API CONNECTION FAILURE");
mainloop_test_done(__func__, false);
}
static void
iterate_mainloop_tests(gboolean event_ready)
{
static mainloop_test_iteration_cb callbacks[] = {
try_mainloop_connect,
test_register_async_devices,
test_async_monitor,
test_async_fence_pass,
test_async_fence_timeout,
test_async_fence_custom_timeout,
};
if (mainloop_iter == (sizeof(callbacks) / sizeof(mainloop_test_iteration_cb))) {
/* all tests ran, everything passed */
crm_info("ALL MAINLOOP TESTS PASSED!");
crm_exit(CRM_EX_OK);
}
callbacks[mainloop_iter] (event_ready);
}
static gboolean
trigger_iterate_mainloop_tests(gpointer user_data)
{
iterate_mainloop_tests(FALSE);
return TRUE;
}
static void
test_shutdown(int nsig)
{
int rc = 0;
if (st) {
rc = st->cmds->disconnect(st);
crm_info("Disconnect: %d", rc);
crm_debug("Destroy");
stonith__api_free(st);
}
if (rc) {
crm_exit(CRM_EX_ERROR);
}
}
static void
mainloop_tests(void)
{
trig = mainloop_add_trigger(G_PRIORITY_HIGH, trigger_iterate_mainloop_tests, NULL);
mainloop_set_trigger(trig);
mainloop_add_signal(SIGTERM, test_shutdown);
crm_info("Starting");
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
context = pcmk__build_arg_context(args, NULL, group, NULL);
pcmk__add_main_args(context, entries);
return context;
}
int
main(int argc, char **argv)
{
GError *error = NULL;
crm_exit_t exit_code = CRM_EX_OK;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
GOptionContext *context = build_arg_context(args, NULL);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
/* We have to use crm_log_init here to set up the logging because there's
* different handling for daemons vs. command line programs, and
* pcmk__cli_init_logging is set up to only handle the latter.
*/
crm_log_init(NULL, LOG_INFO, TRUE, (verbose? TRUE : FALSE), argc, argv,
FALSE);
for (int i = 0; i < args->verbosity; i++) {
crm_bump_log_level(argc, argv);
}
st = stonith__api_new();
if (st == NULL) {
exit_code = CRM_EX_DISCONNECT;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Could not connect to fencer: API memory allocation failed");
goto done;
}
switch (options.mode) {
case test_standard:
standard_dev_test();
break;
case test_api_sanity:
sanity_tests();
break;
case test_api_mainloop:
mainloop_tests();
break;
}
test_shutdown(0);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
pcmk__output_and_clear_error(&error, NULL);
crm_exit(exit_code);
}
diff --git a/daemons/fenced/fenced_scheduler.c b/daemons/fenced/fenced_scheduler.c
index 14e3c7e2b0..00a1952c17 100644
--- a/daemons/fenced/fenced_scheduler.c
+++ b/daemons/fenced/fenced_scheduler.c
@@ -1,258 +1,258 @@
/*
* Copyright 2009-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <errno.h>
#include <glib.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include <pacemaker-fenced.h>
// fenced_scheduler_run() assumes it's the only place scheduler->input gets set
static pcmk_scheduler_t *scheduler = NULL;
/*!
* \internal
* \brief Initialize scheduler data for fencer purposes
*
* \return Standard Pacemaker return code
*/
int
fenced_scheduler_init(void)
{
pcmk__output_t *logger = NULL;
int rc = pcmk__log_output_new(&logger);
if (rc != pcmk_rc_ok) {
return rc;
}
scheduler = pcmk_new_scheduler();
if (scheduler == NULL) {
pcmk__output_free(logger);
return ENOMEM;
}
pe__register_messages(logger);
pcmk__register_lib_messages(logger);
pcmk__output_set_log_level(logger, LOG_TRACE);
scheduler->priv->out = logger;
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Set the local node name for scheduling purposes
*
* \param[in] node_name Name to set as local node name
*/
void
fenced_set_local_node(const char *node_name)
{
pcmk__assert(scheduler != NULL);
scheduler->priv->local_node_name = pcmk__str_copy(node_name);
}
/*!
* \internal
* \brief Get the local node name
*
* \return Local node name
*/
const char *
fenced_get_local_node(void)
{
if (scheduler == NULL) {
return NULL;
}
return scheduler->priv->local_node_name;
}
/*!
* \internal
* \brief Free all scheduler-related resources
*/
void
fenced_scheduler_cleanup(void)
{
if (scheduler != NULL) {
pcmk__output_t *logger = scheduler->priv->out;
if (logger != NULL) {
logger->finish(logger, CRM_EX_OK, true, NULL);
pcmk__output_free(logger);
scheduler->priv->out = NULL;
}
pcmk_free_scheduler(scheduler);
scheduler = NULL;
}
}
/*!
* \internal
* \brief Check whether the local node is in a resource's allowed node list
*
* \param[in] rsc Resource to check
*
* \return Pointer to node if found, otherwise NULL
*/
static pcmk_node_t *
local_node_allowed_for(const pcmk_resource_t *rsc)
{
if ((rsc != NULL) && (scheduler->priv->local_node_name != NULL)) {
GHashTableIter iter;
pcmk_node_t *node = NULL;
g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
if (pcmk__str_eq(node->priv->name, scheduler->priv->local_node_name,
pcmk__str_casei)) {
return node;
}
}
}
return NULL;
}
/*!
* \internal
* \brief If a given resource or any of its children are fencing devices,
* register the devices
*
* \param[in,out] data Resource to check
* \param[in,out] user_data Ignored
*/
static void
register_if_fencing_device(gpointer data, gpointer user_data)
{
pcmk_resource_t *rsc = data;
const char *rsc_id = pcmk__s(rsc->priv->history_id, rsc->id);
xmlNode *xml = NULL;
GHashTableIter hash_iter;
pcmk_node_t *node = NULL;
const char *name = NULL;
const char *value = NULL;
const char *agent = NULL;
const char *rsc_provides = NULL;
stonith_key_value_t *params = NULL;
// If this is a collective resource, check children instead
if (rsc->priv->children != NULL) {
for (GList *iter = rsc->priv->children;
iter != NULL; iter = iter->next) {
register_if_fencing_device(iter->data, NULL);
if (pcmk__is_clone(rsc)) {
return; // Only one instance needs to be checked for clones
}
}
return;
}
if (!pcmk_is_set(rsc->flags, pcmk__rsc_fence_device)) {
return; // Not a fencing device
}
if (pe__resource_is_disabled(rsc)) {
crm_info("Ignoring fencing device %s because it is disabled", rsc->id);
return;
}
if ((stonith_watchdog_timeout_ms <= 0) &&
pcmk__str_eq(rsc->id, STONITH_WATCHDOG_ID, pcmk__str_none)) {
crm_info("Ignoring fencing device %s "
"because watchdog fencing is disabled", rsc->id);
return;
}
// Check whether local node is allowed to run resource
node = local_node_allowed_for(rsc);
if (node == NULL) {
crm_info("Ignoring fencing device %s "
"because local node is not allowed to run it", rsc->id);
return;
}
if (node->assign->score < 0) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for it",
rsc->id, pcmk_readable_score(node->assign->score));
return;
}
// If device is in a group, check whether local node is allowed for group
if (pcmk__is_group(rsc->priv->parent)) {
pcmk_node_t *group_node = local_node_allowed_for(rsc->priv->parent);
if ((group_node != NULL) && (group_node->assign->score < 0)) {
crm_info("Ignoring fencing device %s "
"because local node has preference %s for its group",
rsc->id, pcmk_readable_score(group_node->assign->score));
return;
}
}
crm_debug("Reloading configuration of fencing device %s", rsc->id);
agent = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE);
get_meta_attributes(rsc->priv->meta, rsc, NULL, scheduler);
rsc_provides = g_hash_table_lookup(rsc->priv->meta,
PCMK_STONITH_PROVIDES);
g_hash_table_iter_init(&hash_iter, pe_rsc_params(rsc, node, scheduler));
while (g_hash_table_iter_next(&hash_iter, (gpointer *) &name,
(gpointer *) &value)) {
if ((name == NULL) || (value == NULL)) {
continue;
}
params = stonith__key_value_add(params, name, value);
}
xml = create_device_registration_xml(rsc_id, st_namespace_any, agent,
params, rsc_provides);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
pcmk__assert(fenced_device_register(xml, true) == pcmk_rc_ok);
pcmk__xml_free(xml);
}
/*!
* \internal
* \brief Run the scheduler for fencer purposes
*
* \param[in] cib CIB to use as scheduler input
*
* \note Scheduler object is reset before returning, but \p cib is not freed.
*/
void
fenced_scheduler_run(xmlNode *cib)
{
CRM_CHECK((cib != NULL) && (scheduler != NULL)
&& (scheduler->input == NULL), return);
pcmk_reset_scheduler(scheduler);
scheduler->input = cib;
pcmk__set_scheduler_flags(scheduler,
pcmk__sched_location_only|pcmk__sched_no_counts);
pcmk__schedule_actions(scheduler);
g_list_foreach(scheduler->priv->resources, register_if_fencing_device,
NULL);
scheduler->input = NULL; // Wasn't a copy, so don't let API free it
pcmk_reset_scheduler(scheduler);
}
diff --git a/include/crm/fencing/internal.h b/include/crm/fencing/internal.h
index 14d1a6d66b..b4048e64d0 100644
--- a/include/crm/fencing/internal.h
+++ b/include/crm/fencing/internal.h
@@ -1,186 +1,188 @@
/*
* Copyright 2011-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_FENCING_INTERNAL__H
#define PCMK__CRM_FENCING_INTERNAL__H
#include <glib.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <crm/common/output_internal.h>
#include <crm/common/results_internal.h>
#include <crm/stonith-ng.h>
#ifdef __cplusplus
extern "C" {
#endif
stonith_t *stonith__api_new(void);
void stonith__api_free(stonith_t *stonith_api);
int stonith__api_dispatch(stonith_t *stonith_api);
stonith_key_value_t *stonith__key_value_add(stonith_key_value_t *head,
const char *key, const char *value);
+void stonith__key_value_freeall(stonith_key_value_t *head, bool keys,
+ bool values);
#define stonith__set_call_options(st_call_opts, call_for, flags_to_set) do { \
st_call_opts = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
"Fencer call", (call_for), \
(st_call_opts), (flags_to_set), \
#flags_to_set); \
} while (0)
#define stonith__clear_call_options(st_call_opts, call_for, flags_to_clear) do { \
st_call_opts = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
"Fencer call", (call_for), \
(st_call_opts), (flags_to_clear), \
#flags_to_clear); \
} while (0)
struct stonith_action_s;
typedef struct stonith_action_s stonith_action_t;
stonith_action_t *stonith__action_create(const char *agent,
const char *action_name,
const char *target,
int timeout_sec,
GHashTable *device_args,
GHashTable *port_map,
const char *host_arg);
void stonith__destroy_action(stonith_action_t *action);
pcmk__action_result_t *stonith__action_result(stonith_action_t *action);
int stonith__result2rc(const pcmk__action_result_t *result);
void stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result);
void stonith__xe_get_result(const xmlNode *xml, pcmk__action_result_t *result);
xmlNode *stonith__find_xe_with_result(xmlNode *xml);
int stonith__execute_async(stonith_action_t *action, void *userdata,
void (*done) (int pid,
const pcmk__action_result_t *result,
void *user_data),
void (*fork_cb) (int pid, void *user_data));
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);
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);
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);
void stonith__register_messages(pcmk__output_t *out);
GList *stonith__parse_targets(const char *hosts);
const char *stonith__later_succeeded(const stonith_history_t *event,
const stonith_history_t *top_history);
stonith_history_t *stonith__sort_history(stonith_history_t *history);
const char *stonith__default_host_arg(xmlNode *metadata);
/* Only 1-9 is allowed for fencing topology levels,
* however, 0 is used to unregister all levels in
* unregister requests.
*/
# define ST__LEVEL_COUNT 10
# define STONITH_ATTR_ACTION_OP "action"
# define STONITH_OP_EXEC "st_execute"
# define STONITH_OP_TIMEOUT_UPDATE "st_timeout_update"
# define STONITH_OP_QUERY "st_query"
# define STONITH_OP_FENCE "st_fence"
# define STONITH_OP_RELAY "st_relay"
# define STONITH_OP_DEVICE_ADD "st_device_register"
# define STONITH_OP_DEVICE_DEL "st_device_remove"
# define STONITH_OP_FENCE_HISTORY "st_fence_history"
# define STONITH_OP_LEVEL_ADD "st_level_add"
# define STONITH_OP_LEVEL_DEL "st_level_remove"
# define STONITH_OP_NOTIFY "st_notify"
# define STONITH_OP_POKE "poke"
# define STONITH_WATCHDOG_AGENT "fence_watchdog"
/* Don't change 2 below as it would break rolling upgrade */
# define STONITH_WATCHDOG_AGENT_INTERNAL "#watchdog"
# define STONITH_WATCHDOG_ID "watchdog"
stonith_history_t *stonith__first_matching_event(stonith_history_t *history,
bool (*matching_fn)(stonith_history_t *, void *),
void *user_data);
bool stonith__event_state_pending(stonith_history_t *history, void *user_data);
bool stonith__event_state_eq(stonith_history_t *history, void *user_data);
bool stonith__event_state_neq(stonith_history_t *history, void *user_data);
int stonith__legacy2status(int rc);
int stonith__exit_status(const stonith_callback_data_t *data);
int stonith__execution_status(const stonith_callback_data_t *data);
const char *stonith__exit_reason(const stonith_callback_data_t *data);
int stonith__event_exit_status(const stonith_event_t *event);
int stonith__event_execution_status(const stonith_event_t *event);
const char *stonith__event_exit_reason(const stonith_event_t *event);
char *stonith__event_description(const stonith_event_t *event);
gchar *stonith__history_description(const stonith_history_t *event,
bool full_history,
const char *later_succeeded,
uint32_t show_opts);
/*!
* \internal
* \brief Is a fencing operation in pending state?
*
* \param[in] state State as enum op_state value
*
* \return A boolean
*/
static inline bool
stonith__op_state_pending(enum op_state state)
{
return state != st_failed && state != st_done;
}
gboolean stonith__watchdog_fencing_enabled_for_node(const char *node);
gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node);
/*!
* \internal
* \brief Validate a fencing configuration
*
* \param[in,out] st Fencer connection to use
* \param[in] call_options Group of enum stonith_call_options
* \param[in] rsc_id Resource to validate
* \param[in] namespace_s Type of fence agent to search for
* \param[in] agent Fence agent to validate
* \param[in,out] params Fence device configuration parameters
* \param[in] timeout_sec How long to wait for operation to complete
* \param[in,out] output If non-NULL, where to store any agent output
* \param[in,out] error_output If non-NULL, where to store agent error output
*
* \return Standard Pacemaker return code
*/
int stonith__validate(stonith_t *st, int call_options, const char *rsc_id,
const char *namespace_s, const char *agent,
GHashTable *params, int timeout_sec, char **output,
char **error_output);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_FENCING_INTERNAL__H
diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h
index 6f0e8d8777..83ca9eb826 100644
--- a/include/crm/stonith-ng.h
+++ b/include/crm/stonith-ng.h
@@ -1,772 +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_text2namespace(const char *namespace_s);
const char *stonith_namespace2text(enum stonith_namespace st_namespace);
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
* \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
* \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[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
- * stonith_key_value_freeall().
+ * \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
* \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
* \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
* \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 Type of fence agent to validate ("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 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().
* \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;
};
/* Core functions */
void stonith_key_value_freeall(stonith_key_value_t * kvp, int keys, int values);
void stonith_history_free(stonith_history_t *history);
// Convenience functions
int stonith_api_connect_retry(stonith_t *st, const char *name,
int max_attempts);
const char *stonith_op_state_str(enum op_state state);
/* 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 uses both of these
*/
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"
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);
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);
}
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);
}
/**
* Does the given agent describe a stonith resource that can exist?
*
* \param[in] agent What is the name of the agent?
* \param[in] timeout Timeout to use when querying. If 0 is given,
* use a default of 120.
*
* \return A boolean
*/
bool stonith_agent_exists(const char *agent, int timeout);
/*!
* \brief Turn fence action into a more readable string
*
* \param[in] action Fence action
*/
const char *stonith_action_str(const char *action);
#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);
#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 9bf5c3d8a6..1df406243b 100644
--- a/lib/fencing/st_client.c
+++ b/lib/fencing/st_client.c
@@ -1,2818 +1,2834 @@
/*
* 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);
/*!
* \brief Get agent namespace by name
*
* \param[in] namespace_s Name of namespace as string
*
* \return Namespace as enum value
*/
enum stonith_namespace
stonith_text2namespace(const char *namespace_s)
{
if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) {
return st_namespace_any;
} else if (!strcmp(namespace_s, "redhat")
|| !strcmp(namespace_s, "stonith-ng")) {
return st_namespace_rhcs;
} else if (!strcmp(namespace_s, "internal")) {
return st_namespace_internal;
} else if (!strcmp(namespace_s, "heartbeat")) {
return st_namespace_lha;
}
return st_namespace_invalid;
}
/*!
* \brief Get agent namespace name
*
* \param[in] namespace Namespace as enum value
*
* \return Namespace name as string
*/
const char *
stonith_namespace2text(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: break;
}
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,
stonith_namespace2text(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,
stonith_text2namespace(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 = stonith_text2namespace(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);
if (timeout_sec <= 0) {
timeout_sec = PCMK_DEFAULT_ACTION_TIMEOUT_MS;
}
crm_trace("Looking up metadata for %s agent %s",
stonith_namespace2text(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;
}
void stonith_history_free(stonith_history_t *history)
{
stonith_history_t *hp, *hp_old;
for (hp = history; hp; hp_old = hp, hp = hp->next, free(hp_old)) {
free(hp->target);
free(hp->action);
free(hp->origin);
free(hp->delegate);
free(hp->client);
free(hp->exit_reason);
}
}
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 *namespace_s, 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, namespace_s)) {
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, namespace_s, 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);
}
}
/*!
* \brief Make a blocking connection attempt to the fencer
*
* \param[in,out] st Fencer API object
* \param[in] name Client name to use with fencer
* \param[in] max_attempts Return error if this many attempts fail
*
* \return pcmk_ok on success, result of last attempt otherwise
*/
int
stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts)
{
int rc = -EINVAL; // if max_attempts is not positive
for (int attempt = 1; attempt <= max_attempts; attempt++) {
rc = st->cmds->connect(st, name, NULL);
if (rc == pcmk_ok) {
return pcmk_ok;
} else 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_strerror(rc), rc);
sleep(2);
}
}
crm_notice("Could not connect to fencer: %s " QB_XS " rc=%d",
pcmk_strerror(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().
+ * \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, int keys, int values)
+stonith__key_value_freeall(stonith_key_value_t *head, bool keys, bool values)
{
- stonith_key_value_t *p;
+ while (head != NULL) {
+ stonith_key_value_t *next = head->next;
- while (head) {
- p = head->next;
if (keys) {
free(head->key);
}
if (values) {
free(head->value);
}
free(head);
- head = p;
+ head = next;
}
}
+void
+stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values)
+{
+ stonith__key_value_freeall(head, (keys != 0), (values != 0));
+}
+
#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;
}
bool
stonith_agent_exists(const char *agent, int timeout)
{
stonith_t *st = NULL;
stonith_key_value_t *devices = NULL;
stonith_key_value_t *dIter = NULL;
bool rc = FALSE;
if (agent == NULL) {
return rc;
}
st = stonith__api_new();
if (st == NULL) {
crm_err("Could not list fence agents: API memory allocation failed");
return FALSE;
}
st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout == 0 ? 120 : timeout);
for (dIter = devices; dIter != NULL; dIter = dIter->next) {
if (pcmk__str_eq(dIter->value, agent, pcmk__str_none)) {
rc = TRUE;
break;
}
}
- stonith_key_value_freeall(devices, 1, 1);
+ stonith__key_value_freeall(devices, true, true);
stonith__api_free(st);
return rc;
}
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;
}
}
/*!
* \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;
}
/*!
* \brief Return string equivalent of an operation state value
*
* \param[in] state Fencing operation state value
*
* \return Human-friendly string equivalent of state
*/
const char *
stonith_op_state_str(enum op_state state)
{
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";
}
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);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/lrmd/lrmd_client.c b/lib/lrmd/lrmd_client.c
index d8f259d4db..b0d81bab00 100644
--- a/lib/lrmd/lrmd_client.c
+++ b/lib/lrmd/lrmd_client.c
@@ -1,2681 +1,2681 @@
/*
* Copyright 2012-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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h> // uint32_t, uint64_t
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <glib.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/lrmd_internal.h>
#include <crm/services.h>
#include <crm/services_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/ipc_internal.h>
#include <crm/common/remote_internal.h>
#include <crm/common/tls_internal.h>
#include <crm/common/xml.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h> // stonith__*
#include <gnutls/gnutls.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MAX_TLS_RECV_WAIT 10000
CRM_TRACE_INIT_DATA(lrmd);
static int lrmd_api_disconnect(lrmd_t * lrmd);
static int lrmd_api_is_connected(lrmd_t * lrmd);
/* IPC proxy functions */
int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
static void lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg);
void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
// GnuTLS client handshake timeout in seconds
#define TLS_HANDSHAKE_TIMEOUT 5
static void lrmd_tls_disconnect(lrmd_t * lrmd);
static int global_remote_msg_id = 0;
static void lrmd_tls_connection_destroy(gpointer userdata);
static int add_tls_to_mainloop(lrmd_t *lrmd, bool do_api_handshake);
typedef struct lrmd_private_s {
uint64_t type;
char *token;
mainloop_io_t *source;
/* IPC parameters */
crm_ipc_t *ipc;
pcmk__remote_t *remote;
/* Extra TLS parameters */
char *remote_nodename;
char *server;
int port;
pcmk__tls_t *tls;
/* while the async connection is occurring, this is the id
* of the connection timeout timer. */
int async_timer;
int sock;
/* since tls requires a round trip across the network for a
* request/reply, there are times where we just want to be able
* to send a request from the client and not wait around (or even care
* about) what the reply is. */
int expected_late_replies;
GList *pending_notify;
crm_trigger_t *process_notify;
crm_trigger_t *handshake_trigger;
lrmd_event_callback callback;
/* Internal IPC proxy msg passing for remote guests */
void (*proxy_callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg);
void *proxy_callback_userdata;
char *peer_version;
} lrmd_private_t;
static int process_lrmd_handshake_reply(xmlNode *reply, lrmd_private_t *native);
static void report_async_connection_result(lrmd_t * lrmd, int rc);
static lrmd_list_t *
lrmd_list_add(lrmd_list_t * head, const char *value)
{
lrmd_list_t *p, *end;
p = pcmk__assert_alloc(1, sizeof(lrmd_list_t));
p->val = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_list_freeall(lrmd_list_t * head)
{
lrmd_list_t *p;
while (head) {
char *val = (char *)head->val;
p = head->next;
free(val);
free(head);
head = p;
}
}
lrmd_key_value_t *
lrmd_key_value_add(lrmd_key_value_t * head, const char *key, const char *value)
{
lrmd_key_value_t *p, *end;
p = pcmk__assert_alloc(1, sizeof(lrmd_key_value_t));
p->key = strdup(key);
p->value = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_key_value_freeall(lrmd_key_value_t * head)
{
lrmd_key_value_t *p;
while (head) {
p = head->next;
free(head->key);
free(head->value);
free(head);
head = p;
}
}
/*!
* \brief Create a new lrmd_event_data_t object
*
* \param[in] rsc_id ID of resource involved in event
* \param[in] task Action name
* \param[in] interval_ms Action interval
*
* \return Newly allocated and initialized lrmd_event_data_t
* \note This functions asserts on memory errors, so the return value is
* guaranteed to be non-NULL. The caller is responsible for freeing the
* result with lrmd_free_event().
*/
lrmd_event_data_t *
lrmd_new_event(const char *rsc_id, const char *task, guint interval_ms)
{
lrmd_event_data_t *event = pcmk__assert_alloc(1, sizeof(lrmd_event_data_t));
// lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
event->rsc_id = pcmk__str_copy(rsc_id);
event->op_type = pcmk__str_copy(task);
event->interval_ms = interval_ms;
return event;
}
lrmd_event_data_t *
lrmd_copy_event(lrmd_event_data_t * event)
{
lrmd_event_data_t *copy = NULL;
copy = pcmk__assert_alloc(1, sizeof(lrmd_event_data_t));
copy->type = event->type;
// lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
copy->rsc_id = pcmk__str_copy(event->rsc_id);
copy->op_type = pcmk__str_copy(event->op_type);
copy->user_data = pcmk__str_copy(event->user_data);
copy->output = pcmk__str_copy(event->output);
copy->remote_nodename = pcmk__str_copy(event->remote_nodename);
copy->exit_reason = pcmk__str_copy(event->exit_reason);
copy->call_id = event->call_id;
copy->timeout = event->timeout;
copy->interval_ms = event->interval_ms;
copy->start_delay = event->start_delay;
copy->rsc_deleted = event->rsc_deleted;
copy->rc = event->rc;
copy->op_status = event->op_status;
copy->t_run = event->t_run;
copy->t_rcchange = event->t_rcchange;
copy->exec_time = event->exec_time;
copy->queue_time = event->queue_time;
copy->connection_rc = event->connection_rc;
copy->params = pcmk__str_table_dup(event->params);
return copy;
}
/*!
* \brief Free an executor event
*
* \param[in,out] Executor event object to free
*/
void
lrmd_free_event(lrmd_event_data_t *event)
{
if (event == NULL) {
return;
}
// @TODO Why are these const char *?
free((void *) event->rsc_id);
free((void *) event->op_type);
free((void *) event->user_data);
free((void *) event->remote_nodename);
lrmd__reset_result(event);
if (event->params != NULL) {
g_hash_table_destroy(event->params);
}
free(event);
}
static void
lrmd_dispatch_internal(gpointer data, gpointer user_data)
{
xmlNode *msg = data;
lrmd_t *lrmd = user_data;
const char *type;
const char *proxy_session = crm_element_value(msg,
PCMK__XA_LRMD_IPC_SESSION);
lrmd_private_t *native = lrmd->lrmd_private;
lrmd_event_data_t event = { 0, };
if (proxy_session != NULL) {
/* this is proxy business */
lrmd_internal_proxy_dispatch(lrmd, msg);
return;
} else if (!native->callback) {
/* no callback set */
crm_trace("notify event received but client has not set callback");
return;
}
event.remote_nodename = native->remote_nodename;
type = crm_element_value(msg, PCMK__XA_LRMD_OP);
crm_element_value_int(msg, PCMK__XA_LRMD_CALLID, &event.call_id);
event.rsc_id = crm_element_value(msg, PCMK__XA_LRMD_RSC_ID);
if (pcmk__str_eq(type, LRMD_OP_RSC_REG, pcmk__str_none)) {
event.type = lrmd_event_register;
} else if (pcmk__str_eq(type, LRMD_OP_RSC_UNREG, pcmk__str_none)) {
event.type = lrmd_event_unregister;
} else if (pcmk__str_eq(type, LRMD_OP_RSC_EXEC, pcmk__str_none)) {
int rc = 0;
int exec_time = 0;
int queue_time = 0;
time_t epoch = 0;
crm_element_value_int(msg, PCMK__XA_LRMD_TIMEOUT, &event.timeout);
crm_element_value_ms(msg, PCMK__XA_LRMD_RSC_INTERVAL,
&event.interval_ms);
crm_element_value_int(msg, PCMK__XA_LRMD_RSC_START_DELAY,
&event.start_delay);
crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_RC, &rc);
event.rc = (enum ocf_exitcode) rc;
crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_OP_STATUS,
&event.op_status);
crm_element_value_int(msg, PCMK__XA_LRMD_RSC_DELETED,
&event.rsc_deleted);
crm_element_value_epoch(msg, PCMK__XA_LRMD_RUN_TIME, &epoch);
event.t_run = epoch;
crm_element_value_epoch(msg, PCMK__XA_LRMD_RCCHANGE_TIME, &epoch);
event.t_rcchange = epoch;
crm_element_value_int(msg, PCMK__XA_LRMD_EXEC_TIME, &exec_time);
CRM_LOG_ASSERT(exec_time >= 0);
event.exec_time = QB_MAX(0, exec_time);
crm_element_value_int(msg, PCMK__XA_LRMD_QUEUE_TIME, &queue_time);
CRM_LOG_ASSERT(queue_time >= 0);
event.queue_time = QB_MAX(0, queue_time);
event.op_type = crm_element_value(msg, PCMK__XA_LRMD_RSC_ACTION);
event.user_data = crm_element_value(msg,
PCMK__XA_LRMD_RSC_USERDATA_STR);
event.type = lrmd_event_exec_complete;
/* output and exit_reason may be freed by a callback */
event.output = crm_element_value_copy(msg, PCMK__XA_LRMD_RSC_OUTPUT);
lrmd__set_result(&event, event.rc, event.op_status,
crm_element_value(msg, PCMK__XA_LRMD_RSC_EXIT_REASON));
event.params = xml2list(msg);
} else if (pcmk__str_eq(type, LRMD_OP_NEW_CLIENT, pcmk__str_none)) {
event.type = lrmd_event_new_client;
} else if (pcmk__str_eq(type, LRMD_OP_POKE, pcmk__str_none)) {
event.type = lrmd_event_poke;
} else {
return;
}
crm_trace("op %s notify event received", type);
native->callback(&event);
if (event.params) {
g_hash_table_destroy(event.params);
}
lrmd__reset_result(&event);
}
// \return Always 0, to indicate that IPC mainloop source should be kept
static int
lrmd_ipc_dispatch(const char *buffer, ssize_t length, gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
if (native->callback != NULL) {
xmlNode *msg = pcmk__xml_parse(buffer);
lrmd_dispatch_internal(msg, lrmd);
pcmk__xml_free(msg);
}
return 0;
}
static void
lrmd_free_xml(gpointer userdata)
{
pcmk__xml_free((xmlNode *) userdata);
}
static bool
remote_executor_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
return (native->remote->tls_session != NULL);
}
static void
handle_remote_msg(xmlNode *xml, lrmd_t *lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
const char *msg_type = NULL;
msg_type = crm_element_value(xml, PCMK__XA_LRMD_REMOTE_MSG_TYPE);
if (pcmk__str_eq(msg_type, "notify", pcmk__str_casei)) {
lrmd_dispatch_internal(xml, lrmd);
} else if (pcmk__str_eq(msg_type, "reply", pcmk__str_casei)) {
const char *op = crm_element_value(xml, PCMK__XA_LRMD_OP);
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
/* The register op message we get as a response to lrmd_handshake_async
* is a reply, so we have to handle that here.
*/
if (pcmk__str_eq(op, "register", pcmk__str_casei)) {
int rc = process_lrmd_handshake_reply(xml, native);
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
}
} else {
int reply_id = 0;
crm_element_value_int(xml, PCMK__XA_LRMD_CALLID, &reply_id);
/* if this happens, we want to know about it */
crm_err("Got outdated Pacemaker Remote reply %d", reply_id);
}
}
}
/*!
* \internal
* \brief Notify trigger handler
*
* \param[in,out] userdata API connection
*
* \return Always return G_SOURCE_CONTINUE to leave this trigger handler in the
* mainloop
*/
static int
process_pending_notifies(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
if (native->pending_notify == NULL) {
return G_SOURCE_CONTINUE;
}
crm_trace("Processing pending notifies");
g_list_foreach(native->pending_notify, lrmd_dispatch_internal, lrmd);
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
return G_SOURCE_CONTINUE;
}
/*!
* \internal
* \brief TLS dispatch function for file descriptor sources
*
* \param[in,out] userdata API connection
*
* \return -1 on error to remove the source from the mainloop, or 0 otherwise
* to leave it in the mainloop
*/
static int
lrmd_tls_dispatch(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *xml = NULL;
int rc = pcmk_rc_ok;
if (!remote_executor_connected(lrmd)) {
crm_trace("TLS dispatch triggered after disconnect");
return -1;
}
crm_trace("TLS dispatch triggered");
rc = pcmk__remote_ready(native->remote, 0);
if (rc == pcmk_rc_ok) {
rc = pcmk__read_remote_message(native->remote, -1);
}
if (rc != pcmk_rc_ok && rc != ETIME) {
crm_info("Lost %s executor connection while reading data",
(native->remote_nodename? native->remote_nodename : "local"));
lrmd_tls_disconnect(lrmd);
return -1;
}
/* If rc is ETIME, there was nothing to read but we may already have a
* full message in the buffer
*/
xml = pcmk__remote_message_xml(native->remote);
if (xml == NULL) {
return 0;
}
handle_remote_msg(xml, lrmd);
pcmk__xml_free(xml);
return 0;
}
/* Not used with mainloop */
int
lrmd_poll(lrmd_t * lrmd, int timeout)
{
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
return crm_ipc_ready(native->ipc);
case pcmk__client_tls:
if (native->pending_notify) {
return 1;
} else {
int rc = pcmk__remote_ready(native->remote, 0);
switch (rc) {
case pcmk_rc_ok:
return 1;
case ETIME:
return 0;
default:
return pcmk_rc2legacy(rc);
}
}
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
return -EPROTONOSUPPORT;
}
}
/* Not used with mainloop */
bool
lrmd_dispatch(lrmd_t * lrmd)
{
lrmd_private_t *private = NULL;
pcmk__assert(lrmd != NULL);
private = lrmd->lrmd_private;
switch (private->type) {
case pcmk__client_ipc:
while (crm_ipc_ready(private->ipc)) {
if (crm_ipc_read(private->ipc) > 0) {
const char *msg = crm_ipc_buffer(private->ipc);
lrmd_ipc_dispatch(msg, strlen(msg), lrmd);
}
}
break;
case pcmk__client_tls:
lrmd_tls_dispatch(lrmd);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
private->type);
}
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("Connection closed");
return FALSE;
}
return TRUE;
}
static xmlNode *
lrmd_create_op(const char *token, const char *op, xmlNode *data, int timeout,
enum lrmd_call_options options)
{
xmlNode *op_msg = NULL;
CRM_CHECK(token != NULL, return NULL);
op_msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_COMMAND);
crm_xml_add(op_msg, PCMK__XA_T, PCMK__VALUE_LRMD);
crm_xml_add(op_msg, PCMK__XA_LRMD_OP, op);
crm_xml_add_int(op_msg, PCMK__XA_LRMD_TIMEOUT, timeout);
crm_xml_add_int(op_msg, PCMK__XA_LRMD_CALLOPT, options);
if (data != NULL) {
xmlNode *wrapper = pcmk__xe_create(op_msg, PCMK__XE_LRMD_CALLDATA);
pcmk__xml_copy(wrapper, data);
}
crm_trace("Created executor %s command with call options %.8lx (%d)",
op, (long)options, options);
return op_msg;
}
static void
lrmd_ipc_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
crm_info("Disconnected from local executor");
break;
case pcmk__client_tls:
crm_info("Disconnected from remote executor on %s",
native->remote_nodename);
break;
default:
crm_err("Unsupported executor connection type %d (bug?)",
native->type);
}
/* Prevent these from being cleaned up in lrmd_api_disconnect() */
native->ipc = NULL;
native->source = NULL;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_disconnect;
event.remote_nodename = native->remote_nodename;
native->callback(&event);
}
}
static void
lrmd_tls_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
crm_info("TLS connection destroyed");
if (native->remote->tls_session) {
gnutls_bye(native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(native->remote->tls_session);
native->remote->tls_session = NULL;
}
if (native->tls) {
pcmk__free_tls(native->tls);
native->tls = NULL;
}
if (native->sock >= 0) {
close(native->sock);
}
if (native->process_notify) {
mainloop_destroy_trigger(native->process_notify);
native->process_notify = NULL;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
if (native->handshake_trigger != NULL) {
mainloop_destroy_trigger(native->handshake_trigger);
native->handshake_trigger = NULL;
}
free(native->remote->buffer);
free(native->remote->start_state);
native->remote->buffer = NULL;
native->remote->start_state = NULL;
native->source = 0;
native->sock = -1;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.remote_nodename = native->remote_nodename;
event.type = lrmd_event_disconnect;
native->callback(&event);
}
return;
}
// \return Standard Pacemaker return code
int
lrmd__remote_send_xml(pcmk__remote_t *session, xmlNode *msg, uint32_t id,
const char *msg_type)
{
crm_xml_add_int(msg, PCMK__XA_LRMD_REMOTE_MSG_ID, id);
crm_xml_add(msg, PCMK__XA_LRMD_REMOTE_MSG_TYPE, msg_type);
return pcmk__remote_send_xml(session, msg);
}
// \return Standard Pacemaker return code
static int
read_remote_reply(lrmd_t *lrmd, int total_timeout, int expected_reply_id,
xmlNode **reply)
{
lrmd_private_t *native = lrmd->lrmd_private;
time_t start = time(NULL);
const char *msg_type = NULL;
int reply_id = 0;
int remaining_timeout = 0;
int rc = pcmk_rc_ok;
/* A timeout of 0 here makes no sense. We have to wait a period of time
* for the response to come back. If -1 or 0, default to 10 seconds. */
if (total_timeout <= 0 || total_timeout > MAX_TLS_RECV_WAIT) {
total_timeout = MAX_TLS_RECV_WAIT;
}
for (*reply = NULL; *reply == NULL; ) {
*reply = pcmk__remote_message_xml(native->remote);
if (*reply == NULL) {
/* read some more off the tls buffer if we still have time left. */
if (remaining_timeout) {
remaining_timeout = total_timeout - ((time(NULL) - start) * 1000);
} else {
remaining_timeout = total_timeout;
}
if (remaining_timeout <= 0) {
return ETIME;
}
rc = pcmk__read_remote_message(native->remote, remaining_timeout);
if (rc != pcmk_rc_ok) {
return rc;
}
*reply = pcmk__remote_message_xml(native->remote);
if (*reply == NULL) {
return ENOMSG;
}
}
crm_element_value_int(*reply, PCMK__XA_LRMD_REMOTE_MSG_ID, &reply_id);
msg_type = crm_element_value(*reply, PCMK__XA_LRMD_REMOTE_MSG_TYPE);
if (!msg_type) {
crm_err("Empty msg type received while waiting for reply");
pcmk__xml_free(*reply);
*reply = NULL;
} else if (pcmk__str_eq(msg_type, "notify", pcmk__str_casei)) {
/* got a notify while waiting for reply, trigger the notify to be processed later */
crm_info("queueing notify");
native->pending_notify = g_list_append(native->pending_notify, *reply);
if (native->process_notify) {
crm_info("notify trigger set.");
mainloop_set_trigger(native->process_notify);
}
*reply = NULL;
} else if (!pcmk__str_eq(msg_type, "reply", pcmk__str_casei)) {
/* msg isn't a reply, make some noise */
crm_err("Expected a reply, got %s", msg_type);
pcmk__xml_free(*reply);
*reply = NULL;
} else if (reply_id != expected_reply_id) {
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
} else {
crm_err("Got outdated reply, expected id %d got id %d", expected_reply_id, reply_id);
}
pcmk__xml_free(*reply);
*reply = NULL;
}
}
if (native->remote->buffer && native->process_notify) {
mainloop_set_trigger(native->process_notify);
}
return rc;
}
// \return Standard Pacemaker return code
static int
send_remote_message(lrmd_t *lrmd, xmlNode *msg)
{
int rc = pcmk_rc_ok;
lrmd_private_t *native = lrmd->lrmd_private;
global_remote_msg_id++;
if (global_remote_msg_id <= 0) {
global_remote_msg_id = 1;
}
rc = lrmd__remote_send_xml(native->remote, msg, global_remote_msg_id,
"request");
if (rc != pcmk_rc_ok) {
crm_err("Disconnecting because TLS message could not be sent to "
"Pacemaker Remote: %s", pcmk_rc_str(rc));
lrmd_tls_disconnect(lrmd);
}
return rc;
}
static int
lrmd_tls_send_recv(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = 0;
xmlNode *xml = NULL;
if (!remote_executor_connected(lrmd)) {
return -ENOTCONN;
}
rc = send_remote_message(lrmd, msg);
if (rc != pcmk_rc_ok) {
return pcmk_rc2legacy(rc);
}
rc = read_remote_reply(lrmd, timeout, global_remote_msg_id, &xml);
if (rc != pcmk_rc_ok) {
crm_err("Disconnecting remote after request %d reply not received: %s "
QB_XS " rc=%d timeout=%dms",
global_remote_msg_id, pcmk_rc_str(rc), rc, timeout);
lrmd_tls_disconnect(lrmd);
}
if (reply) {
*reply = xml;
} else {
pcmk__xml_free(xml);
}
return pcmk_rc2legacy(rc);
}
static int
lrmd_send_xml(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_client_response, timeout, reply);
break;
case pcmk__client_tls:
rc = lrmd_tls_send_recv(lrmd, msg, timeout, reply);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static int
lrmd_send_xml_no_reply(lrmd_t * lrmd, xmlNode * msg)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_flags_none, 0, NULL);
break;
case pcmk__client_tls:
rc = send_remote_message(lrmd, msg);
if (rc == pcmk_rc_ok) {
/* we don't want to wait around for the reply, but
* since the request/reply protocol needs to behave the same
* as libqb, a reply will eventually come later anyway. */
native->expected_late_replies++;
}
rc = pcmk_rc2legacy(rc);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static int
lrmd_api_is_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
return crm_ipc_connected(native->ipc);
case pcmk__client_tls:
return remote_executor_connected(lrmd);
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
return 0;
}
}
/*!
* \internal
* \brief Send a prepared API command to the executor
*
* \param[in,out] lrmd Existing connection to the executor
* \param[in] op Name of API command to send
* \param[in] data Command data XML to add to the sent command
* \param[out] output_data If expecting a reply, it will be stored here
* \param[in] timeout Timeout in milliseconds (if 0, defaults to
* a sensible value per the type of connection,
* standard vs. pacemaker remote);
* also propagated to the command XML
* \param[in] call_options Call options to pass to server when sending
* \param[in] expect_reply If true, wait for a reply from the server;
* must be true for IPC (as opposed to TLS) clients
*
* \return pcmk_ok on success, -errno on error
*/
static int
lrmd_send_command(lrmd_t *lrmd, const char *op, xmlNode *data,
xmlNode **output_data, int timeout,
enum lrmd_call_options options, bool expect_reply)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
if (!lrmd_api_is_connected(lrmd)) {
return -ENOTCONN;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
CRM_LOG_ASSERT(native->token != NULL);
crm_trace("Sending %s op to executor", op);
op_msg = lrmd_create_op(native->token, op, data, timeout, options);
if (op_msg == NULL) {
return -EINVAL;
}
if (expect_reply) {
rc = lrmd_send_xml(lrmd, op_msg, timeout, &op_reply);
} else {
rc = lrmd_send_xml_no_reply(lrmd, op_msg);
goto done;
}
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%d): %d", op, timeout, rc);
goto done;
} else if (op_reply == NULL) {
rc = -ENOMSG;
goto done;
}
rc = pcmk_ok;
crm_trace("%s op reply received", op);
if (crm_element_value_int(op_reply, PCMK__XA_LRMD_RC, &rc) != 0) {
rc = -ENOMSG;
goto done;
}
crm_log_xml_trace(op_reply, "Reply");
if (output_data) {
*output_data = op_reply;
op_reply = NULL; /* Prevent subsequent free */
}
done:
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("Executor disconnected");
}
pcmk__xml_free(op_msg);
pcmk__xml_free(op_reply);
return rc;
}
static int
lrmd_api_poke_connection(lrmd_t * lrmd)
{
int rc;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
rc = lrmd_send_command(lrmd, LRMD_OP_POKE, data, NULL, 0, 0,
(native->type == pcmk__client_ipc));
pcmk__xml_free(data);
return rc < 0 ? rc : pcmk_ok;
}
// \return Standard Pacemaker return code
int
lrmd__validate_remote_settings(lrmd_t *lrmd, GHashTable *hash)
{
int rc = pcmk_rc_ok;
const char *value;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XA_LRMD_OP);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
value = g_hash_table_lookup(hash, PCMK_OPT_STONITH_WATCHDOG_TIMEOUT);
if ((value) &&
(stonith__watchdog_fencing_enabled_for_node(native->remote_nodename))) {
crm_xml_add(data, PCMK__XA_LRMD_WATCHDOG, value);
}
rc = lrmd_send_command(lrmd, LRMD_OP_CHECK, data, NULL, 0, 0,
(native->type == pcmk__client_ipc));
pcmk__xml_free(data);
return (rc < 0)? pcmk_legacy2rc(rc) : pcmk_rc_ok;
}
static xmlNode *
lrmd_handshake_hello_msg(const char *name, bool is_proxy)
{
xmlNode *hello = pcmk__xe_create(NULL, PCMK__XE_LRMD_COMMAND);
crm_xml_add(hello, PCMK__XA_T, PCMK__VALUE_LRMD);
crm_xml_add(hello, PCMK__XA_LRMD_OP, CRM_OP_REGISTER);
crm_xml_add(hello, PCMK__XA_LRMD_CLIENTNAME, name);
crm_xml_add(hello, PCMK__XA_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
/* advertise that we are a proxy provider */
if (is_proxy) {
pcmk__xe_set_bool_attr(hello, PCMK__XA_LRMD_IS_IPC_PROVIDER, true);
}
return hello;
}
static int
process_lrmd_handshake_reply(xmlNode *reply, lrmd_private_t *native)
{
int rc = pcmk_rc_ok;
const char *version = crm_element_value(reply, PCMK__XA_LRMD_PROTOCOL_VERSION);
const char *msg_type = crm_element_value(reply, PCMK__XA_LRMD_OP);
const char *tmp_ticket = crm_element_value(reply, PCMK__XA_LRMD_CLIENTID);
const char *start_state = crm_element_value(reply, PCMK__XA_NODE_START_STATE);
long long uptime = -1;
crm_element_value_int(reply, PCMK__XA_LRMD_RC, &rc);
rc = pcmk_legacy2rc(rc);
/* The remote executor may add its uptime to the XML reply, which is useful
* in handling transient attributes when the connection to the remote node
* unexpectedly drops. If no parameter is given, just default to -1.
*/
crm_element_value_ll(reply, PCMK__XA_UPTIME, &uptime);
native->remote->uptime = uptime;
if (start_state) {
native->remote->start_state = strdup(start_state);
}
if (rc == EPROTO) {
crm_err("Executor protocol version mismatch between client (%s) and server (%s)",
LRMD_PROTOCOL_VERSION, version);
crm_log_xml_err(reply, "Protocol Error");
} else if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) {
crm_err("Invalid registration message: %s", msg_type);
crm_log_xml_err(reply, "Bad reply");
rc = EPROTO;
} else if (tmp_ticket == NULL) {
crm_err("No registration token provided");
crm_log_xml_err(reply, "Bad reply");
rc = EPROTO;
} else {
crm_trace("Obtained registration token: %s", tmp_ticket);
native->token = strdup(tmp_ticket);
native->peer_version = strdup(version?version:"1.0"); /* Included since 1.1 */
rc = pcmk_rc_ok;
}
return rc;
}
static int
lrmd_handshake(lrmd_t * lrmd, const char *name)
{
int rc = pcmk_rc_ok;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *reply = NULL;
xmlNode *hello = lrmd_handshake_hello_msg(name, native->proxy_callback != NULL);
rc = lrmd_send_xml(lrmd, hello, -1, &reply);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't complete registration with the executor API: %d", rc);
rc = ECOMM;
} else if (reply == NULL) {
crm_err("Did not receive registration reply");
rc = EPROTO;
} else {
rc = process_lrmd_handshake_reply(reply, native);
}
pcmk__xml_free(reply);
pcmk__xml_free(hello);
if (rc != pcmk_rc_ok) {
lrmd_api_disconnect(lrmd);
}
return rc;
}
static int
lrmd_handshake_async(lrmd_t * lrmd, const char *name)
{
int rc = pcmk_rc_ok;
lrmd_private_t *native = lrmd->lrmd_private;
xmlNode *hello = lrmd_handshake_hello_msg(name, native->proxy_callback != NULL);
rc = send_remote_message(lrmd, hello);
if (rc == pcmk_rc_ok) {
native->expected_late_replies++;
} else {
lrmd_api_disconnect(lrmd);
}
pcmk__xml_free(hello);
return rc;
}
static int
lrmd_ipc_connect(lrmd_t * lrmd, int *fd)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->lrmd_private;
struct ipc_client_callbacks lrmd_callbacks = {
.dispatch = lrmd_ipc_dispatch,
.destroy = lrmd_ipc_connection_destroy
};
crm_info("Connecting to executor");
if (fd) {
/* No mainloop */
native->ipc = crm_ipc_new(CRM_SYSTEM_LRMD, 0);
if (native->ipc != NULL) {
rc = pcmk__connect_generic_ipc(native->ipc);
if (rc == pcmk_rc_ok) {
rc = pcmk__ipc_fd(native->ipc, fd);
}
if (rc != pcmk_rc_ok) {
crm_err("Connection to executor failed: %s", pcmk_rc_str(rc));
rc = -ENOTCONN;
}
}
} else {
native->source = mainloop_add_ipc_client(CRM_SYSTEM_LRMD, G_PRIORITY_HIGH, 0, lrmd, &lrmd_callbacks);
native->ipc = mainloop_get_ipc_client(native->source);
}
if (native->ipc == NULL) {
crm_debug("Could not connect to the executor API");
rc = -ENOTCONN;
}
return rc;
}
static void
copy_gnutls_datum(gnutls_datum_t *dest, gnutls_datum_t *source)
{
pcmk__assert((dest != NULL) && (source != NULL) && (source->data != NULL));
dest->data = gnutls_malloc(source->size);
pcmk__mem_assert(dest->data);
memcpy(dest->data, source->data, source->size);
dest->size = source->size;
}
static void
clear_gnutls_datum(gnutls_datum_t *datum)
{
gnutls_free(datum->data);
datum->data = NULL;
datum->size = 0;
}
#define KEY_READ_LEN 256 // Chunk size for reading key from file
// \return Standard Pacemaker return code
static int
read_gnutls_key(const char *location, gnutls_datum_t *key)
{
FILE *stream = NULL;
size_t buf_len = KEY_READ_LEN;
if ((location == NULL) || (key == NULL)) {
return EINVAL;
}
stream = fopen(location, "r");
if (stream == NULL) {
return errno;
}
key->data = gnutls_malloc(buf_len);
key->size = 0;
while (!feof(stream)) {
int next = fgetc(stream);
if (next == EOF) {
if (!feof(stream)) {
crm_warn("Pacemaker Remote key read was partially successful "
"(copy in memory may be corrupted)");
}
break;
}
if (key->size == buf_len) {
buf_len = key->size + KEY_READ_LEN;
key->data = gnutls_realloc(key->data, buf_len);
pcmk__assert(key->data);
}
key->data[key->size++] = (unsigned char) next;
}
fclose(stream);
if (key->size == 0) {
clear_gnutls_datum(key);
return ENOKEY;
}
return pcmk_rc_ok;
}
// Cache the most recently used Pacemaker Remote authentication key
struct key_cache_s {
time_t updated; // When cached key was read (valid for 1 minute)
const char *location; // Where cached key was read from
gnutls_datum_t key; // Cached key
};
static bool
key_is_cached(struct key_cache_s *key_cache)
{
return key_cache->updated != 0;
}
static bool
key_cache_expired(struct key_cache_s *key_cache)
{
return (time(NULL) - key_cache->updated) >= 60;
}
static void
clear_key_cache(struct key_cache_s *key_cache)
{
clear_gnutls_datum(&(key_cache->key));
if ((key_cache->updated != 0) || (key_cache->location != NULL)) {
key_cache->updated = 0;
key_cache->location = NULL;
crm_debug("Cleared Pacemaker Remote key cache");
}
}
static void
get_cached_key(struct key_cache_s *key_cache, gnutls_datum_t *key)
{
copy_gnutls_datum(key, &(key_cache->key));
crm_debug("Using cached Pacemaker Remote key from %s",
pcmk__s(key_cache->location, "unknown location"));
}
static void
cache_key(struct key_cache_s *key_cache, gnutls_datum_t *key,
const char *location)
{
key_cache->updated = time(NULL);
key_cache->location = location;
copy_gnutls_datum(&(key_cache->key), key);
crm_debug("Using (and cacheing) Pacemaker Remote key from %s",
pcmk__s(location, "unknown location"));
}
/*!
* \internal
* \brief Get Pacemaker Remote authentication key from file or cache
*
* \param[in] location Path to key file to try (this memory must
* persist across all calls of this function)
* \param[out] key Key from location or cache
*
* \return Standard Pacemaker return code
*/
static int
get_remote_key(const char *location, gnutls_datum_t *key)
{
static struct key_cache_s key_cache = { 0, };
int rc = pcmk_rc_ok;
if ((location == NULL) || (key == NULL)) {
return EINVAL;
}
if (key_is_cached(&key_cache)) {
if (key_cache_expired(&key_cache)) {
clear_key_cache(&key_cache);
} else {
get_cached_key(&key_cache, key);
return pcmk_rc_ok;
}
}
rc = read_gnutls_key(location, key);
if (rc != pcmk_rc_ok) {
return rc;
}
cache_key(&key_cache, key, location);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Initialize the Pacemaker Remote authentication key
*
* Try loading the Pacemaker Remote authentication key from cache if available,
* otherwise from these locations, in order of preference:
*
* - The value of the PCMK_authkey_location environment variable, if set
* - The Pacemaker default key file location
*
* \param[out] key Where to store key
*
* \return Standard Pacemaker return code
*/
int
lrmd__init_remote_key(gnutls_datum_t *key)
{
static const char *env_location = NULL;
static bool need_env = true;
int rc = pcmk_rc_ok;
if (need_env) {
env_location = pcmk__env_option(PCMK__ENV_AUTHKEY_LOCATION);
need_env = false;
}
// Try location in environment variable, if set
if (env_location != NULL) {
rc = get_remote_key(env_location, key);
if (rc == pcmk_rc_ok) {
return pcmk_rc_ok;
}
crm_warn("Could not read Pacemaker Remote key from %s: %s",
env_location, pcmk_rc_str(rc));
return ENOKEY;
}
// Try default location, if environment wasn't explicitly set to it
rc = get_remote_key(DEFAULT_REMOTE_KEY_LOCATION, key);
if (rc == pcmk_rc_ok) {
return pcmk_rc_ok;
}
crm_warn("Could not read Pacemaker Remote key from default location %s: %s",
DEFAULT_REMOTE_KEY_LOCATION, pcmk_rc_str(rc));
return ENOKEY;
}
static void
report_async_connection_result(lrmd_t * lrmd, int rc)
{
lrmd_private_t *native = lrmd->lrmd_private;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_connect;
event.remote_nodename = native->remote_nodename;
event.connection_rc = rc;
native->callback(&event);
}
}
static void
tls_handshake_failed(lrmd_t *lrmd, int tls_rc, int rc)
{
lrmd_private_t *native = lrmd->lrmd_private;
crm_warn("Disconnecting after TLS handshake with "
"Pacemaker Remote server %s:%d failed: %s",
native->server, native->port,
(rc == EPROTO)? gnutls_strerror(tls_rc) : pcmk_rc_str(rc));
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
gnutls_deinit(native->remote->tls_session);
native->remote->tls_session = NULL;
lrmd_tls_connection_destroy(lrmd);
}
static void
tls_handshake_succeeded(lrmd_t *lrmd)
{
int rc = pcmk_rc_ok;
lrmd_private_t *native = lrmd->lrmd_private;
/* Now that the handshake is done, see if any client TLS certificate is
* close to its expiration date and log if so. If a TLS certificate is not
* in use, this function will just return so we don't need to check for the
* session type here.
*/
pcmk__tls_check_cert_expiration(native->remote->tls_session);
crm_info("TLS connection to Pacemaker Remote server %s:%d succeeded",
native->server, native->port);
rc = add_tls_to_mainloop(lrmd, true);
/* If add_tls_to_mainloop failed, report that right now. Otherwise, we have
* to wait until we read the async reply to report anything.
*/
if (rc != pcmk_rc_ok) {
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
}
}
/*!
* \internal
* \brief Perform a TLS client handshake with a Pacemaker Remote server
*
* \param[in] lrmd Newly established Pacemaker Remote executor connection
*
* \return Standard Pacemaker return code
*/
static int
tls_client_handshake(lrmd_t *lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
int tls_rc = GNUTLS_E_SUCCESS;
int rc = pcmk__tls_client_handshake(native->remote, TLS_HANDSHAKE_TIMEOUT,
&tls_rc);
if (rc != pcmk_rc_ok) {
tls_handshake_failed(lrmd, tls_rc, rc);
}
return rc;
}
/*!
* \internal
* \brief Add trigger and file descriptor mainloop sources for TLS
*
* \param[in,out] lrmd API connection with established TLS session
* \param[in] do_api_handshake Whether to perform executor handshake
*
* \return Standard Pacemaker return code
*/
static int
add_tls_to_mainloop(lrmd_t *lrmd, bool do_api_handshake)
{
lrmd_private_t *native = lrmd->lrmd_private;
int rc = pcmk_rc_ok;
char *name = crm_strdup_printf("pacemaker-remote-%s:%d",
native->server, native->port);
struct mainloop_fd_callbacks tls_fd_callbacks = {
.dispatch = lrmd_tls_dispatch,
.destroy = lrmd_tls_connection_destroy,
};
native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH,
process_pending_notifies, lrmd);
native->source = mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd,
&tls_fd_callbacks);
/* Async connections lose the client name provided by the API caller, so we
* have to use our generated name here to perform the executor handshake.
*
* @TODO Keep track of the caller-provided name. Perhaps we should be using
* that name in this function instead of generating one anyway.
*/
if (do_api_handshake) {
rc = lrmd_handshake_async(lrmd, name);
}
free(name);
return rc;
}
struct handshake_data_s {
lrmd_t *lrmd;
time_t start_time;
int timeout_sec;
};
static gboolean
try_handshake_cb(gpointer user_data)
{
struct handshake_data_s *hs = user_data;
lrmd_t *lrmd = hs->lrmd;
lrmd_private_t *native = lrmd->lrmd_private;
pcmk__remote_t *remote = native->remote;
int rc = pcmk_rc_ok;
int tls_rc = GNUTLS_E_SUCCESS;
if (time(NULL) >= hs->start_time + hs->timeout_sec) {
rc = ETIME;
tls_handshake_failed(lrmd, GNUTLS_E_TIMEDOUT, rc);
free(hs);
return 0;
}
rc = pcmk__tls_client_try_handshake(remote, &tls_rc);
if (rc == pcmk_rc_ok) {
tls_handshake_succeeded(lrmd);
free(hs);
return 0;
} else if (rc == EAGAIN) {
mainloop_set_trigger(native->handshake_trigger);
return 1;
} else {
rc = EKEYREJECTED;
tls_handshake_failed(lrmd, tls_rc, rc);
free(hs);
return 0;
}
}
static void
lrmd_tcp_connect_cb(void *userdata, int rc, int sock)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->lrmd_private;
int tls_rc = GNUTLS_E_SUCCESS;
bool use_cert = pcmk__x509_enabled();
native->async_timer = 0;
if (rc != pcmk_rc_ok) {
lrmd_tls_connection_destroy(lrmd);
crm_info("Could not connect to Pacemaker Remote at %s:%d: %s "
QB_XS " rc=%d",
native->server, native->port, pcmk_rc_str(rc), rc);
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
return;
}
/* The TCP connection was successful, so establish the TLS connection. */
native->sock = sock;
if (native->tls == NULL) {
rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK);
if (rc != pcmk_rc_ok) {
lrmd_tls_connection_destroy(lrmd);
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
return;
}
}
if (!use_cert) {
gnutls_datum_t psk_key = { NULL, 0 };
rc = lrmd__init_remote_key(&psk_key);
if (rc != pcmk_rc_ok) {
crm_info("Could not connect to Pacemaker Remote at %s:%d: %s "
QB_XS " rc=%d",
native->server, native->port, pcmk_rc_str(rc), rc);
lrmd_tls_connection_destroy(lrmd);
report_async_connection_result(lrmd, pcmk_rc2legacy(rc));
return;
}
pcmk__tls_add_psk_key(native->tls, &psk_key);
gnutls_free(psk_key.data);
}
native->remote->tls_session = pcmk__new_tls_session(native->tls, sock);
if (native->remote->tls_session == NULL) {
lrmd_tls_connection_destroy(lrmd);
report_async_connection_result(lrmd, -EPROTO);
return;
}
/* If the TLS handshake immediately succeeds or fails, we can handle that
* now without having to deal with mainloops and retries. Otherwise, add a
* trigger to keep trying until we get a result (or it times out).
*/
rc = pcmk__tls_client_try_handshake(native->remote, &tls_rc);
if (rc == EAGAIN) {
struct handshake_data_s *hs = NULL;
if (native->handshake_trigger != NULL) {
return;
}
hs = pcmk__assert_alloc(1, sizeof(struct handshake_data_s));
hs->lrmd = lrmd;
hs->start_time = time(NULL);
hs->timeout_sec = TLS_HANDSHAKE_TIMEOUT;
native->handshake_trigger = mainloop_add_trigger(G_PRIORITY_LOW, try_handshake_cb, hs);
mainloop_set_trigger(native->handshake_trigger);
} else if (rc == pcmk_rc_ok) {
tls_handshake_succeeded(lrmd);
} else {
tls_handshake_failed(lrmd, tls_rc, rc);
}
}
static int
lrmd_tls_connect_async(lrmd_t * lrmd, int timeout /*ms */ )
{
int rc = pcmk_rc_ok;
int timer_id = 0;
lrmd_private_t *native = lrmd->lrmd_private;
native->sock = -1;
rc = pcmk__connect_remote(native->server, native->port, timeout, &timer_id,
&(native->sock), lrmd, lrmd_tcp_connect_cb);
if (rc != pcmk_rc_ok) {
crm_warn("Pacemaker Remote connection to %s:%d failed: %s "
QB_XS " rc=%d",
native->server, native->port, pcmk_rc_str(rc), rc);
return rc;
}
native->async_timer = timer_id;
return rc;
}
static int
lrmd_tls_connect(lrmd_t * lrmd, int *fd)
{
int rc = pcmk_rc_ok;
bool use_cert = pcmk__x509_enabled();
lrmd_private_t *native = lrmd->lrmd_private;
native->sock = -1;
rc = pcmk__connect_remote(native->server, native->port, 0, NULL,
&(native->sock), NULL, NULL);
if (rc != pcmk_rc_ok) {
crm_warn("Pacemaker Remote connection to %s:%d failed: %s "
QB_XS " rc=%d",
native->server, native->port, pcmk_rc_str(rc), rc);
lrmd_tls_connection_destroy(lrmd);
return ENOTCONN;
}
if (native->tls == NULL) {
rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK);
if (rc != pcmk_rc_ok) {
lrmd_tls_connection_destroy(lrmd);
return rc;
}
}
if (!use_cert) {
gnutls_datum_t psk_key = { NULL, 0 };
rc = lrmd__init_remote_key(&psk_key);
if (rc != pcmk_rc_ok) {
lrmd_tls_connection_destroy(lrmd);
return rc;
}
pcmk__tls_add_psk_key(native->tls, &psk_key);
gnutls_free(psk_key.data);
}
native->remote->tls_session = pcmk__new_tls_session(native->tls, native->sock);
if (native->remote->tls_session == NULL) {
lrmd_tls_connection_destroy(lrmd);
return EPROTO;
}
if (tls_client_handshake(lrmd) != pcmk_rc_ok) {
return EKEYREJECTED;
}
crm_info("Client TLS connection established with Pacemaker Remote server %s:%d", native->server,
native->port);
if (fd) {
*fd = native->sock;
} else {
rc = add_tls_to_mainloop(lrmd, false);
}
return rc;
}
static int
lrmd_api_connect(lrmd_t * lrmd, const char *name, int *fd)
{
int rc = -ENOTCONN;
lrmd_private_t *native = lrmd->lrmd_private;
switch (native->type) {
case pcmk__client_ipc:
rc = lrmd_ipc_connect(lrmd, fd);
break;
case pcmk__client_tls:
rc = lrmd_tls_connect(lrmd, fd);
rc = pcmk_rc2legacy(rc);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
rc = -EPROTONOSUPPORT;
}
if (rc == pcmk_ok) {
rc = lrmd_handshake(lrmd, name);
rc = pcmk_rc2legacy(rc);
}
return rc;
}
static int
lrmd_api_connect_async(lrmd_t * lrmd, const char *name, int timeout)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->lrmd_private;
CRM_CHECK(native && native->callback, return -EINVAL);
switch (native->type) {
case pcmk__client_ipc:
/* fake async connection with ipc. it should be fast
* enough that we gain very little from async */
rc = lrmd_api_connect(lrmd, name, NULL);
if (!rc) {
report_async_connection_result(lrmd, rc);
}
break;
case pcmk__client_tls:
rc = lrmd_tls_connect_async(lrmd, timeout);
rc = pcmk_rc2legacy(rc);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static void
lrmd_ipc_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
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);
}
}
static void
lrmd_tls_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
if (native->remote->tls_session) {
gnutls_bye(native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(native->remote->tls_session);
native->remote->tls_session = NULL;
}
if (native->async_timer) {
g_source_remove(native->async_timer);
native->async_timer = 0;
}
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
} else if (native->sock >= 0) {
close(native->sock);
native->sock = -1;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
}
static int
lrmd_api_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
int rc = pcmk_ok;
switch (native->type) {
case pcmk__client_ipc:
crm_debug("Disconnecting from local executor");
lrmd_ipc_disconnect(lrmd);
break;
case pcmk__client_tls:
crm_debug("Disconnecting from remote executor on %s",
native->remote_nodename);
lrmd_tls_disconnect(lrmd);
break;
default:
crm_err("Unsupported executor connection type (bug?): %d",
native->type);
rc = -EPROTONOSUPPORT;
}
free(native->token);
native->token = NULL;
free(native->peer_version);
native->peer_version = NULL;
return rc;
}
static int
lrmd_api_register_rsc(lrmd_t * lrmd,
const char *rsc_id,
const char *class,
const char *provider, const char *type, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = NULL;
if (!class || !type || !rsc_id) {
return -EINVAL;
}
if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)
&& (provider == NULL)) {
return -EINVAL;
}
data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, PCMK__XA_LRMD_CLASS, class);
crm_xml_add(data, PCMK__XA_LRMD_PROVIDER, provider);
crm_xml_add(data, PCMK__XA_LRMD_TYPE, type);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_REG, data, NULL, 0, options, true);
pcmk__xml_free(data);
return rc;
}
static int
lrmd_api_unregister_rsc(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_UNREG, data, NULL, 0, options, true);
pcmk__xml_free(data);
return rc;
}
lrmd_rsc_info_t *
lrmd_new_rsc_info(const char *rsc_id, const char *standard,
const char *provider, const char *type)
{
lrmd_rsc_info_t *rsc_info = pcmk__assert_alloc(1, sizeof(lrmd_rsc_info_t));
rsc_info->id = pcmk__str_copy(rsc_id);
rsc_info->standard = pcmk__str_copy(standard);
rsc_info->provider = pcmk__str_copy(provider);
rsc_info->type = pcmk__str_copy(type);
return rsc_info;
}
lrmd_rsc_info_t *
lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info)
{
return lrmd_new_rsc_info(rsc_info->id, rsc_info->standard,
rsc_info->provider, rsc_info->type);
}
void
lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info)
{
if (!rsc_info) {
return;
}
free(rsc_info->id);
free(rsc_info->type);
free(rsc_info->standard);
free(rsc_info->provider);
free(rsc_info);
}
static lrmd_rsc_info_t *
lrmd_api_get_rsc_info(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
lrmd_rsc_info_t *rsc_info = NULL;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
xmlNode *output = NULL;
const char *class = NULL;
const char *provider = NULL;
const char *type = NULL;
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
lrmd_send_command(lrmd, LRMD_OP_RSC_INFO, data, &output, 0, options, true);
pcmk__xml_free(data);
if (!output) {
return NULL;
}
class = crm_element_value(output, PCMK__XA_LRMD_CLASS);
provider = crm_element_value(output, PCMK__XA_LRMD_PROVIDER);
type = crm_element_value(output, PCMK__XA_LRMD_TYPE);
if (!class || !type) {
pcmk__xml_free(output);
return NULL;
} else if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)
&& !provider) {
pcmk__xml_free(output);
return NULL;
}
rsc_info = lrmd_new_rsc_info(rsc_id, class, provider, type);
pcmk__xml_free(output);
return rsc_info;
}
void
lrmd_free_op_info(lrmd_op_info_t *op_info)
{
if (op_info) {
free(op_info->rsc_id);
free(op_info->action);
free(op_info->interval_ms_s);
free(op_info->timeout_ms_s);
free(op_info);
}
}
static int
lrmd_api_get_recurring_ops(lrmd_t *lrmd, const char *rsc_id, int timeout_ms,
enum lrmd_call_options options, GList **output)
{
xmlNode *data = NULL;
xmlNode *output_xml = NULL;
int rc = pcmk_ok;
if (output == NULL) {
return -EINVAL;
}
*output = NULL;
// Send request
if (rsc_id) {
data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
}
rc = lrmd_send_command(lrmd, LRMD_OP_GET_RECURRING, data, &output_xml,
timeout_ms, options, true);
if (data) {
pcmk__xml_free(data);
}
// Process reply
if ((rc != pcmk_ok) || (output_xml == NULL)) {
return rc;
}
for (const xmlNode *rsc_xml = pcmk__xe_first_child(output_xml,
PCMK__XE_LRMD_RSC, NULL,
NULL);
(rsc_xml != NULL) && (rc == pcmk_ok);
rsc_xml = pcmk__xe_next(rsc_xml, PCMK__XE_LRMD_RSC)) {
rsc_id = crm_element_value(rsc_xml, PCMK__XA_LRMD_RSC_ID);
if (rsc_id == NULL) {
crm_err("Could not parse recurring operation information from executor");
continue;
}
for (const xmlNode *op_xml = pcmk__xe_first_child(rsc_xml,
PCMK__XE_LRMD_RSC_OP,
NULL, NULL);
op_xml != NULL;
op_xml = pcmk__xe_next(op_xml, PCMK__XE_LRMD_RSC_OP)) {
lrmd_op_info_t *op_info = calloc(1, sizeof(lrmd_op_info_t));
if (op_info == NULL) {
rc = -ENOMEM;
break;
}
op_info->rsc_id = strdup(rsc_id);
op_info->action = crm_element_value_copy(op_xml,
PCMK__XA_LRMD_RSC_ACTION);
op_info->interval_ms_s =
crm_element_value_copy(op_xml, PCMK__XA_LRMD_RSC_INTERVAL);
op_info->timeout_ms_s =
crm_element_value_copy(op_xml, PCMK__XA_LRMD_TIMEOUT);
*output = g_list_prepend(*output, op_info);
}
}
pcmk__xml_free(output_xml);
return rc;
}
static void
lrmd_api_set_callback(lrmd_t * lrmd, lrmd_event_callback callback)
{
lrmd_private_t *native = lrmd->lrmd_private;
native->callback = callback;
}
void
lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg))
{
lrmd_private_t *native = lrmd->lrmd_private;
native->proxy_callback = callback;
native->proxy_callback_userdata = userdata;
}
void
lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg)
{
lrmd_private_t *native = lrmd->lrmd_private;
if (native->proxy_callback) {
crm_log_xml_trace(msg, "PROXY_INBOUND");
native->proxy_callback(lrmd, native->proxy_callback_userdata, msg);
}
}
int
lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg)
{
if (lrmd == NULL) {
return -ENOTCONN;
}
crm_xml_add(msg, PCMK__XA_LRMD_OP, CRM_OP_IPC_FWD);
crm_log_xml_trace(msg, "PROXY_OUTBOUND");
return lrmd_send_xml_no_reply(lrmd, msg);
}
static int
stonith_get_metadata(const char *provider, const char *type, char **output)
{
int rc = pcmk_ok;
stonith_t *stonith_api = stonith__api_new();
if (stonith_api == NULL) {
crm_err("Could not get fence agent meta-data: API memory allocation failed");
return -ENOMEM;
}
rc = stonith_api->cmds->metadata(stonith_api, st_opt_sync_call, type,
provider, output, 0);
if ((rc == pcmk_ok) && (*output == NULL)) {
rc = -EIO;
}
stonith_api->cmds->free(stonith_api);
return rc;
}
static int
lrmd_api_get_metadata(lrmd_t *lrmd, const char *standard, const char *provider,
const char *type, char **output,
enum lrmd_call_options options)
{
return lrmd->cmds->get_metadata_params(lrmd, standard, provider, type,
output, options, NULL);
}
static int
lrmd_api_get_metadata_params(lrmd_t *lrmd, const char *standard,
const char *provider, const char *type,
char **output, enum lrmd_call_options options,
lrmd_key_value_t *params)
{
svc_action_t *action = NULL;
GHashTable *params_table = NULL;
if (!standard || !type) {
lrmd_key_value_freeall(params);
return -EINVAL;
}
if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
lrmd_key_value_freeall(params);
return stonith_get_metadata(provider, type, output);
}
params_table = pcmk__strkey_table(free, free);
for (const lrmd_key_value_t *param = params; param; param = param->next) {
pcmk__insert_dup(params_table, param->key, param->value);
}
action = services__create_resource_action(type, standard, provider, type,
PCMK_ACTION_META_DATA, 0,
PCMK_DEFAULT_ACTION_TIMEOUT_MS,
params_table, 0);
lrmd_key_value_freeall(params);
if (action == NULL) {
return -ENOMEM;
}
if (action->rc != PCMK_OCF_UNKNOWN) {
services_action_free(action);
return -EINVAL;
}
if (!services_action_sync(action)) {
crm_err("Failed to retrieve meta-data for %s:%s:%s",
standard, provider, type);
services_action_free(action);
return -EIO;
}
if (!action->stdout_data) {
crm_err("Failed to receive meta-data for %s:%s:%s",
standard, provider, type);
services_action_free(action);
return -EIO;
}
*output = strdup(action->stdout_data);
services_action_free(action);
return pcmk_ok;
}
static int
lrmd_api_exec(lrmd_t *lrmd, const char *rsc_id, const char *action,
const char *userdata, guint interval_ms,
int timeout, /* ms */
int start_delay, /* ms */
enum lrmd_call_options options, lrmd_key_value_t * params)
{
int rc = pcmk_ok;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
lrmd_key_value_t *tmp = NULL;
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ACTION, action);
crm_xml_add(data, PCMK__XA_LRMD_RSC_USERDATA_STR, userdata);
crm_xml_add_ms(data, PCMK__XA_LRMD_RSC_INTERVAL, interval_ms);
crm_xml_add_int(data, PCMK__XA_LRMD_TIMEOUT, timeout);
crm_xml_add_int(data, PCMK__XA_LRMD_RSC_START_DELAY, start_delay);
for (tmp = params; tmp; tmp = tmp->next) {
hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
}
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_EXEC, data, NULL, timeout, options, true);
pcmk__xml_free(data);
lrmd_key_value_freeall(params);
return rc;
}
/* timeout is in ms */
static int
lrmd_api_exec_alert(lrmd_t *lrmd, const char *alert_id, const char *alert_path,
int timeout, lrmd_key_value_t *params)
{
int rc = pcmk_ok;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_ALERT);
xmlNode *args = pcmk__xe_create(data, PCMK__XE_ATTRIBUTES);
lrmd_key_value_t *tmp = NULL;
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_ALERT_ID, alert_id);
crm_xml_add(data, PCMK__XA_LRMD_ALERT_PATH, alert_path);
crm_xml_add_int(data, PCMK__XA_LRMD_TIMEOUT, timeout);
for (tmp = params; tmp; tmp = tmp->next) {
hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
}
rc = lrmd_send_command(lrmd, LRMD_OP_ALERT_EXEC, data, NULL, timeout,
lrmd_opt_notify_orig_only, true);
pcmk__xml_free(data);
lrmd_key_value_freeall(params);
return rc;
}
static int
lrmd_api_cancel(lrmd_t *lrmd, const char *rsc_id, const char *action,
guint interval_ms)
{
int rc = pcmk_ok;
xmlNode *data = pcmk__xe_create(NULL, PCMK__XE_LRMD_RSC);
crm_xml_add(data, PCMK__XA_LRMD_ORIGIN, __func__);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ACTION, action);
crm_xml_add(data, PCMK__XA_LRMD_RSC_ID, rsc_id);
crm_xml_add_ms(data, PCMK__XA_LRMD_RSC_INTERVAL, interval_ms);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_CANCEL, data, NULL, 0, 0, true);
pcmk__xml_free(data);
return rc;
}
static int
list_stonith_agents(lrmd_list_t ** resources)
{
int rc = 0;
stonith_t *stonith_api = stonith__api_new();
stonith_key_value_t *stonith_resources = NULL;
stonith_key_value_t *dIter = NULL;
if (stonith_api == NULL) {
crm_err("Could not list fence agents: API memory allocation failed");
return -ENOMEM;
}
stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL,
&stonith_resources, 0);
stonith_api->cmds->free(stonith_api);
for (dIter = stonith_resources; dIter; dIter = dIter->next) {
rc++;
if (resources) {
*resources = lrmd_list_add(*resources, dIter->value);
}
}
- stonith_key_value_freeall(stonith_resources, 1, 0);
+ stonith__key_value_freeall(stonith_resources, true, false);
return rc;
}
static int
lrmd_api_list_agents(lrmd_t * lrmd, lrmd_list_t ** resources, const char *class,
const char *provider)
{
int rc = 0;
int stonith_count = 0; // Initially, whether to include stonith devices
if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
stonith_count = 1;
} else {
GList *gIter = NULL;
GList *agents = resources_list_agents(class, provider);
for (gIter = agents; gIter != NULL; gIter = gIter->next) {
*resources = lrmd_list_add(*resources, (const char *)gIter->data);
rc++;
}
g_list_free_full(agents, free);
if (!class) {
stonith_count = 1;
}
}
if (stonith_count) {
// Now, if stonith devices are included, how many there are
stonith_count = list_stonith_agents(resources);
if (stonith_count > 0) {
rc += stonith_count;
}
}
if (rc == 0) {
crm_notice("No agents found for class %s", class);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static bool
does_provider_have_agent(const char *agent, const char *provider, const char *class)
{
bool found = false;
GList *agents = NULL;
GList *gIter2 = NULL;
agents = resources_list_agents(class, provider);
for (gIter2 = agents; gIter2 != NULL; gIter2 = gIter2->next) {
if (pcmk__str_eq(agent, gIter2->data, pcmk__str_casei)) {
found = true;
}
}
g_list_free_full(agents, free);
return found;
}
static int
lrmd_api_list_ocf_providers(lrmd_t * lrmd, const char *agent, lrmd_list_t ** providers)
{
int rc = pcmk_ok;
char *provider = NULL;
GList *ocf_providers = NULL;
GList *gIter = NULL;
ocf_providers = resources_list_providers(PCMK_RESOURCE_CLASS_OCF);
for (gIter = ocf_providers; gIter != NULL; gIter = gIter->next) {
provider = gIter->data;
if (!agent || does_provider_have_agent(agent, provider,
PCMK_RESOURCE_CLASS_OCF)) {
*providers = lrmd_list_add(*providers, (const char *)gIter->data);
rc++;
}
}
g_list_free_full(ocf_providers, free);
return rc;
}
static int
lrmd_api_list_standards(lrmd_t * lrmd, lrmd_list_t ** supported)
{
int rc = 0;
GList *standards = NULL;
GList *gIter = NULL;
standards = resources_list_standards();
for (gIter = standards; gIter != NULL; gIter = gIter->next) {
*supported = lrmd_list_add(*supported, (const char *)gIter->data);
rc++;
}
if (list_stonith_agents(NULL) > 0) {
*supported = lrmd_list_add(*supported, PCMK_RESOURCE_CLASS_STONITH);
rc++;
}
g_list_free_full(standards, free);
return rc;
}
/*!
* \internal
* \brief Create an executor API object
*
* \param[out] api Will be set to newly created API object (it is the
* caller's responsibility to free this value with
* lrmd_api_delete() if this function succeeds)
* \param[in] nodename If the object will be used for a remote connection,
* the node name to use in cluster for remote executor
* \param[in] server If the object will be used for a remote connection,
* the resolvable host name to connect to
* \param[in] port If the object will be used for a remote connection,
* port number on \p server to connect to
*
* \return Standard Pacemaker return code
* \note If the caller leaves one of \p nodename or \p server NULL, the other's
* value will be used for both. If the caller leaves both NULL, an API
* object will be created for a local executor connection.
*/
int
lrmd__new(lrmd_t **api, const char *nodename, const char *server, int port)
{
lrmd_private_t *pvt = NULL;
if (api == NULL) {
return EINVAL;
}
*api = NULL;
// Allocate all memory needed
*api = calloc(1, sizeof(lrmd_t));
if (*api == NULL) {
return ENOMEM;
}
pvt = calloc(1, sizeof(lrmd_private_t));
if (pvt == NULL) {
lrmd_api_delete(*api);
*api = NULL;
return ENOMEM;
}
(*api)->lrmd_private = pvt;
// @TODO Do we need to do this for local connections?
pvt->remote = calloc(1, sizeof(pcmk__remote_t));
(*api)->cmds = calloc(1, sizeof(lrmd_api_operations_t));
if ((pvt->remote == NULL) || ((*api)->cmds == NULL)) {
lrmd_api_delete(*api);
*api = NULL;
return ENOMEM;
}
// Set methods
(*api)->cmds->connect = lrmd_api_connect;
(*api)->cmds->connect_async = lrmd_api_connect_async;
(*api)->cmds->is_connected = lrmd_api_is_connected;
(*api)->cmds->poke_connection = lrmd_api_poke_connection;
(*api)->cmds->disconnect = lrmd_api_disconnect;
(*api)->cmds->register_rsc = lrmd_api_register_rsc;
(*api)->cmds->unregister_rsc = lrmd_api_unregister_rsc;
(*api)->cmds->get_rsc_info = lrmd_api_get_rsc_info;
(*api)->cmds->get_recurring_ops = lrmd_api_get_recurring_ops;
(*api)->cmds->set_callback = lrmd_api_set_callback;
(*api)->cmds->get_metadata = lrmd_api_get_metadata;
(*api)->cmds->exec = lrmd_api_exec;
(*api)->cmds->cancel = lrmd_api_cancel;
(*api)->cmds->list_agents = lrmd_api_list_agents;
(*api)->cmds->list_ocf_providers = lrmd_api_list_ocf_providers;
(*api)->cmds->list_standards = lrmd_api_list_standards;
(*api)->cmds->exec_alert = lrmd_api_exec_alert;
(*api)->cmds->get_metadata_params = lrmd_api_get_metadata_params;
if ((nodename == NULL) && (server == NULL)) {
pvt->type = pcmk__client_ipc;
} else {
if (nodename == NULL) {
nodename = server;
} else if (server == NULL) {
server = nodename;
}
pvt->type = pcmk__client_tls;
pvt->remote_nodename = strdup(nodename);
pvt->server = strdup(server);
if ((pvt->remote_nodename == NULL) || (pvt->server == NULL)) {
lrmd_api_delete(*api);
*api = NULL;
return ENOMEM;
}
pvt->port = port;
if (pvt->port == 0) {
pvt->port = crm_default_remote_port();
}
}
return pcmk_rc_ok;
}
lrmd_t *
lrmd_api_new(void)
{
lrmd_t *api = NULL;
pcmk__assert(lrmd__new(&api, NULL, NULL, 0) == pcmk_rc_ok);
return api;
}
lrmd_t *
lrmd_remote_api_new(const char *nodename, const char *server, int port)
{
lrmd_t *api = NULL;
pcmk__assert(lrmd__new(&api, nodename, server, port) == pcmk_rc_ok);
return api;
}
void
lrmd_api_delete(lrmd_t * lrmd)
{
if (lrmd == NULL) {
return;
}
if (lrmd->cmds != NULL) { // Never NULL, but make static analysis happy
if (lrmd->cmds->disconnect != NULL) { // Also never really NULL
lrmd->cmds->disconnect(lrmd); // No-op if already disconnected
}
free(lrmd->cmds);
}
if (lrmd->lrmd_private != NULL) {
lrmd_private_t *native = lrmd->lrmd_private;
free(native->server);
free(native->remote_nodename);
free(native->remote);
free(native->token);
free(native->peer_version);
free(lrmd->lrmd_private);
}
free(lrmd);
}
struct metadata_cb {
void (*callback)(int pid, const pcmk__action_result_t *result,
void *user_data);
void *user_data;
};
/*!
* \internal
* \brief Process asynchronous metadata completion
*
* \param[in,out] action Metadata action that completed
*/
static void
metadata_complete(svc_action_t *action)
{
struct metadata_cb *metadata_cb = (struct metadata_cb *) action->cb_data;
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
services__copy_result(action, &result);
pcmk__set_result_output(&result, action->stdout_data, action->stderr_data);
metadata_cb->callback(0, &result, metadata_cb->user_data);
result.action_stdout = NULL; // Prevent free, because action owns it
result.action_stderr = NULL; // Prevent free, because action owns it
pcmk__reset_result(&result);
free(metadata_cb);
}
/*!
* \internal
* \brief Retrieve agent metadata asynchronously
*
* \param[in] rsc Resource agent specification
* \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 This function is not a lrmd_api_operations_t method because it does not
* need an lrmd_t object and does not go through the executor, but
* executes the agent directly.
*/
int
lrmd__metadata_async(const lrmd_rsc_info_t *rsc,
void (*callback)(int pid,
const pcmk__action_result_t *result,
void *user_data),
void *user_data)
{
svc_action_t *action = NULL;
struct metadata_cb *metadata_cb = NULL;
pcmk__action_result_t result = PCMK__UNKNOWN_RESULT;
CRM_CHECK(callback != NULL, return EINVAL);
if ((rsc == NULL) || (rsc->standard == NULL) || (rsc->type == NULL)) {
pcmk__set_result(&result, PCMK_OCF_NOT_CONFIGURED,
PCMK_EXEC_ERROR_FATAL,
"Invalid resource specification");
callback(0, &result, user_data);
pcmk__reset_result(&result);
return EINVAL;
}
if (strcmp(rsc->standard, PCMK_RESOURCE_CLASS_STONITH) == 0) {
return stonith__metadata_async(rsc->type,
pcmk__timeout_ms2s(PCMK_DEFAULT_ACTION_TIMEOUT_MS),
callback, user_data);
}
action = services__create_resource_action(pcmk__s(rsc->id, rsc->type),
rsc->standard, rsc->provider,
rsc->type,
PCMK_ACTION_META_DATA, 0,
PCMK_DEFAULT_ACTION_TIMEOUT_MS,
NULL, 0);
if (action == NULL) {
pcmk__set_result(&result, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
"Out of memory");
callback(0, &result, user_data);
pcmk__reset_result(&result);
return ENOMEM;
}
if (action->rc != PCMK_OCF_UNKNOWN) {
services__copy_result(action, &result);
callback(0, &result, user_data);
pcmk__reset_result(&result);
services_action_free(action);
return EINVAL;
}
action->cb_data = calloc(1, sizeof(struct metadata_cb));
if (action->cb_data == NULL) {
services_action_free(action);
pcmk__set_result(&result, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
"Out of memory");
callback(0, &result, user_data);
pcmk__reset_result(&result);
return ENOMEM;
}
metadata_cb = (struct metadata_cb *) action->cb_data;
metadata_cb->callback = callback;
metadata_cb->user_data = user_data;
if (!services_action_async(action, metadata_complete)) {
services_action_free(action);
return pcmk_rc_error; // @TODO Derive from action->rc and ->status
}
// The services library has taken responsibility for action
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Set the result of an executor event
*
* \param[in,out] event Executor event to set
* \param[in] rc OCF exit status of event
* \param[in] op_status Executor status of event
* \param[in] exit_reason Human-friendly description of event
*/
void
lrmd__set_result(lrmd_event_data_t *event, enum ocf_exitcode rc, int op_status,
const char *exit_reason)
{
if (event == NULL) {
return;
}
event->rc = rc;
event->op_status = op_status;
// lrmd_event_data_t has (const char *) members that lrmd_free_event() frees
pcmk__str_update((char **) &event->exit_reason, exit_reason);
}
/*!
* \internal
* \brief Clear an executor event's exit reason, output, and error output
*
* \param[in,out] event Executor event to reset
*/
void
lrmd__reset_result(lrmd_event_data_t *event)
{
if (event == NULL) {
return;
}
free((void *) event->exit_reason);
event->exit_reason = NULL;
free((void *) event->output);
event->output = NULL;
}
/*!
* \internal
* \brief Get the uptime of a remote resource connection
*
* When the cluster connects to a remote resource, part of that resource's
* handshake includes the uptime of the remote resource's connection. This
* uptime is stored in the lrmd_t object.
*
* \return The connection's uptime, or -1 if unknown
*/
time_t
lrmd__uptime(lrmd_t *lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
if (native->remote == NULL) {
return -1;
} else {
return native->remote->uptime;
}
}
const char *
lrmd__node_start_state(lrmd_t *lrmd)
{
lrmd_private_t *native = lrmd->lrmd_private;
if (native->remote == NULL) {
return NULL;
} else {
return native->remote->start_state;
}
}
diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c
index 5bc2ce6ec6..fc389c2e57 100644
--- a/lib/pacemaker/pcmk_fence.c
+++ b/lib/pacemaker/pcmk_fence.c
@@ -1,677 +1,677 @@
/*
* Copyright 2009-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/results.h>
#include <crm/common/output.h>
#include <crm/common/output_internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h> // stonith__*
#include <glib.h>
#include <libxml/tree.h>
#include <pacemaker.h>
#include <pacemaker-internal.h>
#include "libpacemaker_private.h"
static const int st_opts = st_opt_sync_call|st_opt_allow_self_fencing;
static GMainLoop *mainloop = NULL;
static struct {
stonith_t *st;
const char *target;
const char *action;
char *name;
unsigned int timeout;
unsigned int tolerance;
int delay;
pcmk__action_result_t result;
} async_fence_data = { NULL, };
static int
handle_level(stonith_t *st, const char *target, int fence_level, GList *devices,
bool added)
{
const char *node = NULL;
const char *pattern = NULL;
const char *name = NULL;
char *value = NULL;
int rc = pcmk_rc_ok;
if (target == NULL) {
// Not really possible, but makes static analysis happy
return EINVAL;
}
/* Determine if targeting by attribute, node name pattern or node name */
value = strchr(target, '=');
if (value != NULL) {
name = target;
*value++ = '\0';
} else if (*target == '@') {
pattern = target + 1;
} else {
node = target;
}
/* Register or unregister level as appropriate */
if (added) {
stonith_key_value_t *kvs = NULL;
for (GList *iter = devices; iter != NULL; iter = iter->next) {
kvs = stonith__key_value_add(kvs, NULL, iter->data);
}
rc = st->cmds->register_level_full(st, st_opts, node, pattern, name,
value, fence_level, kvs);
- stonith_key_value_freeall(kvs, 0, 1);
+ stonith__key_value_freeall(kvs, false, true);
} else {
rc = st->cmds->remove_level_full(st, st_opts, node, pattern,
name, value, fence_level);
}
return pcmk_legacy2rc(rc);
}
static stonith_history_t *
reduce_fence_history(stonith_history_t *history)
{
stonith_history_t *new, *hp, *np;
if (!history) {
return history;
}
new = history;
hp = new->next;
new->next = NULL;
while (hp) {
stonith_history_t *hp_next = hp->next;
hp->next = NULL;
for (np = new; ; np = np->next) {
if ((hp->state == st_done) || (hp->state == st_failed)) {
/* action not in progress */
if (pcmk__str_eq(hp->target, np->target, pcmk__str_casei)
&& pcmk__str_eq(hp->action, np->action, pcmk__str_none)
&& (hp->state == np->state)
&& ((hp->state == st_done)
|| pcmk__str_eq(hp->delegate, np->delegate,
pcmk__str_casei))) {
/* purge older hp */
stonith_history_free(hp);
break;
}
}
if (!np->next) {
np->next = hp;
break;
}
}
hp = hp_next;
}
return new;
}
static void
notify_callback(stonith_t * st, stonith_event_t * e)
{
if (pcmk__str_eq(async_fence_data.target, e->target, pcmk__str_casei)
&& pcmk__str_eq(async_fence_data.action, e->action, pcmk__str_none)) {
pcmk__set_result(&async_fence_data.result,
stonith__event_exit_status(e),
stonith__event_execution_status(e),
stonith__event_exit_reason(e));
g_main_loop_quit(mainloop);
}
}
static void
fence_callback(stonith_t * stonith, stonith_callback_data_t * data)
{
pcmk__set_result(&async_fence_data.result, stonith__exit_status(data),
stonith__execution_status(data),
stonith__exit_reason(data));
g_main_loop_quit(mainloop);
}
static gboolean
async_fence_helper(gpointer user_data)
{
stonith_t *st = async_fence_data.st;
int call_id = 0;
int rc = stonith_api_connect_retry(st, async_fence_data.name, 10);
int timeout = 0;
if (rc != pcmk_ok) {
g_main_loop_quit(mainloop);
pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR,
PCMK_EXEC_NOT_CONNECTED, pcmk_strerror(rc));
return TRUE;
}
st->cmds->register_notification(st, PCMK__VALUE_ST_NOTIFY_FENCE,
notify_callback);
call_id = st->cmds->fence_with_delay(st,
st_opt_allow_self_fencing,
async_fence_data.target,
async_fence_data.action,
pcmk__timeout_ms2s(async_fence_data.timeout),
pcmk__timeout_ms2s(async_fence_data.tolerance),
async_fence_data.delay);
if (call_id < 0) {
g_main_loop_quit(mainloop);
pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR,
PCMK_EXEC_ERROR, pcmk_strerror(call_id));
return TRUE;
}
timeout = pcmk__timeout_ms2s(async_fence_data.timeout);
if (async_fence_data.delay > 0) {
timeout += async_fence_data.delay;
}
st->cmds->register_callback(st, call_id, timeout, st_opt_timeout_updates,
NULL, "callback", fence_callback);
return TRUE;
}
int
pcmk__request_fencing(stonith_t *st, const char *target, const char *action,
const char *name, unsigned int timeout,
unsigned int tolerance, int delay, char **reason)
{
crm_trigger_t *trig;
int rc = pcmk_rc_ok;
async_fence_data.st = st;
async_fence_data.name = strdup(name);
async_fence_data.target = target;
async_fence_data.action = action;
async_fence_data.timeout = timeout;
async_fence_data.tolerance = tolerance;
async_fence_data.delay = delay;
pcmk__set_result(&async_fence_data.result, CRM_EX_ERROR, PCMK_EXEC_UNKNOWN,
NULL);
trig = mainloop_add_trigger(G_PRIORITY_HIGH, async_fence_helper, NULL);
mainloop_set_trigger(trig);
mainloop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(mainloop);
free(async_fence_data.name);
if (reason != NULL) {
// Give the caller ownership of the exit reason
*reason = async_fence_data.result.exit_reason;
async_fence_data.result.exit_reason = NULL;
}
rc = stonith__result2rc(&async_fence_data.result);
pcmk__reset_result(&async_fence_data.result);
return rc;
}
int
pcmk_request_fencing(xmlNodePtr *xml, const char *target, const char *action,
const char *name, unsigned int timeout,
unsigned int tolerance, int delay, char **reason)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__request_fencing(st, target, action, name, timeout, tolerance,
delay, reason);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_history(pcmk__output_t *out, stonith_t *st, const char *target,
unsigned int timeout, int verbose, bool broadcast,
bool cleanup)
{
stonith_history_t *history = NULL;
stonith_history_t *latest = NULL;
int rc = pcmk_rc_ok;
int opts = 0;
if (cleanup) {
out->info(out, "cleaning up fencing-history%s%s",
target ? " for node " : "", target ? target : "");
}
if (broadcast) {
out->info(out, "gather fencing-history from all nodes");
}
stonith__set_call_options(opts, target, st_opts);
if (cleanup) {
stonith__set_call_options(opts, target, st_opt_cleanup);
}
if (broadcast) {
stonith__set_call_options(opts, target, st_opt_broadcast);
}
if (pcmk__str_eq(target, "*", pcmk__str_none)) {
target = NULL;
}
rc = st->cmds->history(st, opts, target, &history, pcmk__timeout_ms2s(timeout));
if (cleanup) {
// Cleanup doesn't return a history list
stonith_history_free(history);
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "event", "events", "Fencing history");
history = stonith__sort_history(history);
for (stonith_history_t *hp = history; hp != NULL; hp = hp->next) {
if (hp->state == st_done) {
latest = hp;
}
if (out->is_quiet(out) || !verbose) {
continue;
}
out->message(out, "stonith-event", hp, true, false,
stonith__later_succeeded(hp, history),
(uint32_t) pcmk_show_failed_detail);
out->increment_list(out);
}
if (latest) {
if (out->is_quiet(out)) {
out->message(out, "stonith-event", latest, false, true, NULL,
(uint32_t) pcmk_show_failed_detail);
} else if (!verbose) { // already printed if verbose
out->message(out, "stonith-event", latest, false, false, NULL,
(uint32_t) pcmk_show_failed_detail);
out->increment_list(out);
}
}
out->end_list(out);
stonith_history_free(history);
return pcmk_legacy2rc(rc);
}
int
pcmk_fence_history(xmlNodePtr *xml, const char *target, unsigned int timeout,
bool quiet, int verbose, bool broadcast, bool cleanup)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
out->quiet = quiet;
rc = pcmk__fence_history(out, st, target, timeout, verbose, broadcast,
cleanup);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_installed(pcmk__output_t *out, stonith_t *st, unsigned int timeout)
{
stonith_key_value_t *devices = NULL;
int rc = pcmk_rc_ok;
rc = st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices,
pcmk__timeout_ms2s(timeout));
// rc is a negative error code or a positive number of agents
if (rc < 0) {
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "fence device", "fence devices",
"Installed fence devices");
for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) {
out->list_item(out, "device", "%s", iter->value);
}
out->end_list(out);
- stonith_key_value_freeall(devices, 1, 1);
+ stonith__key_value_freeall(devices, true, true);
return pcmk_rc_ok;
}
int
pcmk_fence_installed(xmlNodePtr *xml, unsigned int timeout)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_installed(out, st, timeout);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_last(pcmk__output_t *out, const char *target, bool as_nodeid)
{
time_t when = 0;
if (target == NULL) {
return pcmk_rc_ok;
}
if (as_nodeid) {
when = stonith_api_time(atol(target), NULL, FALSE);
} else {
when = stonith_api_time(0, target, FALSE);
}
return out->message(out, "last-fenced", target, when);
}
int
pcmk_fence_last(xmlNodePtr *xml, const char *target, bool as_nodeid)
{
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__xml_output_new(&out, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
stonith__register_messages(out);
rc = pcmk__fence_last(out, target, as_nodeid);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
return rc;
}
int
pcmk__fence_list_targets(pcmk__output_t *out, stonith_t *st,
const char *device_id, unsigned int timeout)
{
GList *targets = NULL;
char *lists = NULL;
int rc = pcmk_rc_ok;
rc = st->cmds->list(st, st_opts, device_id, &lists, pcmk__timeout_ms2s(timeout));
if (rc != pcmk_rc_ok) {
return pcmk_legacy2rc(rc);
}
targets = stonith__parse_targets(lists);
out->begin_list(out, "fence target", "fence targets", "Fence Targets");
while (targets != NULL) {
out->list_item(out, NULL, "%s", (const char *) targets->data);
targets = targets->next;
}
out->end_list(out);
free(lists);
return rc;
}
int
pcmk_fence_list_targets(xmlNodePtr *xml, const char *device_id, unsigned int timeout)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_list_targets(out, st, device_id, timeout);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_metadata(pcmk__output_t *out, stonith_t *st, const char *agent,
unsigned int timeout)
{
char *buffer = NULL;
int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer,
pcmk__timeout_ms2s(timeout));
if (rc != pcmk_rc_ok) {
return pcmk_legacy2rc(rc);
}
out->output_xml(out, PCMK_XE_METADATA, buffer);
free(buffer);
return rc;
}
int
pcmk_fence_metadata(xmlNodePtr *xml, const char *agent, unsigned int timeout)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_metadata(out, st, agent, timeout);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_registered(pcmk__output_t *out, stonith_t *st, const char *target,
unsigned int timeout)
{
stonith_key_value_t *devices = NULL;
int rc = pcmk_rc_ok;
rc = st->cmds->query(st, st_opts, target, &devices, pcmk__timeout_ms2s(timeout));
/* query returns a negative error code or a positive number of results. */
if (rc < 0) {
return pcmk_legacy2rc(rc);
}
out->begin_list(out, "fence device", "fence devices",
"Registered fence devices");
for (stonith_key_value_t *iter = devices; iter != NULL; iter = iter->next) {
out->list_item(out, "device", "%s", iter->value);
}
out->end_list(out);
- stonith_key_value_freeall(devices, 1, 1);
+ stonith__key_value_freeall(devices, true, true);
/* Return pcmk_rc_ok here, not the number of results. Callers probably
* don't care.
*/
return pcmk_rc_ok;
}
int
pcmk_fence_registered(xmlNodePtr *xml, const char *target, unsigned int timeout)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_registered(out, st, target, timeout);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_register_level(stonith_t *st, const char *target, int fence_level,
GList *devices)
{
return handle_level(st, target, fence_level, devices, true);
}
int
pcmk_fence_register_level(xmlNodePtr *xml, const char *target, int fence_level,
GList *devices)
{
stonith_t* st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_register_level(st, target, fence_level, devices);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_unregister_level(stonith_t *st, const char *target, int fence_level)
{
return handle_level(st, target, fence_level, NULL, false);
}
int
pcmk_fence_unregister_level(xmlNodePtr *xml, const char *target, int fence_level)
{
stonith_t* st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_unregister_level(st, target, fence_level);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__fence_validate(pcmk__output_t *out, stonith_t *st, const char *agent,
const char *id, GHashTable *params, unsigned int timeout)
{
char *output = NULL;
char *error_output = NULL;
int rc;
rc = stonith__validate(st, st_opt_sync_call, id, NULL, agent, params,
pcmk__timeout_ms2s(timeout), &output, &error_output);
out->message(out, "validate", agent, id, output, error_output, rc);
return pcmk_legacy2rc(rc);
}
int
pcmk_fence_validate(xmlNodePtr *xml, const char *agent, const char *id,
GHashTable *params, unsigned int timeout)
{
stonith_t *st = NULL;
pcmk__output_t *out = NULL;
int rc = pcmk_rc_ok;
rc = pcmk__setup_output_fencing(&out, &st, xml);
if (rc != pcmk_rc_ok) {
return rc;
}
rc = pcmk__fence_validate(out, st, agent, id, params, timeout);
pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
st->cmds->disconnect(st);
stonith__api_free(st);
return rc;
}
int
pcmk__get_fencing_history(stonith_t *st, stonith_history_t **stonith_history,
enum pcmk__fence_history fence_history)
{
int rc = pcmk_rc_ok;
if ((st == NULL) || (st->state == stonith_disconnected)) {
rc = ENOTCONN;
} else if (fence_history != pcmk__fence_history_none) {
rc = st->cmds->history(st, st_opt_sync_call, NULL, stonith_history,
120);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
return rc;
}
*stonith_history = stonith__sort_history(*stonith_history);
if (fence_history == pcmk__fence_history_reduced) {
*stonith_history = reduce_fence_history(*stonith_history);
}
}
return rc;
}
diff --git a/tools/stonith_admin.c b/tools/stonith_admin.c
index fa50d108cb..bbbd7af001 100644
--- a/tools/stonith_admin.c
+++ b/tools/stonith_admin.c
@@ -1,726 +1,726 @@
/*
* Copyright 2009-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h> // gboolean, gchar, etc.
#include <crm/crm.h>
#include <crm/common/ipc.h>
#include <crm/cluster/internal.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output_internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h> // stonith__register_messages()
#include <crm/cib.h>
#include <crm/pengine/status.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#define SUMMARY "stonith_admin - Access the Pacemaker fencing API"
char action = 0;
struct {
gboolean as_nodeid;
gboolean broadcast;
gboolean cleanup;
gboolean installed;
gboolean metadata;
gboolean registered;
gboolean validate_cfg;
GList *devices;
GHashTable *params;
int fence_level;
int timeout ;
long long tolerance_ms;
int delay;
char *agent;
char *confirm_host;
char *fence_host;
char *history;
char *last_fenced;
char *query;
char *reboot_host;
char *register_dev;
char *register_level;
char *targets;
char *terminate;
char *unfence_host;
char *unregister_dev;
char *unregister_level;
} options = {
.timeout = 120,
.delay = 0
};
gboolean add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
#define INDENT " "
/* *INDENT-OFF* */
static GOptionEntry defn_entries[] = {
{ "register", 'R', 0, G_OPTION_ARG_STRING, &options.register_dev,
"Register the named stonith device. Requires: --agent.\n"
INDENT "Optional: --option, --env-option.",
"DEVICE" },
{ "deregister", 'D', 0, G_OPTION_ARG_STRING, &options.unregister_dev,
"De-register the named stonith device.",
"DEVICE" },
{ "register-level", 'r', 0, G_OPTION_ARG_STRING, &options.register_level,
"Register a stonith level for the named target,\n"
INDENT "specified as one of NAME, @PATTERN, or ATTR=VALUE.\n"
INDENT "Requires: --index and one or more --device entries.",
"TARGET" },
{ "deregister-level", 'd', 0, G_OPTION_ARG_STRING, &options.unregister_level,
"Unregister a stonith level for the named target,\n"
INDENT "specified as for --register-level. Requires: --index",
"TARGET" },
{ NULL }
};
static GOptionEntry query_entries[] = {
{ "list", 'l', 0, G_OPTION_ARG_STRING, &options.terminate,
"List devices that can terminate the specified host.\n"
INDENT "Optional: --timeout",
"HOST" },
{ "list-registered", 'L', 0, G_OPTION_ARG_NONE, &options.registered,
"List all registered devices. Optional: --timeout.",
NULL },
{ "list-installed", 'I', 0, G_OPTION_ARG_NONE, &options.installed,
"List all installed devices. Optional: --timeout.",
NULL },
{ "list-targets", 's', 0, G_OPTION_ARG_STRING, &options.targets,
"List the targets that can be fenced by the\n"
INDENT "named device. Optional: --timeout.",
"DEVICE" },
{ "metadata", 'M', 0, G_OPTION_ARG_NONE, &options.metadata,
"Show agent metadata. Requires: --agent.\n"
INDENT "Optional: --timeout.",
NULL },
{ "query", 'Q', 0, G_OPTION_ARG_STRING, &options.query,
"Check the named device's status. Optional: --timeout.",
"DEVICE" },
{ "history", 'H', 0, G_OPTION_ARG_STRING, &options.history,
"Show last successful fencing operation for named node\n"
INDENT "(or '*' for all nodes). Optional: --timeout, --cleanup,\n"
INDENT "--quiet (show only the operation's epoch timestamp),\n"
INDENT "--verbose (show all recorded and pending operations),\n"
INDENT "--broadcast (update history from all nodes available).",
"NODE" },
{ "last", 'h', 0, G_OPTION_ARG_STRING, &options.last_fenced,
"Indicate when the named node was last fenced.\n"
INDENT "Optional: --as-node-id.",
"NODE" },
{ "validate", 'K', 0, G_OPTION_ARG_NONE, &options.validate_cfg,
"Validate a fence device configuration.\n"
INDENT "Requires: --agent. Optional: --option, --env-option,\n"
INDENT "--quiet (print no output, only return status).",
NULL },
{ NULL }
};
static GOptionEntry fence_entries[] = {
{ "fence", 'F', 0, G_OPTION_ARG_STRING, &options.fence_host,
"Fence named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "unfence", 'U', 0, G_OPTION_ARG_STRING, &options.unfence_host,
"Unfence named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "reboot", 'B', 0, G_OPTION_ARG_STRING, &options.reboot_host,
"Reboot named host. Optional: --timeout, --tolerance, --delay.",
"HOST" },
{ "confirm", 'C', 0, G_OPTION_ARG_STRING, &options.confirm_host,
"Tell cluster that named host is now safely down.",
"HOST", },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cleanup", 'c', 0, G_OPTION_ARG_NONE, &options.cleanup,
"Cleanup wherever appropriate. Requires --history.",
NULL },
{ "broadcast", 'b', 0, G_OPTION_ARG_NONE, &options.broadcast,
"Broadcast wherever appropriate.",
NULL },
{ "agent", 'a', 0, G_OPTION_ARG_STRING, &options.agent,
"The agent to use (for example, fence_xvm;\n"
INDENT "with --register, --metadata, --validate).",
"AGENT" },
{ "option", 'o', 0, G_OPTION_ARG_CALLBACK, add_stonith_params,
"Specify a device configuration parameter as NAME=VALUE\n"
INDENT "(may be specified multiple times; with --register,\n"
INDENT "--validate).",
"PARAM" },
{ "env-option", 'e', 0, G_OPTION_ARG_CALLBACK, add_env_params,
"Specify a device configuration parameter with the\n"
INDENT "specified name, using the value of the\n"
INDENT "environment variable of the same name prefixed with\n"
INDENT "OCF_RESKEY_ (may be specified multiple times;\n"
INDENT "with --register, --validate).",
"PARAM" },
{ "tag", 'T', 0, G_OPTION_ARG_CALLBACK, set_tag,
"Identify fencing operations in logs with the specified\n"
INDENT "tag; useful when multiple entities might invoke\n"
INDENT "stonith_admin (used with most commands).",
"TAG" },
{ "device", 'v', 0, G_OPTION_ARG_CALLBACK, add_stonith_device,
"Device ID (with --register-level, device to associate with\n"
INDENT "a given host and level; may be specified multiple times)"
#if PCMK__ENABLE_CIBSECRETS
"\n" INDENT "(with --validate, name to use to load CIB secrets)"
#endif
".",
"DEVICE" },
{ "index", 'i', 0, G_OPTION_ARG_INT, &options.fence_level,
"The stonith level (1-9) (with --register-level,\n"
INDENT "--deregister-level).",
"LEVEL" },
{ "timeout", 't', 0, G_OPTION_ARG_INT, &options.timeout,
"Operation timeout in seconds (default 120;\n"
INDENT "used with most commands).",
"SECONDS" },
{ "delay", 'y', 0, G_OPTION_ARG_INT, &options.delay,
"Apply a fencing delay in seconds. Any static/random delays from\n"
INDENT "pcmk_delay_base/max will be added, otherwise all\n"
INDENT "disabled with the value -1\n"
INDENT "(default 0; with --fence, --reboot, --unfence).",
"SECONDS" },
{ "as-node-id", 'n', 0, G_OPTION_ARG_NONE, &options.as_nodeid,
"(Advanced) The supplied node is the corosync node ID\n"
INDENT "(with --last).",
NULL },
{ "tolerance", 0, 0, G_OPTION_ARG_CALLBACK, add_tolerance,
"(Advanced) Do nothing if an equivalent --fence request\n"
INDENT "succeeded less than this many seconds earlier\n"
INDENT "(with --fence, --unfence, --reboot).",
"SECONDS" },
{ NULL }
};
/* *INDENT-ON* */
static pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_HTML,
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static const int st_opts = st_opt_sync_call|st_opt_allow_self_fencing;
static char *name = NULL;
gboolean
add_env_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
char *key = crm_strdup_printf("OCF_RESKEY_%s", optarg);
const char *env = getenv(key);
gboolean retval = TRUE;
if (env == NULL) {
g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_INVALID_PARAM, "Invalid option: -e %s", optarg);
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", optarg, env);
if (options.params != NULL) {
options.params = pcmk__strkey_table(free, free);
}
pcmk__insert_dup(options.params, optarg, env);
}
free(key);
return retval;
}
gboolean
add_stonith_device(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.devices = g_list_append(options.devices, pcmk__str_copy(optarg));
return TRUE;
}
gboolean
add_tolerance(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
// pcmk__request_fencing() expects an unsigned int
options.tolerance_ms = crm_get_msec(optarg);
if (options.tolerance_ms < 0) {
crm_warn("Ignoring invalid tolerance '%s'", optarg);
options.tolerance_ms = 0;
} else {
options.tolerance_ms = QB_MIN(options.tolerance_ms, UINT_MAX);
}
return TRUE;
}
gboolean
add_stonith_params(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
gchar *name = NULL;
gchar *value = NULL;
int rc = 0;
gboolean retval = TRUE;
crm_info("Scanning: -o %s", optarg);
rc = pcmk__scan_nvpair(optarg, &name, &value);
if (rc != pcmk_rc_ok) {
g_set_error(error, PCMK__RC_ERROR, rc, "Invalid option: -o %s: %s", optarg, pcmk_rc_str(rc));
retval = FALSE;
} else {
crm_info("Got: '%s'='%s'", name, value);
if (options.params == NULL) {
options.params = pcmk__strkey_table(free, free);
}
pcmk__insert_dup(options.params, name, value);
}
g_free(name);
g_free(value);
return retval;
}
gboolean
set_tag(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
free(name);
name = crm_strdup_printf("%s.%s", crm_system_name, optarg);
return TRUE;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
GOptionContext *context = NULL;
GOptionEntry extra_prog_entries[] = {
{ "quiet", 'q', 0, G_OPTION_ARG_NONE, &(args->quiet),
"Be less descriptive in output.",
NULL },
{ NULL }
};
context = pcmk__build_arg_context(args, "text (default), html, xml", group, NULL);
/* Add the -q option, which cannot be part of the globally supported options
* because some tools use that flag for something else.
*/
pcmk__add_main_args(context, extra_prog_entries);
pcmk__add_arg_group(context, "definition", "Device Definition Commands:",
"Show device definition help", defn_entries);
pcmk__add_arg_group(context, "queries", "Queries:",
"Show query help", query_entries);
pcmk__add_arg_group(context, "fence", "Fencing Commands:",
"Show fence help", fence_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
// \return Standard Pacemaker return code
static int
request_fencing(stonith_t *st, const char *target, const char *command,
GError **error)
{
char *reason = NULL;
int rc = pcmk__request_fencing(st, target, command, name,
options.timeout * 1000,
options.tolerance_ms, options.delay,
&reason);
if (rc != pcmk_rc_ok) {
const char *rc_str = pcmk_rc_str(rc);
const char *what = "fence";
if (strcmp(command, PCMK_ACTION_ON) == 0) {
what = "unfence";
}
// If reason is identical to return code string, don't display it twice
if (pcmk__str_eq(rc_str, reason, pcmk__str_none)) {
free(reason);
reason = NULL;
}
g_set_error(error, PCMK__RC_ERROR, rc,
"Couldn't %s %s: %s%s%s%s",
what, target, rc_str,
((reason == NULL)? "" : " ("),
((reason == NULL)? "" : reason),
((reason == NULL)? "" : ")"));
}
free(reason);
return rc;
}
int
main(int argc, char **argv)
{
int rc = 0;
crm_exit_t exit_code = CRM_EX_OK;
bool no_connect = false;
bool required_agent = false;
char *target = NULL;
const char *device = NULL;
stonith_t *st = NULL;
GError *error = NULL;
pcmk__output_t *out = NULL;
GOptionGroup *output_group = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "adehilorstvyBCDFHQRTU");
GOptionContext *context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, formats);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
pcmk__cli_init_logging("stonith_admin", args->verbosity);
if (name == NULL) {
name = strdup(crm_system_name);
}
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_ERROR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
args->output_ty, pcmk_rc_str(rc));
goto done;
}
pcmk__output_enable_list_element(out);
stonith__register_messages(out);
if (args->version) {
out->version(out, false);
goto done;
}
if (options.validate_cfg) {
required_agent = true;
no_connect = true;
action = 'K';
}
if (options.installed) {
no_connect = true;
action = 'I';
}
if (options.registered) {
action = 'L';
}
if (options.register_dev != NULL) {
required_agent = true;
action = 'R';
device = options.register_dev;
}
if (options.query != NULL) {
action = 'Q';
device = options.query;
}
if (options.unregister_dev != NULL) {
action = 'D';
device = options.unregister_dev;
}
if (options.targets != NULL) {
action = 's';
device = options.targets;
}
if (options.terminate != NULL) {
action = 'L';
target = options.terminate;
}
if (options.metadata) {
no_connect = true;
required_agent = true;
action = 'M';
}
if (options.reboot_host != NULL) {
no_connect = true;
action = 'B';
target = options.reboot_host;
crm_log_args(argc, argv);
}
if (options.fence_host != NULL) {
no_connect = true;
action = 'F';
target = options.fence_host;
crm_log_args(argc, argv);
}
if (options.unfence_host != NULL) {
no_connect = true;
action = 'U';
target = options.unfence_host;
crm_log_args(argc, argv);
}
if (options.confirm_host != NULL) {
action = 'C';
target = options.confirm_host;
crm_log_args(argc, argv);
}
if (options.last_fenced != NULL) {
action = 'h';
target = options.last_fenced;
}
if (options.history != NULL) {
action = 'H';
target = options.history;
}
if (options.register_level != NULL) {
action = 'r';
target = options.register_level;
}
if (options.unregister_level != NULL) {
action = 'd';
target = options.unregister_level;
}
if ((options.timeout > (UINT_MAX / 1000)) || (options.timeout < 0)) {
out->err(out, "Integer value \"%d\" for -t out of range", options.timeout);
exit_code = CRM_EX_USAGE;
goto done;
}
if (action == 0) {
char *help = g_option_context_get_help(context, TRUE, NULL);
out->err(out, "%s", help);
g_free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
if (required_agent && options.agent == NULL) {
char *help = g_option_context_get_help(context, TRUE, NULL);
out->err(out, "Please specify an agent to query using -a,--agent [value]");
out->err(out, "%s", help);
g_free(help);
exit_code = CRM_EX_USAGE;
goto done;
}
out->quiet = args->quiet;
st = stonith__api_new();
if (st == NULL) {
rc = -ENOMEM;
} else if (!no_connect) {
rc = st->cmds->connect(st, name, NULL);
}
if (rc < 0) {
out->err(out, "Could not connect to fencer: %s", pcmk_strerror(rc));
exit_code = CRM_EX_DISCONNECT;
goto done;
}
switch (action) {
case 'I':
rc = pcmk__fence_installed(out, st, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list installed devices: %s", pcmk_rc_str(rc));
}
break;
case 'L':
rc = pcmk__fence_registered(out, st, target, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Failed to list registered devices: %s", pcmk_rc_str(rc));
}
break;
case 'Q':
rc = st->cmds->monitor(st, st_opts, device, options.timeout);
if (rc != pcmk_rc_ok) {
rc = st->cmds->list(st, st_opts, device, NULL, options.timeout);
}
rc = pcmk_legacy2rc(rc);
break;
case 's':
rc = pcmk__fence_list_targets(out, st, device, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Couldn't list targets: %s", pcmk_rc_str(rc));
}
break;
case 'R': {
/* register_device wants a stonith_key_value_t instead of a GHashTable */
stonith_key_value_t *params = NULL;
GHashTableIter iter;
gpointer key, val;
if (options.params != NULL) {
g_hash_table_iter_init(&iter, options.params);
while (g_hash_table_iter_next(&iter, &key, &val)) {
params = stonith__key_value_add(params, key, val);
}
}
rc = st->cmds->register_device(st, st_opts, device, NULL, options.agent,
params);
- stonith_key_value_freeall(params, 1, 1);
+ stonith__key_value_freeall(params, true, true);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't register device %s using agent %s: %s",
device, options.agent, pcmk_rc_str(rc));
}
break;
}
case 'D':
rc = st->cmds->remove_device(st, st_opts, device);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't unregister device %s: %s",
device, pcmk_rc_str(rc));
}
break;
case 'd':
rc = pcmk__fence_unregister_level(st, target, options.fence_level);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't unregister topology level %d for %s: %s",
options.fence_level, target, pcmk_rc_str(rc));
}
break;
case 'r':
rc = pcmk__fence_register_level(st, target, options.fence_level, options.devices);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't register topology level %d for %s: %s",
options.fence_level, target, pcmk_rc_str(rc));
}
break;
case 'M':
rc = pcmk__fence_metadata(out, st, options.agent, options.timeout*1000);
if (rc != pcmk_rc_ok) {
out->err(out, "Can't get fence agent meta-data: %s",
pcmk_rc_str(rc));
}
break;
case 'C':
rc = st->cmds->confirm(st, st_opts, target);
rc = pcmk_legacy2rc(rc);
break;
case 'B':
rc = request_fencing(st, target, PCMK_ACTION_REBOOT, &error);
break;
case 'F':
rc = request_fencing(st, target, PCMK_ACTION_OFF, &error);
break;
case 'U':
rc = request_fencing(st, target, PCMK_ACTION_ON, &error);
break;
case 'h':
rc = pcmk__fence_last(out, target, options.as_nodeid);
break;
case 'H':
rc = pcmk__fence_history(out, st, target, options.timeout*1000, args->verbosity,
options.broadcast, options.cleanup);
break;
case 'K':
device = NULL;
if (options.devices != NULL) {
device = g_list_nth_data(options.devices, 0);
}
rc = pcmk__fence_validate(out, st, options.agent, device, options.params,
options.timeout*1000);
break;
}
crm_info("Command returned: %s (%d)", pcmk_rc_str(rc), rc);
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
pcmk__output_and_clear_error(&error, out);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
pcmk__unregister_formats();
free(name);
g_list_free_full(options.devices, free);
if (options.params != NULL) {
g_hash_table_destroy(options.params);
}
if (st != NULL) {
st->cmds->disconnect(st);
stonith__api_free(st);
}
return exit_code;
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 21, 7:07 PM (16 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1665389
Default Alt Text
(351 KB)

Event Timeline