diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
commit | e5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch) | |
tree | a6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/pengine/pe_digest.c | |
parent | Initial commit. (diff) | |
download | pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip |
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/pengine/pe_digest.c')
-rw-r--r-- | lib/pengine/pe_digest.c | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/lib/pengine/pe_digest.c b/lib/pengine/pe_digest.c new file mode 100644 index 0000000..b8047da --- /dev/null +++ b/lib/pengine/pe_digest.c @@ -0,0 +1,592 @@ +/* + * Copyright 2004-2022 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 <glib.h> +#include <stdbool.h> + +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/common/xml.h> +#include <crm/common/xml_internal.h> +#include <crm/pengine/internal.h> +#include "pe_status_private.h" + +extern bool pcmk__is_daemon; + +/*! + * \internal + * \brief Free an operation digest cache entry + * + * \param[in,out] ptr Pointer to cache entry to free + * + * \note The argument is a gpointer so this can be used as a hash table + * free function. + */ +void +pe__free_digests(gpointer ptr) +{ + op_digest_cache_t *data = ptr; + + if (data != NULL) { + free_xml(data->params_all); + free_xml(data->params_secure); + free_xml(data->params_restart); + + free(data->digest_all_calc); + free(data->digest_restart_calc); + free(data->digest_secure_calc); + + free(data); + } +} + +// Return true if XML attribute name is not substring of a given string +static bool +attr_not_in_string(xmlAttrPtr a, void *user_data) +{ + bool filter = false; + char *name = crm_strdup_printf(" %s ", (const char *) a->name); + + if (strstr((const char *) user_data, name) == NULL) { + crm_trace("Filtering %s (not found in '%s')", + (const char *) a->name, (const char *) user_data); + filter = true; + } + free(name); + return filter; +} + +// Return true if XML attribute name is substring of a given string +static bool +attr_in_string(xmlAttrPtr a, void *user_data) +{ + bool filter = false; + char *name = crm_strdup_printf(" %s ", (const char *) a->name); + + if (strstr((const char *) user_data, name) != NULL) { + crm_trace("Filtering %s (found in '%s')", + (const char *) a->name, (const char *) user_data); + filter = true; + } + free(name); + return filter; +} + +/*! + * \internal + * \brief Add digest of all parameters to a digest cache entry + * + * \param[out] data Digest cache entry to modify + * \param[in,out] rsc Resource that action was for + * \param[in] node Node action was performed on + * \param[in] params Resource parameters evaluated for node + * \param[in] task Name of action performed + * \param[in,out] interval_ms Action's interval (will be reset if in overrides) + * \param[in] xml_op Unused + * \param[in] op_version CRM feature set to use for digest calculation + * \param[in] overrides Key/value table to override resource parameters + * \param[in,out] data_set Cluster working set + */ +static void +calculate_main_digest(op_digest_cache_t *data, pe_resource_t *rsc, + const pe_node_t *node, GHashTable *params, + const char *task, guint *interval_ms, + const xmlNode *xml_op, const char *op_version, + GHashTable *overrides, pe_working_set_t *data_set) +{ + pe_action_t *action = NULL; + + data->params_all = create_xml_node(NULL, XML_TAG_PARAMS); + + /* REMOTE_CONTAINER_HACK: Allow Pacemaker Remote nodes to run containers + * that themselves are Pacemaker Remote nodes + */ + (void) pe__add_bundle_remote_name(rsc, data_set, data->params_all, + XML_RSC_ATTR_REMOTE_RA_ADDR); + + // If interval was overridden, reset it + if (overrides != NULL) { + const char *interval_s = g_hash_table_lookup(overrides, CRM_META "_" + XML_LRM_ATTR_INTERVAL); + + if (interval_s != NULL) { + long long value_ll; + + if ((pcmk__scan_ll(interval_s, &value_ll, 0LL) == pcmk_rc_ok) + && (value_ll >= 0) && (value_ll <= G_MAXUINT)) { + *interval_ms = (guint) value_ll; + } + } + } + + action = custom_action(rsc, pcmk__op_key(rsc->id, task, *interval_ms), + task, node, TRUE, FALSE, data_set); + if (overrides != NULL) { + g_hash_table_foreach(overrides, hash2field, data->params_all); + } + g_hash_table_foreach(params, hash2field, data->params_all); + g_hash_table_foreach(action->extra, hash2field, data->params_all); + g_hash_table_foreach(action->meta, hash2metafield, data->params_all); + + pcmk__filter_op_for_digest(data->params_all); + + /* Given a non-recurring operation with extra parameters configured, + * in case that the main digest doesn't match, even if the restart + * digest matches, enforce a restart rather than a reload-agent anyway. + * So that it ensures any changes of the extra parameters get applied + * for this specific operation, and the digests calculated for the + * resulting lrm_rsc_op will be correct. + * Mark the implied rc RSC_DIGEST_RESTART for the case that the main + * digest doesn't match. + */ + if (*interval_ms == 0 + && g_hash_table_size(action->extra) > 0) { + data->rc = RSC_DIGEST_RESTART; + } + + pe_free_action(action); + + data->digest_all_calc = calculate_operation_digest(data->params_all, + op_version); +} + +// Return true if XML attribute name is a Pacemaker-defined fencing parameter +static bool +is_fence_param(xmlAttrPtr attr, void *user_data) +{ + return pcmk_stonith_param((const char *) attr->name); +} + +/*! + * \internal + * \brief Add secure digest to a digest cache entry + * + * \param[out] data Digest cache entry to modify + * \param[in] rsc Resource that action was for + * \param[in] params Resource parameters evaluated for node + * \param[in] xml_op XML of operation in CIB status (if available) + * \param[in] op_version CRM feature set to use for digest calculation + * \param[in] overrides Key/value hash table to override resource parameters + */ +static void +calculate_secure_digest(op_digest_cache_t *data, const pe_resource_t *rsc, + GHashTable *params, const xmlNode *xml_op, + const char *op_version, GHashTable *overrides) +{ + const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); + const char *secure_list = NULL; + bool old_version = (compare_version(op_version, "3.16.0") < 0); + + if (xml_op == NULL) { + secure_list = " passwd password user "; + } else { + secure_list = crm_element_value(xml_op, XML_LRM_ATTR_OP_SECURE); + } + + if (old_version) { + data->params_secure = create_xml_node(NULL, XML_TAG_PARAMS); + if (overrides != NULL) { + g_hash_table_foreach(overrides, hash2field, data->params_secure); + } + + g_hash_table_foreach(params, hash2field, data->params_secure); + + } else { + // Start with a copy of all parameters + data->params_secure = copy_xml(data->params_all); + } + + if (secure_list != NULL) { + pcmk__xe_remove_matching_attrs(data->params_secure, attr_in_string, + (void *) secure_list); + } + if (old_version + && pcmk_is_set(pcmk_get_ra_caps(class), + pcmk_ra_cap_fence_params)) { + /* For stonith resources, Pacemaker adds special parameters, + * but these are not listed in fence agent meta-data, so with older + * versions of DC, the controller will not hash them. That means we have + * to filter them out before calculating our hash for comparison. + */ + pcmk__xe_remove_matching_attrs(data->params_secure, is_fence_param, + NULL); + } + pcmk__filter_op_for_digest(data->params_secure); + + /* CRM_meta_timeout *should* be part of a digest for recurring operations. + * However, with older versions of DC, the controller does not add timeout + * to secure digests, because it only includes parameters declared by the + * resource agent. + * Remove any timeout that made it this far, to match. + */ + if (old_version) { + xml_remove_prop(data->params_secure, CRM_META "_" XML_ATTR_TIMEOUT); + } + + data->digest_secure_calc = calculate_operation_digest(data->params_secure, + op_version); +} + +/*! + * \internal + * \brief Add restart digest to a digest cache entry + * + * \param[out] data Digest cache entry to modify + * \param[in] xml_op XML of operation in CIB status (if available) + * \param[in] op_version CRM feature set to use for digest calculation + * + * \note This function doesn't need to handle overrides because it starts with + * data->params_all, which already has overrides applied. + */ +static void +calculate_restart_digest(op_digest_cache_t *data, const xmlNode *xml_op, + const char *op_version) +{ + const char *value = NULL; + + // We must have XML of resource operation history + if (xml_op == NULL) { + return; + } + + // And the history must have a restart digest to compare against + if (crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST) == NULL) { + return; + } + + // Start with a copy of all parameters + data->params_restart = copy_xml(data->params_all); + + // Then filter out reloadable parameters, if any + value = crm_element_value(xml_op, XML_LRM_ATTR_OP_RESTART); + if (value != NULL) { + pcmk__xe_remove_matching_attrs(data->params_restart, attr_not_in_string, + (void *) value); + } + + value = crm_element_value(xml_op, XML_ATTR_CRM_VERSION); + data->digest_restart_calc = calculate_operation_digest(data->params_restart, + value); +} + +/*! + * \internal + * \brief Create a new digest cache entry with calculated digests + * + * \param[in,out] rsc Resource that action was for + * \param[in] task Name of action performed + * \param[in,out] interval_ms Action's interval (will be reset if in overrides) + * \param[in] node Node action was performed on + * \param[in] xml_op XML of operation in CIB status (if available) + * \param[in] overrides Key/value table to override resource parameters + * \param[in] calc_secure Whether to calculate secure digest + * \param[in,out] data_set Cluster working set + * + * \return Pointer to new digest cache entry (or NULL on memory error) + * \note It is the caller's responsibility to free the result using + * pe__free_digests(). + */ +op_digest_cache_t * +pe__calculate_digests(pe_resource_t *rsc, const char *task, guint *interval_ms, + const pe_node_t *node, const xmlNode *xml_op, + GHashTable *overrides, bool calc_secure, + pe_working_set_t *data_set) +{ + op_digest_cache_t *data = calloc(1, sizeof(op_digest_cache_t)); + const char *op_version = NULL; + GHashTable *params = NULL; + + if (data == NULL) { + return NULL; + } + + data->rc = RSC_DIGEST_MATCH; + + if (xml_op != NULL) { + op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION); + } + + if (op_version == NULL && data_set != NULL && data_set->input != NULL) { + op_version = crm_element_value(data_set->input, XML_ATTR_CRM_VERSION); + } + + if (op_version == NULL) { + op_version = CRM_FEATURE_SET; + } + + params = pe_rsc_params(rsc, node, data_set); + calculate_main_digest(data, rsc, node, params, task, interval_ms, xml_op, + op_version, overrides, data_set); + if (calc_secure) { + calculate_secure_digest(data, rsc, params, xml_op, op_version, + overrides); + } + calculate_restart_digest(data, xml_op, op_version); + return data; +} + +/*! + * \internal + * \brief Calculate action digests and store in node's digest cache + * + * \param[in,out] rsc Resource that action was for + * \param[in] task Name of action performed + * \param[in] interval_ms Action's interval + * \param[in,out] node Node action was performed on + * \param[in] xml_op XML of operation in CIB status (if available) + * \param[in] calc_secure Whether to calculate secure digest + * \param[in,out] data_set Cluster working set + * + * \return Pointer to node's digest cache entry + */ +static op_digest_cache_t * +rsc_action_digest(pe_resource_t *rsc, const char *task, guint interval_ms, + pe_node_t *node, const xmlNode *xml_op, + bool calc_secure, pe_working_set_t *data_set) +{ + op_digest_cache_t *data = NULL; + char *key = pcmk__op_key(rsc->id, task, interval_ms); + + data = g_hash_table_lookup(node->details->digest_cache, key); + if (data == NULL) { + data = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op, + NULL, calc_secure, data_set); + CRM_ASSERT(data != NULL); + g_hash_table_insert(node->details->digest_cache, strdup(key), data); + } + free(key); + return data; +} + +/*! + * \internal + * \brief Calculate operation digests and compare against an XML history entry + * + * \param[in,out] rsc Resource to check + * \param[in] xml_op Resource history XML + * \param[in,out] node Node to use for digest calculation + * \param[in,out] data_set Cluster working set + * + * \return Pointer to node's digest cache entry, with comparison result set + */ +op_digest_cache_t * +rsc_action_digest_cmp(pe_resource_t *rsc, const xmlNode *xml_op, + pe_node_t *node, pe_working_set_t *data_set) +{ + op_digest_cache_t *data = NULL; + guint interval_ms = 0; + + const char *op_version; + const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); + const char *digest_all; + const char *digest_restart; + + CRM_ASSERT(node != NULL); + + op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION); + digest_all = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST); + digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST); + + crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms); + data = rsc_action_digest(rsc, task, interval_ms, node, xml_op, + pcmk_is_set(data_set->flags, pe_flag_sanitized), + data_set); + + if (digest_restart && data->digest_restart_calc && strcmp(data->digest_restart_calc, digest_restart) != 0) { + pe_rsc_info(rsc, "Parameters to %ums-interval %s action for %s on %s " + "changed: hash was %s vs. now %s (restart:%s) %s", + interval_ms, task, rsc->id, pe__node_name(node), + pcmk__s(digest_restart, "missing"), + data->digest_restart_calc, + op_version, + crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC)); + data->rc = RSC_DIGEST_RESTART; + + } else if (digest_all == NULL) { + /* it is unknown what the previous op digest was */ + data->rc = RSC_DIGEST_UNKNOWN; + + } else if (strcmp(digest_all, data->digest_all_calc) != 0) { + /* Given a non-recurring operation with extra parameters configured, + * in case that the main digest doesn't match, even if the restart + * digest matches, enforce a restart rather than a reload-agent anyway. + * So that it ensures any changes of the extra parameters get applied + * for this specific operation, and the digests calculated for the + * resulting lrm_rsc_op will be correct. + * Preserve the implied rc RSC_DIGEST_RESTART for the case that the main + * digest doesn't match. + */ + if (interval_ms == 0 + && data->rc == RSC_DIGEST_RESTART) { + pe_rsc_info(rsc, "Parameters containing extra ones to %ums-interval" + " %s action for %s on %s " + "changed: hash was %s vs. now %s (restart:%s) %s", + interval_ms, task, rsc->id, pe__node_name(node), + pcmk__s(digest_all, "missing"), data->digest_all_calc, + op_version, + crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC)); + + } else { + pe_rsc_info(rsc, "Parameters to %ums-interval %s action for %s on %s " + "changed: hash was %s vs. now %s (%s:%s) %s", + interval_ms, task, rsc->id, pe__node_name(node), + pcmk__s(digest_all, "missing"), data->digest_all_calc, + (interval_ms > 0)? "reschedule" : "reload", + op_version, + crm_element_value(xml_op, XML_ATTR_TRANSITION_MAGIC)); + data->rc = RSC_DIGEST_ALL; + } + + } else { + data->rc = RSC_DIGEST_MATCH; + } + return data; +} + +/*! + * \internal + * \brief Create an unfencing summary for use in special node attribute + * + * Create a string combining a fence device's resource ID, agent type, and + * parameter digest (whether for all parameters or just non-private parameters). + * This can be stored in a special node attribute, allowing us to detect changes + * in either the agent type or parameters, to know whether unfencing must be + * redone or can be safely skipped when the device's history is cleaned. + * + * \param[in] rsc_id Fence device resource ID + * \param[in] agent_type Fence device agent + * \param[in] param_digest Fence device parameter digest + * + * \return Newly allocated string with unfencing digest + * \note The caller is responsible for freeing the result. + */ +static inline char * +create_unfencing_summary(const char *rsc_id, const char *agent_type, + const char *param_digest) +{ + return crm_strdup_printf("%s:%s:%s", rsc_id, agent_type, param_digest); +} + +/*! + * \internal + * \brief Check whether a node can skip unfencing + * + * Check whether a fence device's current definition matches a node's + * stored summary of when it was last unfenced by the device. + * + * \param[in] rsc_id Fence device's resource ID + * \param[in] agent Fence device's agent type + * \param[in] digest_calc Fence device's current parameter digest + * \param[in] node_summary Value of node's special unfencing node attribute + * (a comma-separated list of unfencing summaries for + * all devices that have unfenced this node) + * + * \return TRUE if digest matches, FALSE otherwise + */ +static bool +unfencing_digest_matches(const char *rsc_id, const char *agent, + const char *digest_calc, const char *node_summary) +{ + bool matches = FALSE; + + if (rsc_id && agent && digest_calc && node_summary) { + char *search_secure = create_unfencing_summary(rsc_id, agent, + digest_calc); + + /* The digest was calculated including the device ID and agent, + * so there is no risk of collision using strstr(). + */ + matches = (strstr(node_summary, search_secure) != NULL); + crm_trace("Calculated unfencing digest '%s' %sfound in '%s'", + search_secure, matches? "" : "not ", node_summary); + free(search_secure); + } + return matches; +} + +/* Magic string to use as action name for digest cache entries used for + * unfencing checks. This is not a real action name (i.e. "on"), so + * pcmk__check_action_config() won't confuse these entries with real actions. + */ +#define STONITH_DIGEST_TASK "stonith-on" + +/*! + * \internal + * \brief Calculate fence device digests and digest comparison result + * + * \param[in,out] rsc Fence device resource + * \param[in] agent Fence device's agent type + * \param[in,out] node Node with digest cache to use + * \param[in,out] data_set Cluster working set + * + * \return Node's digest cache entry + */ +op_digest_cache_t * +pe__compare_fencing_digest(pe_resource_t *rsc, const char *agent, + pe_node_t *node, pe_working_set_t *data_set) +{ + const char *node_summary = NULL; + + // Calculate device's current parameter digests + op_digest_cache_t *data = rsc_action_digest(rsc, STONITH_DIGEST_TASK, 0U, + node, NULL, TRUE, data_set); + + // Check whether node has special unfencing summary node attribute + node_summary = pe_node_attribute_raw(node, CRM_ATTR_DIGESTS_ALL); + if (node_summary == NULL) { + data->rc = RSC_DIGEST_UNKNOWN; + return data; + } + + // Check whether full parameter digest matches + if (unfencing_digest_matches(rsc->id, agent, data->digest_all_calc, + node_summary)) { + data->rc = RSC_DIGEST_MATCH; + return data; + } + + // Check whether secure parameter digest matches + node_summary = pe_node_attribute_raw(node, CRM_ATTR_DIGESTS_SECURE); + if (unfencing_digest_matches(rsc->id, agent, data->digest_secure_calc, + node_summary)) { + data->rc = RSC_DIGEST_MATCH; + if (!pcmk__is_daemon && data_set->priv != NULL) { + pcmk__output_t *out = data_set->priv; + out->info(out, "Only 'private' parameters to %s " + "for unfencing %s changed", rsc->id, + pe__node_name(node)); + } + return data; + } + + // Parameters don't match + data->rc = RSC_DIGEST_ALL; + if (pcmk_is_set(data_set->flags, pe_flag_sanitized) && data->digest_secure_calc) { + if (data_set->priv != NULL) { + pcmk__output_t *out = data_set->priv; + char *digest = create_unfencing_summary(rsc->id, agent, + data->digest_secure_calc); + + out->info(out, "Parameters to %s for unfencing " + "%s changed, try '%s'", rsc->id, + pe__node_name(node), digest); + free(digest); + } else if (!pcmk__is_daemon) { + char *digest = create_unfencing_summary(rsc->id, agent, + data->digest_secure_calc); + + printf("Parameters to %s for unfencing %s changed, try '%s'\n", + rsc->id, pe__node_name(node), digest); + free(digest); + } + } + return data; +} |