pacemaker  2.1.5-b7adf64e51
Scalable High-Availability cluster resource manager
pe_digest.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2022 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #include <glib.h>
13 #include <stdbool.h>
14 
15 #include <crm/crm.h>
16 #include <crm/msg_xml.h>
17 #include <crm/common/xml.h>
19 #include <crm/pengine/internal.h>
20 #include "pe_status_private.h"
21 
22 extern bool pcmk__is_daemon;
23 
33 void
34 pe__free_digests(gpointer ptr)
35 {
36  op_digest_cache_t *data = ptr;
37 
38  if (data != NULL) {
39  free_xml(data->params_all);
40  free_xml(data->params_secure);
41  free_xml(data->params_restart);
42 
43  free(data->digest_all_calc);
44  free(data->digest_restart_calc);
45  free(data->digest_secure_calc);
46 
47  free(data);
48  }
49 }
50 
51 // Return true if XML attribute name is not substring of a given string
52 static bool
53 attr_not_in_string(xmlAttrPtr a, void *user_data)
54 {
55  bool filter = false;
56  char *name = crm_strdup_printf(" %s ", (const char *) a->name);
57 
58  if (strstr((const char *) user_data, name) == NULL) {
59  crm_trace("Filtering %s (not found in '%s')",
60  (const char *) a->name, (const char *) user_data);
61  filter = true;
62  }
63  free(name);
64  return filter;
65 }
66 
67 // Return true if XML attribute name is substring of a given string
68 static bool
69 attr_in_string(xmlAttrPtr a, void *user_data)
70 {
71  bool filter = false;
72  char *name = crm_strdup_printf(" %s ", (const char *) a->name);
73 
74  if (strstr((const char *) user_data, name) != NULL) {
75  crm_trace("Filtering %s (found in '%s')",
76  (const char *) a->name, (const char *) user_data);
77  filter = true;
78  }
79  free(name);
80  return filter;
81 }
82 
98 static void
99 calculate_main_digest(op_digest_cache_t *data, pe_resource_t *rsc,
100  pe_node_t *node, GHashTable *params,
101  const char *task, guint *interval_ms,
102  xmlNode *xml_op, const char *op_version,
103  GHashTable *overrides, pe_working_set_t *data_set)
104 {
105  pe_action_t *action = NULL;
106 
107  data->params_all = create_xml_node(NULL, XML_TAG_PARAMS);
108 
109  /* REMOTE_CONTAINER_HACK: Allow Pacemaker Remote nodes to run containers
110  * that themselves are Pacemaker Remote nodes
111  */
112  (void) pe__add_bundle_remote_name(rsc, data_set, data->params_all,
114 
115  // If interval was overridden, reset it
116  if (overrides != NULL) {
117  const char *interval_s = g_hash_table_lookup(overrides, CRM_META "_"
119 
120  if (interval_s != NULL) {
121  long long value_ll;
122 
123  if ((pcmk__scan_ll(interval_s, &value_ll, 0LL) == pcmk_rc_ok)
124  && (value_ll >= 0) && (value_ll <= G_MAXUINT)) {
125  *interval_ms = (guint) value_ll;
126  }
127  }
128  }
129 
130  action = custom_action(rsc, pcmk__op_key(rsc->id, task, *interval_ms),
131  task, node, TRUE, FALSE, data_set);
132  if (overrides != NULL) {
133  g_hash_table_foreach(overrides, hash2field, data->params_all);
134  }
135  g_hash_table_foreach(params, hash2field, data->params_all);
136  g_hash_table_foreach(action->extra, hash2field, data->params_all);
137  g_hash_table_foreach(action->meta, hash2metafield, data->params_all);
138 
139  pcmk__filter_op_for_digest(data->params_all);
140 
141  /* Given a non-recurring operation with extra parameters configured,
142  * in case that the main digest doesn't match, even if the restart
143  * digest matches, enforce a restart rather than a reload-agent anyway.
144  * So that it ensures any changes of the extra parameters get applied
145  * for this specific operation, and the digests calculated for the
146  * resulting lrm_rsc_op will be correct.
147  * Mark the implied rc RSC_DIGEST_RESTART for the case that the main
148  * digest doesn't match.
149  */
150  if (*interval_ms == 0
151  && g_hash_table_size(action->extra) > 0) {
152  data->rc = RSC_DIGEST_RESTART;
153  }
154 
156 
157  data->digest_all_calc = calculate_operation_digest(data->params_all,
158  op_version);
159 }
160 
161 // Return true if XML attribute name is a Pacemaker-defined fencing parameter
162 static bool
163 is_fence_param(xmlAttrPtr attr, void *user_data)
164 {
165  return pcmk_stonith_param((const char *) attr->name);
166 }
167 
179 static void
180 calculate_secure_digest(op_digest_cache_t *data, pe_resource_t *rsc,
181  GHashTable *params, xmlNode *xml_op,
182  const char *op_version, GHashTable *overrides)
183 {
184  const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
185  const char *secure_list = NULL;
186  bool old_version = (compare_version(op_version, "3.16.0") < 0);
187 
188  if (xml_op == NULL) {
189  secure_list = " passwd password user ";
190  } else {
191  secure_list = crm_element_value(xml_op, XML_LRM_ATTR_OP_SECURE);
192  }
193 
194  if (old_version) {
195  data->params_secure = create_xml_node(NULL, XML_TAG_PARAMS);
196  if (overrides != NULL) {
197  g_hash_table_foreach(overrides, hash2field, data->params_secure);
198  }
199 
200  g_hash_table_foreach(params, hash2field, data->params_secure);
201 
202  } else {
203  // Start with a copy of all parameters
204  data->params_secure = copy_xml(data->params_all);
205  }
206 
207  if (secure_list != NULL) {
208  pcmk__xe_remove_matching_attrs(data->params_secure, attr_in_string,
209  (void *) secure_list);
210  }
211  if (old_version
212  && pcmk_is_set(pcmk_get_ra_caps(class),
214  /* For stonith resources, Pacemaker adds special parameters,
215  * but these are not listed in fence agent meta-data, so with older
216  * versions of DC, the controller will not hash them. That means we have
217  * to filter them out before calculating our hash for comparison.
218  */
219  pcmk__xe_remove_matching_attrs(data->params_secure, is_fence_param,
220  NULL);
221  }
222  pcmk__filter_op_for_digest(data->params_secure);
223 
224  /* CRM_meta_timeout *should* be part of a digest for recurring operations.
225  * However, with older versions of DC, the controller does not add timeout
226  * to secure digests, because it only includes parameters declared by the
227  * resource agent.
228  * Remove any timeout that made it this far, to match.
229  */
230  if (old_version) {
231  xml_remove_prop(data->params_secure, CRM_META "_" XML_ATTR_TIMEOUT);
232  }
233 
234  data->digest_secure_calc = calculate_operation_digest(data->params_secure,
235  op_version);
236 }
237 
249 static void
250 calculate_restart_digest(op_digest_cache_t *data, xmlNode *xml_op,
251  const char *op_version)
252 {
253  const char *value = NULL;
254 
255  // We must have XML of resource operation history
256  if (xml_op == NULL) {
257  return;
258  }
259 
260  // And the history must have a restart digest to compare against
261  if (crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST) == NULL) {
262  return;
263  }
264 
265  // Start with a copy of all parameters
266  data->params_restart = copy_xml(data->params_all);
267 
268  // Then filter out reloadable parameters, if any
270  if (value != NULL) {
271  pcmk__xe_remove_matching_attrs(data->params_restart, attr_not_in_string,
272  (void *) value);
273  }
274 
275  value = crm_element_value(xml_op, XML_ATTR_CRM_VERSION);
276  data->digest_restart_calc = calculate_operation_digest(data->params_restart,
277  value);
278 }
279 
298 pe__calculate_digests(pe_resource_t *rsc, const char *task, guint *interval_ms,
299  pe_node_t *node, xmlNode *xml_op, GHashTable *overrides,
300  bool calc_secure, pe_working_set_t *data_set)
301 {
302  op_digest_cache_t *data = calloc(1, sizeof(op_digest_cache_t));
303  const char *op_version = NULL;
304  GHashTable *params = NULL;
305 
306  if (data == NULL) {
307  return NULL;
308  }
309 
310  data->rc = RSC_DIGEST_MATCH;
311 
312  if (xml_op != NULL) {
313  op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION);
314  }
315 
316  if (op_version == NULL && data_set != NULL && data_set->input != NULL) {
318  }
319 
320  if (op_version == NULL) {
321  op_version = CRM_FEATURE_SET;
322  }
323 
324  params = pe_rsc_params(rsc, node, data_set);
325  calculate_main_digest(data, rsc, node, params, task, interval_ms, xml_op,
326  op_version, overrides, data_set);
327  if (calc_secure) {
328  calculate_secure_digest(data, rsc, params, xml_op, op_version,
329  overrides);
330  }
331  calculate_restart_digest(data, xml_op, op_version);
332  return data;
333 }
334 
349 static op_digest_cache_t *
350 rsc_action_digest(pe_resource_t *rsc, const char *task, guint interval_ms,
351  pe_node_t *node, xmlNode *xml_op, bool calc_secure,
353 {
354  op_digest_cache_t *data = NULL;
355  char *key = pcmk__op_key(rsc->id, task, interval_ms);
356 
357  data = g_hash_table_lookup(node->details->digest_cache, key);
358  if (data == NULL) {
359  data = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op,
360  NULL, calc_secure, data_set);
361  CRM_ASSERT(data != NULL);
362  g_hash_table_insert(node->details->digest_cache, strdup(key), data);
363  }
364  free(key);
365  return data;
366 }
367 
380 rsc_action_digest_cmp(pe_resource_t * rsc, xmlNode * xml_op, pe_node_t * node,
382 {
383  op_digest_cache_t *data = NULL;
384  guint interval_ms = 0;
385 
386  const char *op_version;
387  const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
388  const char *digest_all;
389  const char *digest_restart;
390 
391  CRM_ASSERT(node != NULL);
392 
393  op_version = crm_element_value(xml_op, XML_ATTR_CRM_VERSION);
394  digest_all = crm_element_value(xml_op, XML_LRM_ATTR_OP_DIGEST);
395  digest_restart = crm_element_value(xml_op, XML_LRM_ATTR_RESTART_DIGEST);
396 
397  crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
398  data = rsc_action_digest(rsc, task, interval_ms, node, xml_op,
400  data_set);
401 
402  if (digest_restart && data->digest_restart_calc && strcmp(data->digest_restart_calc, digest_restart) != 0) {
403  pe_rsc_info(rsc, "Parameters to %ums-interval %s action for %s on %s "
404  "changed: hash was %s vs. now %s (restart:%s) %s",
405  interval_ms, task, rsc->id, pe__node_name(node),
406  pcmk__s(digest_restart, "missing"),
407  data->digest_restart_calc,
408  op_version,
410  data->rc = RSC_DIGEST_RESTART;
411 
412  } else if (digest_all == NULL) {
413  /* it is unknown what the previous op digest was */
414  data->rc = RSC_DIGEST_UNKNOWN;
415 
416  } else if (strcmp(digest_all, data->digest_all_calc) != 0) {
417  /* Given a non-recurring operation with extra parameters configured,
418  * in case that the main digest doesn't match, even if the restart
419  * digest matches, enforce a restart rather than a reload-agent anyway.
420  * So that it ensures any changes of the extra parameters get applied
421  * for this specific operation, and the digests calculated for the
422  * resulting lrm_rsc_op will be correct.
423  * Preserve the implied rc RSC_DIGEST_RESTART for the case that the main
424  * digest doesn't match.
425  */
426  if (interval_ms == 0
427  && data->rc == RSC_DIGEST_RESTART) {
428  pe_rsc_info(rsc, "Parameters containing extra ones to %ums-interval"
429  " %s action for %s on %s "
430  "changed: hash was %s vs. now %s (restart:%s) %s",
431  interval_ms, task, rsc->id, pe__node_name(node),
432  pcmk__s(digest_all, "missing"), data->digest_all_calc,
433  op_version,
435 
436  } else {
437  pe_rsc_info(rsc, "Parameters to %ums-interval %s action for %s on %s "
438  "changed: hash was %s vs. now %s (%s:%s) %s",
439  interval_ms, task, rsc->id, pe__node_name(node),
440  pcmk__s(digest_all, "missing"), data->digest_all_calc,
441  (interval_ms > 0)? "reschedule" : "reload",
442  op_version,
444  data->rc = RSC_DIGEST_ALL;
445  }
446 
447  } else {
448  data->rc = RSC_DIGEST_MATCH;
449  }
450  return data;
451 }
452 
470 static inline char *
471 create_unfencing_summary(const char *rsc_id, const char *agent_type,
472  const char *param_digest)
473 {
474  return crm_strdup_printf("%s:%s:%s", rsc_id, agent_type, param_digest);
475 }
476 
493 static bool
494 unfencing_digest_matches(const char *rsc_id, const char *agent,
495  const char *digest_calc, const char *node_summary)
496 {
497  bool matches = FALSE;
498 
499  if (rsc_id && agent && digest_calc && node_summary) {
500  char *search_secure = create_unfencing_summary(rsc_id, agent,
501  digest_calc);
502 
503  /* The digest was calculated including the device ID and agent,
504  * so there is no risk of collision using strstr().
505  */
506  matches = (strstr(node_summary, search_secure) != NULL);
507  crm_trace("Calculated unfencing digest '%s' %sfound in '%s'",
508  search_secure, matches? "" : "not ", node_summary);
509  free(search_secure);
510  }
511  return matches;
512 }
513 
514 /* Magic string to use as action name for digest cache entries used for
515  * unfencing checks. This is not a real action name (i.e. "on"), so
516  * pcmk__check_action_config() won't confuse these entries with real actions.
517  */
518 #define STONITH_DIGEST_TASK "stonith-on"
519 
534 {
535  const char *node_summary = NULL;
536 
537  // Calculate device's current parameter digests
538  op_digest_cache_t *data = rsc_action_digest(rsc, STONITH_DIGEST_TASK, 0U,
539  node, NULL, TRUE, data_set);
540 
541  // Check whether node has special unfencing summary node attribute
542  node_summary = pe_node_attribute_raw(node, CRM_ATTR_DIGESTS_ALL);
543  if (node_summary == NULL) {
544  data->rc = RSC_DIGEST_UNKNOWN;
545  return data;
546  }
547 
548  // Check whether full parameter digest matches
549  if (unfencing_digest_matches(rsc->id, agent, data->digest_all_calc,
550  node_summary)) {
551  data->rc = RSC_DIGEST_MATCH;
552  return data;
553  }
554 
555  // Check whether secure parameter digest matches
556  node_summary = pe_node_attribute_raw(node, CRM_ATTR_DIGESTS_SECURE);
557  if (unfencing_digest_matches(rsc->id, agent, data->digest_secure_calc,
558  node_summary)) {
559  data->rc = RSC_DIGEST_MATCH;
560  if (!pcmk__is_daemon && data_set->priv != NULL) {
561  pcmk__output_t *out = data_set->priv;
562  out->info(out, "Only 'private' parameters to %s "
563  "for unfencing %s changed", rsc->id,
564  pe__node_name(node));
565  }
566  return data;
567  }
568 
569  // Parameters don't match
570  data->rc = RSC_DIGEST_ALL;
571  if (pcmk_is_set(data_set->flags, pe_flag_sanitized) && data->digest_secure_calc) {
572  if (data_set->priv != NULL) {
573  pcmk__output_t *out = data_set->priv;
574  char *digest = create_unfencing_summary(rsc->id, agent,
575  data->digest_secure_calc);
576 
577  out->info(out, "Parameters to %s for unfencing "
578  "%s changed, try '%s'", rsc->id,
579  pe__node_name(node), digest);
580  free(digest);
581  } else if (!pcmk__is_daemon) {
582  char *digest = create_unfencing_summary(rsc->id, agent,
583  data->digest_secure_calc);
584 
585  printf("Parameters to %s for unfencing %s changed, try '%s'\n",
586  rsc->id, pe__node_name(node), digest);
587  free(digest);
588  }
589  }
590  return data;
591 }
A dumping ground.
char data[0]
Definition: cpg.c:55
void hash2field(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry.
Definition: nvpair.c:770
#define XML_ATTR_TRANSITION_MAGIC
Definition: msg_xml.h:412
const char * name
Definition: cib.c:24
op_digest_cache_t * rsc_action_digest_cmp(pe_resource_t *rsc, xmlNode *xml_op, pe_node_t *node, pe_working_set_t *data_set)
Definition: pe_digest.c:380
#define CRM_FEATURE_SET
Definition: crm.h:69
bool pcmk_stonith_param(const char *param)
Check whether a given stonith parameter is handled by Pacemaker.
Definition: agents.c:174
#define XML_LRM_ATTR_INTERVAL
Definition: msg_xml.h:294
#define XML_LRM_ATTR_OP_DIGEST
Definition: msg_xml.h:313
#define XML_ATTR_TIMEOUT
Definition: msg_xml.h:128
#define XML_LRM_ATTR_OP_RESTART
Definition: msg_xml.h:314
#define CRM_ATTR_DIGESTS_ALL
Definition: crm.h:121
void pcmk__filter_op_for_digest(xmlNode *param_set)
Definition: operations.c:390
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
#define XML_RSC_ATTR_REMOTE_RA_ADDR
Definition: msg_xml.h:250
const char * action
Definition: pcmk_fence.c:30
#define XML_LRM_ATTR_OP_SECURE
Definition: msg_xml.h:315
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:891
#define XML_LRM_ATTR_TASK
Definition: msg_xml.h:300
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:97
char * calculate_operation_digest(xmlNode *local_cib, const char *version)
Calculate and return digest of XML operation.
Definition: digest.c:153
int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
Retrieve the millisecond value of an XML attribute.
Definition: nvpair.c:610
#define pe_flag_sanitized
Definition: pe_types.h:121
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:517
const char * pe_node_attribute_raw(const pe_node_t *node, const char *name)
Definition: common.c:562
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml.c:682
#define crm_trace(fmt, args...)
Definition: logging.h:365
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:121
struct pe_node_shared_s * details
Definition: pe_types.h:252
pe_working_set_t * data_set
Wrappers for and extensions to libxml2.
bool pcmk__is_daemon
Definition: logging.c:47
xmlNode * create_xml_node(xmlNode *parent, const char *name)
Definition: xml.c:749
#define CRM_ATTR_DIGESTS_SECURE
Definition: crm.h:122
#define XML_LRM_ATTR_RESTART_DIGEST
Definition: msg_xml.h:316
void free_xml(xmlNode *child)
Definition: xml.c:885
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)
Definition: pe_digest.c:532
xmlNode * input
Definition: pe_types.h:144
op_digest_cache_t * pe__calculate_digests(pe_resource_t *rsc, const char *task, guint *interval_ms, pe_node_t *node, xmlNode *xml_op, GHashTable *overrides, bool calc_secure, pe_working_set_t *data_set)
Definition: pe_digest.c:298
char * pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
Generate an operation key (RESOURCE_ACTION_INTERVAL)
Definition: operations.c:45
const char * pe__add_bundle_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set, xmlNode *xml, const char *field)
Definition: bundle.c:708
#define CRM_META
Definition: crm.h:78
#define CRM_ASSERT(expr)
Definition: results.h:42
#define STONITH_DIGEST_TASK
Definition: pe_digest.c:518
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:2145
This structure contains everything that makes up a single output formatter.
int compare_version(const char *version1, const char *version2)
Definition: utils.c:189
GHashTable * pe_rsc_params(pe_resource_t *rsc, const pe_node_t *node, pe_working_set_t *data_set)
Get a table of resource parameters.
Definition: complex.c:429
#define XML_LRM_ATTR_INTERVAL_MS
Definition: msg_xml.h:298
void hash2metafield(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry, as meta-attribute name.
Definition: nvpair.c:798
#define XML_ATTR_CRM_VERSION
Definition: msg_xml.h:118
GHashTable * digest_cache
cache of calculated resource digests
Definition: pe_types.h:243
uint32_t pcmk_get_ra_caps(const char *standard)
Get capabilities of a resource agent standard.
Definition: agents.c:31
void pe__free_digests(gpointer ptr)
Definition: pe_digest.c:34
unsigned long long flags
Definition: pe_types.h:153
pe_action_t * custom_action(pe_resource_t *rsc, char *key, const char *task, const pe_node_t *on_node, gboolean optional, gboolean foo, pe_working_set_t *data_set)
Create or update an action object.
Definition: pe_actions.c:940
#define XML_TAG_PARAMS
Definition: msg_xml.h:212
void pe_free_action(pe_action_t *action)
Definition: pe_actions.c:1193
#define pe_rsc_info(rsc, fmt, args...)
Definition: internal.h:45
#define XML_AGENT_ATTR_CLASS
Definition: msg_xml.h:269
char * id
Definition: pe_types.h:329