summaryrefslogtreecommitdiffstats
path: root/lib/common/operations.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/operations.c')
-rw-r--r--lib/common/operations.c530
1 files changed, 530 insertions, 0 deletions
diff --git a/lib/common/operations.c b/lib/common/operations.c
new file mode 100644
index 0000000..3db96cd
--- /dev/null
+++ b/lib/common/operations.c
@@ -0,0 +1,530 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include <crm/crm.h>
+#include <crm/lrmd.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml.h>
+#include <crm/common/xml_internal.h>
+#include <crm/common/util.h>
+
+/*!
+ * \brief Generate an operation key (RESOURCE_ACTION_INTERVAL)
+ *
+ * \param[in] rsc_id ID of resource being operated on
+ * \param[in] op_type Operation name
+ * \param[in] interval_ms Operation interval
+ *
+ * \return Newly allocated memory containing operation key as string
+ *
+ * \note This function asserts on errors, so it will never return NULL.
+ * The caller is responsible for freeing the result with free().
+ */
+char *
+pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
+{
+ CRM_ASSERT(rsc_id != NULL);
+ CRM_ASSERT(op_type != NULL);
+ return crm_strdup_printf(PCMK__OP_FMT, rsc_id, op_type, interval_ms);
+}
+
+static inline gboolean
+convert_interval(const char *s, guint *interval_ms)
+{
+ unsigned long l;
+
+ errno = 0;
+ l = strtoul(s, NULL, 10);
+
+ if (errno != 0) {
+ return FALSE;
+ }
+
+ *interval_ms = (guint) l;
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Check for underbar-separated substring match
+ *
+ * \param[in] key Overall string being checked
+ * \param[in] position Match before underbar at this \p key index
+ * \param[in] matches Substrings to match (may contain underbars)
+ *
+ * \return \p key index of underbar before any matching substring,
+ * or 0 if none
+ */
+static size_t
+match_before(const char *key, size_t position, const char **matches)
+{
+ for (int i = 0; matches[i] != NULL; ++i) {
+ const size_t match_len = strlen(matches[i]);
+
+ // Must have at least X_MATCH before position
+ if (position > (match_len + 1)) {
+ const size_t possible = position - match_len - 1;
+
+ if ((key[possible] == '_')
+ && (strncmp(key + possible + 1, matches[i], match_len) == 0)) {
+ return possible;
+ }
+ }
+ }
+ return 0;
+}
+
+gboolean
+parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms)
+{
+ guint local_interval_ms = 0;
+ const size_t key_len = (key == NULL)? 0 : strlen(key);
+
+ // Operation keys must be formatted as RSC_ACTION_INTERVAL
+ size_t action_underbar = 0; // Index in key of underbar before ACTION
+ size_t interval_underbar = 0; // Index in key of underbar before INTERVAL
+ size_t possible = 0;
+
+ /* Underbar was a poor choice of separator since both RSC and ACTION can
+ * contain underbars. Here, list action names and name prefixes that can.
+ */
+ const char *actions_with_underbars[] = {
+ CRMD_ACTION_MIGRATED,
+ CRMD_ACTION_MIGRATE,
+ NULL
+ };
+ const char *action_prefixes_with_underbars[] = {
+ "pre_" CRMD_ACTION_NOTIFY,
+ "post_" CRMD_ACTION_NOTIFY,
+ "confirmed-pre_" CRMD_ACTION_NOTIFY,
+ "confirmed-post_" CRMD_ACTION_NOTIFY,
+ NULL,
+ };
+
+ // Initialize output variables in case of early return
+ if (rsc_id) {
+ *rsc_id = NULL;
+ }
+ if (op_type) {
+ *op_type = NULL;
+ }
+ if (interval_ms) {
+ *interval_ms = 0;
+ }
+
+ // RSC_ACTION_INTERVAL implies a minimum of 5 characters
+ if (key_len < 5) {
+ return FALSE;
+ }
+
+ // Find, parse, and validate interval
+ interval_underbar = key_len - 2;
+ while ((interval_underbar > 2) && (key[interval_underbar] != '_')) {
+ --interval_underbar;
+ }
+ if ((interval_underbar == 2)
+ || !convert_interval(key + interval_underbar + 1, &local_interval_ms)) {
+ return FALSE;
+ }
+
+ // Find the base (OCF) action name, disregarding prefixes
+ action_underbar = match_before(key, interval_underbar,
+ actions_with_underbars);
+ if (action_underbar == 0) {
+ action_underbar = interval_underbar - 2;
+ while ((action_underbar > 0) && (key[action_underbar] != '_')) {
+ --action_underbar;
+ }
+ if (action_underbar == 0) {
+ return FALSE;
+ }
+ }
+ possible = match_before(key, action_underbar,
+ action_prefixes_with_underbars);
+ if (possible != 0) {
+ action_underbar = possible;
+ }
+
+ // Set output variables
+ if (rsc_id != NULL) {
+ *rsc_id = strndup(key, action_underbar);
+ CRM_ASSERT(*rsc_id != NULL);
+ }
+ if (op_type != NULL) {
+ *op_type = strndup(key + action_underbar + 1,
+ interval_underbar - action_underbar - 1);
+ CRM_ASSERT(*op_type != NULL);
+ }
+ if (interval_ms != NULL) {
+ *interval_ms = local_interval_ms;
+ }
+ return TRUE;
+}
+
+char *
+pcmk__notify_key(const char *rsc_id, const char *notify_type,
+ const char *op_type)
+{
+ CRM_CHECK(rsc_id != NULL, return NULL);
+ CRM_CHECK(op_type != NULL, return NULL);
+ CRM_CHECK(notify_type != NULL, return NULL);
+ return crm_strdup_printf("%s_%s_notify_%s_0",
+ rsc_id, notify_type, op_type);
+}
+
+/*!
+ * \brief Parse a transition magic string into its constituent parts
+ *
+ * \param[in] magic Magic string to parse (must be non-NULL)
+ * \param[out] uuid If non-NULL, where to store copy of parsed UUID
+ * \param[out] transition_id If non-NULL, where to store parsed transition ID
+ * \param[out] action_id If non-NULL, where to store parsed action ID
+ * \param[out] op_status If non-NULL, where to store parsed result status
+ * \param[out] op_rc If non-NULL, where to store parsed actual rc
+ * \param[out] target_rc If non-NULL, where to stored parsed target rc
+ *
+ * \return TRUE if key was valid, FALSE otherwise
+ * \note If uuid is supplied and this returns TRUE, the caller is responsible
+ * for freeing the memory for *uuid using free().
+ */
+gboolean
+decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
+ int *op_status, int *op_rc, int *target_rc)
+{
+ int res = 0;
+ char *key = NULL;
+ gboolean result = TRUE;
+ int local_op_status = -1;
+ int local_op_rc = -1;
+
+ CRM_CHECK(magic != NULL, return FALSE);
+
+#ifdef HAVE_SSCANF_M
+ res = sscanf(magic, "%d:%d;%ms", &local_op_status, &local_op_rc, &key);
+#else
+ key = calloc(1, strlen(magic) - 3); // magic must have >=4 other characters
+ CRM_ASSERT(key);
+ res = sscanf(magic, "%d:%d;%s", &local_op_status, &local_op_rc, key);
+#endif
+ if (res == EOF) {
+ crm_err("Could not decode transition information '%s': %s",
+ magic, pcmk_rc_str(errno));
+ result = FALSE;
+ } else if (res < 3) {
+ crm_warn("Transition information '%s' incomplete (%d of 3 expected items)",
+ magic, res);
+ result = FALSE;
+ } else {
+ if (op_status) {
+ *op_status = local_op_status;
+ }
+ if (op_rc) {
+ *op_rc = local_op_rc;
+ }
+ result = decode_transition_key(key, uuid, transition_id, action_id,
+ target_rc);
+ }
+ free(key);
+ return result;
+}
+
+char *
+pcmk__transition_key(int transition_id, int action_id, int target_rc,
+ const char *node)
+{
+ CRM_CHECK(node != NULL, return NULL);
+ return crm_strdup_printf("%d:%d:%d:%-*s",
+ action_id, transition_id, target_rc, 36, node);
+}
+
+/*!
+ * \brief Parse a transition key into its constituent parts
+ *
+ * \param[in] key Transition key to parse (must be non-NULL)
+ * \param[out] uuid If non-NULL, where to store copy of parsed UUID
+ * \param[out] transition_id If non-NULL, where to store parsed transition ID
+ * \param[out] action_id If non-NULL, where to store parsed action ID
+ * \param[out] target_rc If non-NULL, where to stored parsed target rc
+ *
+ * \return TRUE if key was valid, FALSE otherwise
+ * \note If uuid is supplied and this returns TRUE, the caller is responsible
+ * for freeing the memory for *uuid using free().
+ */
+gboolean
+decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
+ int *target_rc)
+{
+ int local_transition_id = -1;
+ int local_action_id = -1;
+ int local_target_rc = -1;
+ char local_uuid[37] = { '\0' };
+
+ // Initialize any supplied output arguments
+ if (uuid) {
+ *uuid = NULL;
+ }
+ if (transition_id) {
+ *transition_id = -1;
+ }
+ if (action_id) {
+ *action_id = -1;
+ }
+ if (target_rc) {
+ *target_rc = -1;
+ }
+
+ CRM_CHECK(key != NULL, return FALSE);
+ if (sscanf(key, "%d:%d:%d:%36s", &local_action_id, &local_transition_id,
+ &local_target_rc, local_uuid) != 4) {
+ crm_err("Invalid transition key '%s'", key);
+ return FALSE;
+ }
+ if (strlen(local_uuid) != 36) {
+ crm_warn("Invalid UUID '%s' in transition key '%s'", local_uuid, key);
+ }
+ if (uuid) {
+ *uuid = strdup(local_uuid);
+ CRM_ASSERT(*uuid);
+ }
+ if (transition_id) {
+ *transition_id = local_transition_id;
+ }
+ if (action_id) {
+ *action_id = local_action_id;
+ }
+ if (target_rc) {
+ *target_rc = local_target_rc;
+ }
+ return TRUE;
+}
+
+// Return true if a is an attribute that should be filtered
+static bool
+should_filter_for_digest(xmlAttrPtr a, void *user_data)
+{
+ if (strncmp((const char *) a->name, CRM_META "_",
+ sizeof(CRM_META " ") - 1) == 0) {
+ return true;
+ }
+ return pcmk__str_any_of((const char *) a->name,
+ XML_ATTR_ID,
+ XML_ATTR_CRM_VERSION,
+ XML_LRM_ATTR_OP_DIGEST,
+ XML_LRM_ATTR_TARGET,
+ XML_LRM_ATTR_TARGET_UUID,
+ "pcmk_external_ip",
+ NULL);
+}
+
+/*!
+ * \internal
+ * \brief Remove XML attributes not needed for operation digest
+ *
+ * \param[in,out] param_set XML with operation parameters
+ */
+void
+pcmk__filter_op_for_digest(xmlNode *param_set)
+{
+ char *key = NULL;
+ char *timeout = NULL;
+ guint interval_ms = 0;
+
+ if (param_set == NULL) {
+ return;
+ }
+
+ /* Timeout is useful for recurring operation digests, so grab it before
+ * removing meta-attributes
+ */
+ key = crm_meta_name(XML_LRM_ATTR_INTERVAL_MS);
+ if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
+ interval_ms = 0;
+ }
+ free(key);
+ key = NULL;
+ if (interval_ms != 0) {
+ key = crm_meta_name(XML_ATTR_TIMEOUT);
+ timeout = crm_element_value_copy(param_set, key);
+ }
+
+ // Remove all CRM_meta_* attributes and certain other attributes
+ pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL);
+
+ // Add timeout back for recurring operation digests
+ if (timeout != NULL) {
+ crm_xml_add(param_set, key, timeout);
+ }
+ free(timeout);
+ free(key);
+}
+
+int
+rsc_op_expected_rc(const lrmd_event_data_t *op)
+{
+ int rc = 0;
+
+ if (op && op->user_data) {
+ decode_transition_key(op->user_data, NULL, NULL, NULL, &rc);
+ }
+ return rc;
+}
+
+gboolean
+did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
+{
+ switch (op->op_status) {
+ case PCMK_EXEC_CANCELLED:
+ case PCMK_EXEC_PENDING:
+ return FALSE;
+
+ case PCMK_EXEC_NOT_SUPPORTED:
+ case PCMK_EXEC_TIMEOUT:
+ case PCMK_EXEC_ERROR:
+ case PCMK_EXEC_NOT_CONNECTED:
+ case PCMK_EXEC_NO_FENCE_DEVICE:
+ case PCMK_EXEC_NO_SECRETS:
+ case PCMK_EXEC_INVALID:
+ return TRUE;
+
+ default:
+ if (target_rc != op->rc) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/*!
+ * \brief Create a CIB XML element for an operation
+ *
+ * \param[in,out] parent If not NULL, make new XML node a child of this
+ * \param[in] prefix Generate an ID using this prefix
+ * \param[in] task Operation task to set
+ * \param[in] interval_spec Operation interval to set
+ * \param[in] timeout If not NULL, operation timeout to set
+ *
+ * \return New XML object on success, NULL otherwise
+ */
+xmlNode *
+crm_create_op_xml(xmlNode *parent, const char *prefix, const char *task,
+ const char *interval_spec, const char *timeout)
+{
+ xmlNode *xml_op;
+
+ CRM_CHECK(prefix && task && interval_spec, return NULL);
+
+ xml_op = create_xml_node(parent, XML_ATTR_OP);
+ crm_xml_set_id(xml_op, "%s-%s-%s", prefix, task, interval_spec);
+ crm_xml_add(xml_op, XML_LRM_ATTR_INTERVAL, interval_spec);
+ crm_xml_add(xml_op, "name", task);
+ if (timeout) {
+ crm_xml_add(xml_op, XML_ATTR_TIMEOUT, timeout);
+ }
+ return xml_op;
+}
+
+/*!
+ * \brief Check whether an operation requires resource agent meta-data
+ *
+ * \param[in] rsc_class Resource agent class (or NULL to skip class check)
+ * \param[in] op Operation action (or NULL to skip op check)
+ *
+ * \return true if operation needs meta-data, false otherwise
+ * \note At least one of rsc_class and op must be specified.
+ */
+bool
+crm_op_needs_metadata(const char *rsc_class, const char *op)
+{
+ /* Agent metadata is used to determine whether an agent reload is possible,
+ * so if this op is not relevant to that feature, we don't need metadata.
+ */
+
+ CRM_CHECK((rsc_class != NULL) || (op != NULL), return false);
+
+ if ((rsc_class != NULL)
+ && !pcmk_is_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) {
+ // Metadata is needed only for resource classes that use parameters
+ return false;
+ }
+ if (op == NULL) {
+ return true;
+ }
+
+ // Metadata is needed only for these actions
+ return pcmk__str_any_of(op, CRMD_ACTION_START, CRMD_ACTION_STATUS,
+ CRMD_ACTION_PROMOTE, CRMD_ACTION_DEMOTE,
+ CRMD_ACTION_RELOAD, CRMD_ACTION_RELOAD_AGENT,
+ CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED,
+ CRMD_ACTION_NOTIFY, NULL);
+}
+
+/*!
+ * \internal
+ * \brief Check whether an action name is for a fencing action
+ *
+ * \param[in] action Action name to check
+ *
+ * \return true if \p action is "off", "reboot", or "poweroff", otherwise false
+ */
+bool
+pcmk__is_fencing_action(const char *action)
+{
+ return pcmk__str_any_of(action, "off", "reboot", "poweroff", NULL);
+}
+
+bool
+pcmk_is_probe(const char *task, guint interval)
+{
+ if (task == NULL) {
+ return false;
+ }
+
+ return (interval == 0) && pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_none);
+}
+
+bool
+pcmk_xe_is_probe(const xmlNode *xml_op)
+{
+ const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
+ const char *interval_ms_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS);
+ int interval_ms;
+
+ pcmk__scan_min_int(interval_ms_s, &interval_ms, 0);
+ return pcmk_is_probe(task, interval_ms);
+}
+
+bool
+pcmk_xe_mask_probe_failure(const xmlNode *xml_op)
+{
+ int status = PCMK_EXEC_UNKNOWN;
+ int rc = PCMK_OCF_OK;
+
+ if (!pcmk_xe_is_probe(xml_op)) {
+ return false;
+ }
+
+ crm_element_value_int(xml_op, XML_LRM_ATTR_OPSTATUS, &status);
+ crm_element_value_int(xml_op, XML_LRM_ATTR_RC, &rc);
+
+ return rc == PCMK_OCF_NOT_INSTALLED || rc == PCMK_OCF_INVALID_PARAM ||
+ status == PCMK_EXEC_NOT_INSTALLED;
+}