summaryrefslogtreecommitdiffstats
path: root/lib/pengine/pe_digest.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/pengine/pe_digest.c
parentInitial commit. (diff)
downloadpacemaker-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.c592
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;
+}