diff --git a/lib/common/operations.c b/lib/common/operations.c index df7f2e398d..db0a82a5ff 100644 --- a/lib/common/operations.c +++ b/lib/common/operations.c @@ -1,437 +1,425 @@ /* * Copyright 2004-2020 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include /*! * \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(CRM_OP_FMT, rsc_id, op_type, interval_ms); } gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms) { char *notify = NULL; char *mutable_key = NULL; char *mutable_key_ptr = NULL; size_t len = 0, offset = 0; unsigned long long ch = 0; guint local_interval_ms = 0; // 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; } CRM_CHECK(key && *key, return FALSE); // Parse interval at end of string len = strlen(key); offset = len - 1; while ((offset > 0) && isdigit(key[offset])) { ch = key[offset] - '0'; for (int digits = len - offset; digits > 1; --digits) { ch = ch * 10; } local_interval_ms += ch; offset--; } crm_trace("Operation key '%s' has interval %ums", key, local_interval_ms); if (interval_ms) { *interval_ms = local_interval_ms; } CRM_CHECK((offset != (len - 1)) && (key[offset] == '_'), return FALSE); mutable_key = strndup(key, offset); offset--; while (offset > 0 && key[offset] != '_') { offset--; } CRM_CHECK(key[offset] == '_', free(mutable_key); return FALSE); mutable_key_ptr = mutable_key + offset + 1; crm_trace(" Action: %s", mutable_key_ptr); if (op_type) { *op_type = strdup(mutable_key_ptr); } mutable_key[offset] = 0; offset--; notify = strstr(mutable_key, "_post_notify"); if (notify && safe_str_eq(notify, "_post_notify")) { notify[0] = 0; } notify = strstr(mutable_key, "_pre_notify"); if (notify && safe_str_eq(notify, "_pre_notify")) { notify[0] = 0; } crm_trace(" Resource: %s", mutable_key); if (rsc_id) { *rsc_id = mutable_key; } else { free(mutable_key); } return TRUE; } char * generate_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 SSCANF_HAS_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_strerror(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 * generate_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; } void filter_action_parameters(xmlNode * param_set, const char *version) { char *key = NULL; char *timeout = NULL; - char *interval_ms_s = NULL; + guint interval_ms = 0; const char *attr_filter[] = { XML_ATTR_ID, XML_ATTR_CRM_VERSION, XML_LRM_ATTR_OP_DIGEST, XML_LRM_ATTR_TARGET, XML_LRM_ATTR_TARGET_UUID, "pcmk_external_ip" }; - gboolean do_delete = FALSE; - int lpc = 0; - static int meta_len = 0; - - if (meta_len == 0) { - meta_len = strlen(CRM_META); - } + const int meta_len = strlen(CRM_META); if (param_set == NULL) { return; } - for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) { + // Remove the specific attributes listed in attr_filter + for (int lpc = 0; lpc < DIMOF(attr_filter); lpc++) { xml_remove_prop(param_set, attr_filter[lpc]); } key = crm_meta_name(XML_LRM_ATTR_INTERVAL_MS); - interval_ms_s = crm_element_value_copy(param_set, key); + if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) { + interval_ms = 0; + } free(key); key = crm_meta_name(XML_ATTR_TIMEOUT); timeout = crm_element_value_copy(param_set, key); - if (param_set) { - xmlAttrPtr xIter = param_set->properties; + // Remove all CRM_meta_* attributes + for (xmlAttrPtr xIter = param_set->properties; xIter != NULL; ) { + const char *prop_name = (const char *) (xIter->name); - while (xIter) { - const char *prop_name = (const char *)xIter->name; + xIter = xIter->next; - xIter = xIter->next; - do_delete = FALSE; - if (strncasecmp(prop_name, CRM_META, meta_len) == 0) { - do_delete = TRUE; - } - - if (do_delete) { - xml_remove_prop(param_set, prop_name); - } + // @TODO Why is this case-insensitive? + if (strncasecmp(prop_name, CRM_META, meta_len) == 0) { + xml_remove_prop(param_set, prop_name); } } - if (interval_ms_s && strcmp(interval_ms_s, "0")) { - /* Re-instate the operation's timeout value */ - if (timeout != NULL) { - crm_xml_add(param_set, key, timeout); - } + if ((interval_ms != 0) && (timeout != NULL)) { + // Add the timeout back, it's useful for recurring operation digests + crm_xml_add(param_set, key, timeout); } - free(interval_ms_s); free(timeout); free(key); } int rsc_op_expected_rc(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_LRM_OP_CANCELLED: case PCMK_LRM_OP_PENDING: return FALSE; break; case PCMK_LRM_OP_NOTSUPPORTED: case PCMK_LRM_OP_TIMEOUT: case PCMK_LRM_OP_ERROR: case PCMK_LRM_OP_NOT_CONNECTED: case PCMK_LRM_OP_INVALID: return TRUE; break; default: if (target_rc != op->rc) { return TRUE; } } return FALSE; } /*! * \brief Create a CIB XML element for an operation * * \param[in] parent If not NULL, make new XML node a child of this one * \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 meta-data is used to determine whether a reload is possible, and to * evaluate versioned parameters -- so if this op is not relevant to those * features, we don't need the meta-data. */ CRM_CHECK(rsc_class || op, return FALSE); if (rsc_class && is_not_set(pcmk_get_ra_caps(rsc_class), pcmk_ra_cap_params)) { /* Meta-data is only needed for resource classes that use parameters */ return FALSE; } /* Meta-data is only needed for these actions */ if (op && strcmp(op, CRMD_ACTION_START) && strcmp(op, CRMD_ACTION_STATUS) && strcmp(op, CRMD_ACTION_PROMOTE) && strcmp(op, CRMD_ACTION_DEMOTE) && strcmp(op, CRMD_ACTION_RELOAD) && strcmp(op, CRMD_ACTION_MIGRATE) && strcmp(op, CRMD_ACTION_MIGRATED) && strcmp(op, CRMD_ACTION_NOTIFY)) { return FALSE; } return TRUE; }