pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
pe_digest.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2024 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/common/xml.h>
18 #include <crm/pengine/internal.h>
19 #include "pe_status_private.h"
20 
21 extern bool pcmk__is_daemon;
22 
32 void
33 pe__free_digests(gpointer ptr)
34 {
35  pcmk__op_digest_t *data = ptr;
36 
37  if (data != NULL) {
38  pcmk__xml_free(data->params_all);
39  pcmk__xml_free(data->params_secure);
40  pcmk__xml_free(data->params_restart);
41 
42  free(data->digest_all_calc);
43  free(data->digest_restart_calc);
44  free(data->digest_secure_calc);
45 
46  free(data);
47  }
48 }
49 
50 // Return true if XML attribute name is not substring of a given string
51 static bool
52 attr_not_in_string(xmlAttrPtr a, void *user_data)
53 {
54  bool filter = false;
55  char *name = crm_strdup_printf(" %s ", (const char *) a->name);
56 
57  if (strstr((const char *) user_data, name) == NULL) {
58  crm_trace("Filtering %s (not found in '%s')",
59  (const char *) a->name, (const char *) user_data);
60  filter = true;
61  }
62  free(name);
63  return filter;
64 }
65 
66 // Return true if XML attribute name is substring of a given string
67 static bool
68 attr_in_string(xmlAttrPtr a, void *user_data)
69 {
70  bool filter = false;
71  char *name = crm_strdup_printf(" %s ", (const char *) a->name);
72 
73  if (strstr((const char *) user_data, name) != NULL) {
74  crm_trace("Filtering %s (found in '%s')",
75  (const char *) a->name, (const char *) user_data);
76  filter = true;
77  }
78  free(name);
79  return filter;
80 }
81 
97 static void
98 calculate_main_digest(pcmk__op_digest_t *data, pcmk_resource_t *rsc,
99  const pcmk_node_t *node, GHashTable *params,
100  const char *task, guint *interval_ms,
101  const xmlNode *xml_op, const char *op_version,
102  GHashTable *overrides, pcmk_scheduler_t *scheduler)
103 {
104  xmlNode *action_config = NULL;
105 
106  data->params_all = pcmk__xe_create(NULL, PCMK_XE_PARAMETERS);
107 
108  /* REMOTE_CONTAINER_HACK: Allow Pacemaker Remote nodes to run containers
109  * that themselves are Pacemaker Remote nodes
110  */
111  (void) pe__add_bundle_remote_name(rsc, data->params_all,
113 
114  if (overrides != NULL) {
115  // If interval was overridden, reset it
116  const char *meta_name = CRM_META "_" PCMK_META_INTERVAL;
117  const char *interval_s = g_hash_table_lookup(overrides, meta_name);
118 
119  if (interval_s != NULL) {
120  long long value_ll;
121 
122  if ((pcmk__scan_ll(interval_s, &value_ll, 0LL) == pcmk_rc_ok)
123  && (value_ll >= 0) && (value_ll <= G_MAXUINT)) {
124  *interval_ms = (guint) value_ll;
125  }
126  }
127 
128  // Add overrides to list of all parameters
129  g_hash_table_foreach(overrides, hash2field, data->params_all);
130  }
131 
132  // Add provided instance parameters
133  g_hash_table_foreach(params, hash2field, data->params_all);
134 
135  // Find action configuration XML in CIB
136  action_config = pcmk__find_action_config(rsc, task, *interval_ms, true);
137 
138  /* Add action-specific resource instance attributes to the digest list.
139  *
140  * If this is a one-time action with action-specific instance attributes,
141  * enforce a restart instead of reload-agent in case the main digest doesn't
142  * match, even if the restart digest does. This ensures any changes of the
143  * action-specific parameters get applied for this specific action, and
144  * digests calculated for the resulting history will be correct. Default the
145  * result to RSC_DIGEST_RESTART for the case where the main digest doesn't
146  * match.
147  */
148  params = pcmk__unpack_action_rsc_params(action_config, node->priv->attrs,
149  scheduler);
150  if ((*interval_ms == 0) && (g_hash_table_size(params) > 0)) {
152  }
153  g_hash_table_foreach(params, hash2field, data->params_all);
154  g_hash_table_destroy(params);
155 
156  // Add action meta-attributes
157  params = pcmk__unpack_action_meta(rsc, node, task, *interval_ms,
158  action_config);
159  g_hash_table_foreach(params, hash2metafield, data->params_all);
160  g_hash_table_destroy(params);
161 
162  pcmk__filter_op_for_digest(data->params_all);
163 
164  data->digest_all_calc = pcmk__digest_operation(data->params_all);
165 }
166 
167 // Return true if XML attribute name is a Pacemaker-defined fencing parameter
168 static bool
169 is_fence_param(xmlAttrPtr attr, void *user_data)
170 {
171  return pcmk_stonith_param((const char *) attr->name);
172 }
173 
185 static void
186 calculate_secure_digest(pcmk__op_digest_t *data, const pcmk_resource_t *rsc,
187  GHashTable *params, const xmlNode *xml_op,
188  const char *op_version, GHashTable *overrides)
189 {
190  const char *class = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS);
191  const char *secure_list = NULL;
192  bool old_version = (compare_version(op_version, "3.16.0") < 0);
193 
194  if (xml_op == NULL) {
195  secure_list = " passwd password user ";
196  } else {
197  secure_list = crm_element_value(xml_op, PCMK__XA_OP_SECURE_PARAMS);
198  }
199 
200  if (old_version) {
201  data->params_secure = pcmk__xe_create(NULL, PCMK_XE_PARAMETERS);
202  if (overrides != NULL) {
203  g_hash_table_foreach(overrides, hash2field, data->params_secure);
204  }
205 
206  g_hash_table_foreach(params, hash2field, data->params_secure);
207 
208  } else {
209  // Start with a copy of all parameters
210  data->params_secure = pcmk__xml_copy(NULL, data->params_all);
211  }
212 
213  if (secure_list != NULL) {
214  pcmk__xe_remove_matching_attrs(data->params_secure, attr_in_string,
215  (void *) secure_list);
216  }
217  if (old_version
218  && pcmk_is_set(pcmk_get_ra_caps(class),
220  /* For stonith resources, Pacemaker adds special parameters,
221  * but these are not listed in fence agent meta-data, so with older
222  * versions of DC, the controller will not hash them. That means we have
223  * to filter them out before calculating our hash for comparison.
224  */
225  pcmk__xe_remove_matching_attrs(data->params_secure, is_fence_param,
226  NULL);
227  }
228  pcmk__filter_op_for_digest(data->params_secure);
229 
230  /* CRM_meta_timeout *should* be part of a digest for recurring operations.
231  * However, with older versions of DC, the controller does not add timeout
232  * to secure digests, because it only includes parameters declared by the
233  * resource agent.
234  * Remove any timeout that made it this far, to match.
235  */
236  if (old_version) {
237  pcmk__xe_remove_attr(data->params_secure,
239  }
240 
241  data->digest_secure_calc = pcmk__digest_operation(data->params_secure);
242 }
243 
255 static void
256 calculate_restart_digest(pcmk__op_digest_t *data, const xmlNode *xml_op,
257  const char *op_version)
258 {
259  const char *value = NULL;
260 
261  // We must have XML of resource operation history
262  if (xml_op == NULL) {
263  return;
264  }
265 
266  // And the history must have a restart digest to compare against
267  if (crm_element_value(xml_op, PCMK__XA_OP_RESTART_DIGEST) == NULL) {
268  return;
269  }
270 
271  // Start with a copy of all parameters
272  data->params_restart = pcmk__xml_copy(NULL, data->params_all);
273 
274  // Then filter out reloadable parameters, if any
276  if (value != NULL) {
277  pcmk__xe_remove_matching_attrs(data->params_restart, attr_not_in_string,
278  (void *) value);
279  }
280 
282  data->digest_restart_calc = pcmk__digest_operation(data->params_restart);
283 }
284 
303 pe__calculate_digests(pcmk_resource_t *rsc, const char *task,
304  guint *interval_ms, const pcmk_node_t *node,
305  const xmlNode *xml_op, GHashTable *overrides,
306  bool calc_secure, pcmk_scheduler_t *scheduler)
307 {
308  pcmk__op_digest_t *data = NULL;
309  const char *op_version = NULL;
310  GHashTable *params = NULL;
311 
312  CRM_CHECK(scheduler != NULL, return NULL);
313 
314  data = calloc(1, sizeof(pcmk__op_digest_t));
315  if (data == NULL) {
317  "Could not allocate memory for operation digest");
318  return NULL;
319  }
320 
321  data->rc = pcmk__digest_match;
322 
323  if (xml_op != NULL) {
324  op_version = crm_element_value(xml_op, PCMK_XA_CRM_FEATURE_SET);
325  }
326 
327  if ((op_version == NULL) && (scheduler->input != NULL)) {
328  op_version = crm_element_value(scheduler->input,
330  }
331 
332  if (op_version == NULL) {
333  op_version = CRM_FEATURE_SET;
334  }
335 
336  params = pe_rsc_params(rsc, node, scheduler);
337  calculate_main_digest(data, rsc, node, params, task, interval_ms, xml_op,
338  op_version, overrides, scheduler);
339  if (calc_secure) {
340  calculate_secure_digest(data, rsc, params, xml_op, op_version,
341  overrides);
342  }
343  calculate_restart_digest(data, xml_op, op_version);
344  return data;
345 }
346 
361 static pcmk__op_digest_t *
362 rsc_action_digest(pcmk_resource_t *rsc, const char *task, guint interval_ms,
363  pcmk_node_t *node, const xmlNode *xml_op,
364  bool calc_secure, pcmk_scheduler_t *scheduler)
365 {
366  pcmk__op_digest_t *data = NULL;
367  char *key = pcmk__op_key(rsc->id, task, interval_ms);
368 
369  data = g_hash_table_lookup(node->priv->digest_cache, key);
370  if (data == NULL) {
371  data = pe__calculate_digests(rsc, task, &interval_ms, node, xml_op,
372  NULL, calc_secure, scheduler);
373  pcmk__assert(data != NULL);
374  g_hash_table_insert(node->priv->digest_cache, strdup(key), data);
375  }
376  free(key);
377  return data;
378 }
379 
392 rsc_action_digest_cmp(pcmk_resource_t *rsc, const xmlNode *xml_op,
394 {
395  pcmk__op_digest_t *data = NULL;
396  guint interval_ms = 0;
397 
398  const char *op_version;
399  const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION);
400  const char *digest_all;
401  const char *digest_restart;
402 
403  pcmk__assert(node != NULL);
404 
405  op_version = crm_element_value(xml_op, PCMK_XA_CRM_FEATURE_SET);
406  digest_all = crm_element_value(xml_op, PCMK__XA_OP_DIGEST);
407  digest_restart = crm_element_value(xml_op, PCMK__XA_OP_RESTART_DIGEST);
408 
409  crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms);
410  data = rsc_action_digest(rsc, task, interval_ms, node, xml_op,
413  scheduler);
414 
415  if (digest_restart && data->digest_restart_calc && strcmp(data->digest_restart_calc, digest_restart) != 0) {
416  pcmk__rsc_info(rsc,
417  "Parameters to %ums-interval %s action for %s on %s "
418  "changed: hash was %s vs. now %s (restart:%s) %s",
419  interval_ms, task, rsc->id, pcmk__node_name(node),
420  pcmk__s(digest_restart, "missing"),
421  data->digest_restart_calc, op_version,
424 
425  } else if (digest_all == NULL) {
426  /* it is unknown what the previous op digest was */
428 
429  } else if (strcmp(digest_all, data->digest_all_calc) != 0) {
430  /* Given a non-recurring operation with extra parameters configured,
431  * in case that the main digest doesn't match, even if the restart
432  * digest matches, enforce a restart rather than a reload-agent anyway.
433  * So that it ensures any changes of the extra parameters get applied
434  * for this specific operation, and the digests calculated for the
435  * resulting PCMK__XE_LRM_RSC_OP will be correct.
436  * Preserve the implied rc pcmk__digest_restart for the case that the
437  * main digest doesn't match.
438  */
439  if ((interval_ms == 0) && (data->rc == pcmk__digest_restart)) {
440  pcmk__rsc_info(rsc,
441  "Parameters containing extra ones to %ums-interval"
442  " %s action for %s on %s "
443  "changed: hash was %s vs. now %s (restart:%s) %s",
444  interval_ms, task, rsc->id, pcmk__node_name(node),
445  pcmk__s(digest_all, "missing"),
446  data->digest_all_calc, op_version,
447  crm_element_value(xml_op,
449 
450  } else {
451  pcmk__rsc_info(rsc,
452  "Parameters to %ums-interval %s action for %s on %s "
453  "changed: hash was %s vs. now %s (%s:%s) %s",
454  interval_ms, task, rsc->id, pcmk__node_name(node),
455  pcmk__s(digest_all, "missing"),
456  data->digest_all_calc,
457  (interval_ms > 0)? "reschedule" : "reload",
458  op_version,
459  crm_element_value(xml_op,
462  }
463 
464  } else {
465  data->rc = pcmk__digest_match;
466  }
467  return data;
468 }
469 
487 static inline char *
488 create_unfencing_summary(const char *rsc_id, const char *agent_type,
489  const char *param_digest)
490 {
491  return crm_strdup_printf("%s:%s:%s", rsc_id, agent_type, param_digest);
492 }
493 
510 static bool
511 unfencing_digest_matches(const char *rsc_id, const char *agent,
512  const char *digest_calc, const char *node_summary)
513 {
514  bool matches = FALSE;
515 
516  if (rsc_id && agent && digest_calc && node_summary) {
517  char *search_secure = create_unfencing_summary(rsc_id, agent,
518  digest_calc);
519 
520  /* The digest was calculated including the device ID and agent,
521  * so there is no risk of collision using strstr().
522  */
523  matches = (strstr(node_summary, search_secure) != NULL);
524  crm_trace("Calculated unfencing digest '%s' %sfound in '%s'",
525  search_secure, matches? "" : "not ", node_summary);
526  free(search_secure);
527  }
528  return matches;
529 }
530 
531 /* Magic string to use as action name for digest cache entries used for
532  * unfencing checks. This is not a real action name (i.e. "on"), so
533  * pcmk__check_action_config() won't confuse these entries with real actions.
534  */
535 #define STONITH_DIGEST_TASK "stonith-on"
536 
551 {
552  const char *node_summary = NULL;
553 
554  // Calculate device's current parameter digests
555  pcmk__op_digest_t *data = rsc_action_digest(rsc, STONITH_DIGEST_TASK, 0U,
556  node, NULL, TRUE, scheduler);
557 
558  // Check whether node has special unfencing summary node attribute
559  node_summary = pcmk__node_attr(node, CRM_ATTR_DIGESTS_ALL, NULL,
561  if (node_summary == NULL) {
563  return data;
564  }
565 
566  // Check whether full parameter digest matches
567  if (unfencing_digest_matches(rsc->id, agent, data->digest_all_calc,
568  node_summary)) {
569  data->rc = pcmk__digest_match;
570  return data;
571  }
572 
573  // Check whether secure parameter digest matches
574  node_summary = pcmk__node_attr(node, CRM_ATTR_DIGESTS_SECURE, NULL,
576  if (unfencing_digest_matches(rsc->id, agent, data->digest_secure_calc,
577  node_summary)) {
578  data->rc = pcmk__digest_match;
579  if (!pcmk__is_daemon && (scheduler->priv->out != NULL)) {
580  pcmk__output_t *out = scheduler->priv->out;
581 
582  out->info(out, "Only 'private' parameters to %s "
583  "for unfencing %s changed", rsc->id,
584  pcmk__node_name(node));
585  }
586  return data;
587  }
588 
589  // Parameters don't match
592  && (data->digest_secure_calc != NULL)) {
593 
594  if (scheduler->priv->out != NULL) {
595  pcmk__output_t *out = scheduler->priv->out;
596  char *digest = create_unfencing_summary(rsc->id, agent,
597  data->digest_secure_calc);
598 
599  out->info(out, "Parameters to %s for unfencing "
600  "%s changed, try '%s'", rsc->id,
601  pcmk__node_name(node), digest);
602  free(digest);
603  } else if (!pcmk__is_daemon) {
604  char *digest = create_unfencing_summary(rsc->id, agent,
605  data->digest_secure_calc);
606 
607  printf("Parameters to %s for unfencing %s changed, try '%s'\n",
608  rsc->id, pcmk__node_name(node), digest);
609  free(digest);
610  }
611  }
612  return data;
613 }
#define PCMK__XA_OP_FORCE_RESTART
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
A dumping ground.
#define pcmk__sched_err(scheduler, fmt...)
char data[0]
Definition: cpg.c:58
void pcmk__filter_op_for_digest(xmlNode *param_set)
Definition: digest.c:299
void hash2field(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry.
Definition: nvpair.c:249
const char * name
Definition: cib.c:26
#define PCMK__XA_TRANSITION_MAGIC
#define pcmk__rsc_info(rsc, fmt, args...)
const char * pe__add_bundle_remote_name(pcmk_resource_t *rsc, xmlNode *xml, const char *field)
Definition: bundle.c:908
#define CRM_FEATURE_SET
Definition: crm.h:66
bool pcmk_stonith_param(const char *param)
Check whether a given stonith parameter is handled by Pacemaker.
Definition: agents.c:166
#define PCMK_REMOTE_RA_ADDR
Definition: options.h:122
GHashTable * pcmk__unpack_action_rsc_params(const xmlNode *action_xml, GHashTable *node_attrs, pcmk_scheduler_t *data_set)
Definition: pe_actions.c:247
pcmk__scheduler_private_t * priv
Definition: scheduler.h:99
#define PCMK_XE_PARAMETERS
Definition: xml_names.h:159
uint64_t flags
Definition: scheduler.h:89
#define CRM_ATTR_DIGESTS_ALL
Definition: crm.h:100
#define PCMK__XA_OP_RESTART_DIGEST
int(* info)(pcmk__output_t *out, const char *format,...) G_GNUC_PRINTF(2
void pcmk__xe_remove_attr(xmlNode *element, const char *name)
Definition: xml_element.c:339
#define PCMK_XA_OPERATION
Definition: xml_names.h:349
xmlNode * pcmk__xe_create(xmlNode *parent, const char *name)
Definition: xml_element.c:407
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
pcmk__node_private_t * priv
Definition: nodes.h:85
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:92
int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
Retrieve the millisecond value of an XML attribute.
Definition: xml_element.c:1322
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define PCMK__XA_OP_DIGEST
#define crm_trace(fmt, args...)
Definition: logging.h:372
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
Wrappers for and extensions to libxml2.
bool pcmk__is_daemon
Definition: logging.c:47
pcmk__op_digest_t * rsc_action_digest_cmp(pcmk_resource_t *rsc, const xmlNode *xml_op, pcmk_node_t *node, pcmk_scheduler_t *scheduler)
Definition: pe_digest.c:392
#define CRM_ATTR_DIGESTS_SECURE
Definition: crm.h:101
GHashTable * digest_cache
char * pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms)
Generate an operation key (RESOURCE_ACTION_INTERVAL)
Definition: actions.c:195
#define PCMK_META_TIMEOUT
Definition: options.h:114
#define pcmk__assert(expr)
#define PCMK_XA_CRM_FEATURE_SET
Definition: xml_names.h:254
pcmk__op_digest_t * pe__compare_fencing_digest(pcmk_resource_t *rsc, const char *agent, pcmk_node_t *node, pcmk_scheduler_t *scheduler)
Definition: pe_digest.c:549
pcmk__op_digest_t * pe__calculate_digests(pcmk_resource_t *rsc, const char *task, guint *interval_ms, const pcmk_node_t *node, const xmlNode *xml_op, GHashTable *overrides, bool calc_secure, pcmk_scheduler_t *scheduler)
Definition: pe_digest.c:303
xmlNode * pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name, guint interval_ms, bool include_disabled)
Definition: pe_actions.c:137
#define PCMK_XA_CLASS
Definition: xml_names.h:246
#define PCMK_META_INTERVAL
Definition: options.h:91
#define CRM_META
Definition: crm.h:75
pcmk_scheduler_t * scheduler
#define STONITH_DIGEST_TASK
Definition: pe_digest.c:535
xmlNode * input
Definition: scheduler.h:81
const char * pcmk__node_attr(const pcmk_node_t *node, const char *name, const char *target, enum pcmk__rsc_node node_type)
Definition: attrs.c:114
This structure contains everything that makes up a single output formatter.
int compare_version(const char *version1, const char *version2)
Definition: utils.c:202
void hash2metafield(gpointer key, gpointer value, gpointer user_data)
Set XML attribute based on hash table entry, as meta-attribute name.
Definition: nvpair.c:277
GHashTable * attrs
char * pcmk__digest_operation(xmlNode *input)
Definition: digest.c:133
#define PCMK__XA_OP_SECURE_PARAMS
uint32_t pcmk_get_ra_caps(const char *standard)
Get capabilities of a resource agent standard.
Definition: agents.c:27
void pe__free_digests(gpointer ptr)
Definition: pe_digest.c:33
GHashTable * pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
Get a table of resource parameters.
Definition: complex.c:476
GHashTable * pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node, const char *action_name, guint interval_ms, const xmlNode *action_config)
Definition: pe_actions.c:682
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml_element.c:379
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1