1 /*
2 * Copyright 2004-2026 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 <stdbool.h>
13
14 #include <libxml/tree.h> // xmlNode
15
16 #include <crm/pengine/internal.h>
17 #include <crm/common/xml.h>
18
19 #include "pe_status_private.h"
20
21 void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length);
22
23 static pcmk_node_t *active_node(const pcmk_resource_t *rsc,
24 unsigned int *count_all,
25 unsigned int *count_clean);
26
27 static pcmk__rsc_methods_t resource_class_functions[] = {
28 {
29 native_unpack,
30 native_find_rsc,
31 native_active,
32 native_resource_state,
33 native_location,
34 native_free,
35 pe__count_common,
36 pe__native_is_filtered,
37 active_node,
38 pe__primitive_max_per_node,
39 },
40 {
41 group_unpack,
42 native_find_rsc,
43 group_active,
44 group_resource_state,
45 native_location,
46 group_free,
47 pe__count_common,
48 pe__group_is_filtered,
49 active_node,
50 pe__group_max_per_node,
51 },
52 {
53 clone_unpack,
54 native_find_rsc,
55 clone_active,
56 clone_resource_state,
57 native_location,
58 clone_free,
59 pe__count_common,
60 pe__clone_is_filtered,
61 active_node,
62 pe__clone_max_per_node,
63 },
64 {
65 pe__unpack_bundle,
66 native_find_rsc,
67 pe__bundle_active,
68 pe__bundle_resource_state,
69 native_location,
70 pe__free_bundle,
71 pe__count_bundle,
72 pe__bundle_is_filtered,
73 pe__bundle_active_node,
74 pe__bundle_max_per_node,
75 }
76 };
77
78 static enum pcmk__rsc_variant
79 get_resource_type(const char *name)
80 {
81 if (pcmk__str_eq(name, PCMK_XE_PRIMITIVE, pcmk__str_casei)) {
82 return pcmk__rsc_variant_primitive;
83
84 } else if (pcmk__str_eq(name, PCMK_XE_GROUP, pcmk__str_casei)) {
85 return pcmk__rsc_variant_group;
86
87 } else if (pcmk__str_eq(name, PCMK_XE_CLONE, pcmk__str_casei)) {
88 return pcmk__rsc_variant_clone;
89
90 } else if (pcmk__str_eq(name, PCMK_XE_BUNDLE, pcmk__str_casei)) {
91 return pcmk__rsc_variant_bundle;
92 }
93
94 return pcmk__rsc_variant_unknown;
95 }
96
97 /*!
98 * \internal
99 * \brief Insert a meta-attribute if not already present
100 *
101 * \param[in] key Meta-attribute name
102 * \param[in] value Meta-attribute value to add if not already present
103 * \param[in,out] table Meta-attribute hash table to insert into
104 *
105 * \note This is like pcmk__insert_meta() except it won't overwrite existing
106 * values.
107 */
108 static void
109 dup_attr(gpointer key, gpointer value, gpointer user_data)
110 {
111 GHashTable *table = user_data;
112
113 CRM_CHECK((key != NULL) && (table != NULL), return);
114
115 if (value == NULL) {
116 return;
117 }
118
119 if (pcmk__str_eq((const char *) value, "#default", pcmk__str_casei)) {
120 // @COMPAT Deprecated since 2.1.8
121 pcmk__config_warn("Support for setting meta-attributes (such as %s) to "
122 "the explicit value '#default' is deprecated and "
123 "will be removed in a future release",
124 (const char *) key);
125 return;
126 }
127
128 if (!g_hash_table_contains(table, key)) {
129 pcmk__insert_dup(table, (const char *) key, (const char *) value);
130 }
131 }
132
133 static void
134 expand_parents_fixed_nvpairs(const pcmk_resource_t *rsc,
135 const pcmk_rule_input_t *rule_input,
136 GHashTable *meta_hash, pcmk_scheduler_t *scheduler)
137 {
138 GHashTable *parent_orig_meta = pcmk__strkey_table(free, free);
139
140 /* Search all parent resources, get the fixed value of
141 * PCMK_XE_META_ATTRIBUTES set only in the original xml, and stack it in the
142 * hash table. The fixed value of the lower parent resource takes precedence
143 * and is not overwritten.
144 */
145 for (const pcmk_resource_t *parent = rsc->priv->parent; parent != NULL;
146 parent = parent->priv->parent) {
147
148 /* A hash table for comparison is generated, including the id-ref. */
149 pe__unpack_dataset_nvpairs(parent->priv->xml, PCMK_XE_META_ATTRIBUTES,
150 rule_input, parent_orig_meta, NULL,
151 scheduler);
152 }
153
154 // This will not overwrite any values already existing for child
155 g_hash_table_foreach(parent_orig_meta, dup_attr, meta_hash);
156 g_hash_table_destroy(parent_orig_meta);
157 }
158
159 /*
160 * \brief Get fully evaluated resource meta-attributes
161 *
162 * \param[in,out] meta_hash Where to store evaluated meta-attributes
163 * \param[in] rsc Resource to get meta-attributes for
164 * \param[in] node Ignored
165 * \param[in,out] scheduler Scheduler data
166 */
167 void
168 get_meta_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc,
169 pcmk_node_t *node, pcmk_scheduler_t *scheduler)
170 {
171 pcmk_rule_input_t rule_input = { NULL, };
172
173 CRM_CHECK((meta_hash != NULL) && (rsc != NULL) && (scheduler != NULL),
174 return);
175
176 for (const xmlAttr *attr = pcmk__xe_first_attr(rsc->priv->xml);
177 attr != NULL; attr = attr->next) {
178
179 if (attr->children == NULL) {
180 continue;
181 }
182
183 dup_attr((void *) attr->name, (void *) attr->children->content,
184 meta_hash);
185 }
186
187 rule_input.now = scheduler->priv->now;
188 rule_input.rsc_standard = pcmk__xe_get(rsc->priv->xml, PCMK_XA_CLASS);
189 rule_input.rsc_provider = pcmk__xe_get(rsc->priv->xml, PCMK_XA_PROVIDER);
190 rule_input.rsc_agent = pcmk__xe_get(rsc->priv->xml, PCMK_XA_TYPE);
191
192 pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_META_ATTRIBUTES,
193 &rule_input, meta_hash, NULL, scheduler);
194
195 /* Set the PCMK_XE_META_ATTRIBUTES explicitly set in the parent resource to
196 * the hash table of the child resource. If it is already explicitly set as
197 * a child, it will not be overwritten.
198 */
199 if (rsc->priv->parent != NULL) {
200 expand_parents_fixed_nvpairs(rsc, &rule_input, meta_hash, scheduler);
201 }
202
203 /* check the defaults */
204 pe__unpack_dataset_nvpairs(scheduler->priv->rsc_defaults,
205 PCMK_XE_META_ATTRIBUTES, &rule_input, meta_hash,
206 NULL, scheduler);
207
208 /* If there is PCMK_XE_META_ATTRIBUTES that the parent resource has not
209 * explicitly set, set a value that is not set from PCMK_XE_RSC_DEFAULTS
210 * either. The values already set up to this point will not be overwritten.
211 */
212 if (rsc->priv->parent != NULL) {
213 g_hash_table_foreach(rsc->priv->parent->priv->meta, dup_attr,
214 meta_hash);
215 }
216 }
217
218 /*!
219 * \brief Get final values of a resource's instance attributes
220 *
221 * \param[in,out] instance_attrs Where to store the instance attributes
222 * \param[in] rsc Resource to get instance attributes for
223 * \param[in] node If not NULL, evaluate rules for this node
224 * \param[in,out] scheduler Scheduler data
225 */
226 void
227 get_rsc_attributes(GHashTable *instance_attrs, const pcmk_resource_t *rsc,
228 const pcmk_node_t *node, pcmk_scheduler_t *scheduler)
229 {
230 pcmk_rule_input_t rule_input = {
231 .now = NULL,
232 };
233
234 CRM_CHECK((instance_attrs != NULL) && (rsc != NULL) && (scheduler != NULL),
235 return);
236
237 rule_input.now = scheduler->priv->now;
238 if (node != NULL) {
239 rule_input.node_attrs = node->priv->attrs;
240 }
241
242 // Evaluate resource's own values, then its ancestors' values
243 pe__unpack_dataset_nvpairs(rsc->priv->xml, PCMK_XE_INSTANCE_ATTRIBUTES,
244 &rule_input, instance_attrs, NULL, scheduler);
245 if (rsc->priv->parent != NULL) {
246 get_rsc_attributes(instance_attrs, rsc->priv->parent, node, scheduler);
247 }
248 }
249
250 static char *
251 template_op_key(const xmlNode *op)
252 {
253 const char *name = pcmk__xe_get(op, PCMK_XA_NAME);
254 const char *role = pcmk__xe_get(op, PCMK_XA_ROLE);
255 char *key = NULL;
256
257 if ((role == NULL)
258 || pcmk__strcase_any_of(role, PCMK_ROLE_STARTED, PCMK_ROLE_UNPROMOTED,
259 PCMK__ROLE_UNPROMOTED_LEGACY, NULL)) {
260 role = PCMK__ROLE_UNKNOWN;
261 }
262
263 key = pcmk__assert_asprintf("%s-%s", name, role);
264 return key;
265 }
266
267 static int
268 unpack_template(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
269 {
270 xmlNode *cib_resources = NULL;
271 xmlNode *template = NULL;
272 xmlNode *rsc_ops = NULL;
273 xmlNode *template_ops = NULL;
274 GHashTable *rsc_ops_hash = NULL;
275 const char *template_ref = NULL;
276 const char *id = NULL;
277 int rc = pcmk_rc_ok;
278
279 template_ref = pcmk__xe_get(rsc->priv->orig_xml, PCMK_XA_TEMPLATE);
280 if (template_ref == NULL) {
281 goto done;
282 }
283
284 id = pcmk__xe_id(rsc->priv->orig_xml);
285
286 if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
287 pcmk__config_err("The resource object '%s' should not reference itself",
288 id);
289 rc = pcmk_rc_unpack_error;
290 goto done;
291 }
292
293 cib_resources = pcmk_find_cib_element(scheduler->input, PCMK_XE_RESOURCES);
294 if (cib_resources == NULL) {
295 pcmk__config_err("No " PCMK_XE_RESOURCES " section");
296 rc = pcmk_rc_unpack_error;
297 goto done;
298 }
299
300 template = pcmk__xe_first_child(cib_resources, PCMK_XE_TEMPLATE,
301 PCMK_XA_ID, template_ref);
302 if (template == NULL) {
303 pcmk__config_err("No template named '%s'", template_ref);
304 rc = pcmk_rc_unpack_error;
305 goto done;
306 }
307
308 rsc->priv->xml = pcmk__xml_copy(NULL, template);
309 xmlNodeSetName(rsc->priv->xml, rsc->priv->orig_xml->name);
310 pcmk__xe_set(rsc->priv->xml, PCMK_XA_ID, id);
311 pcmk__xe_set(rsc->priv->xml, PCMK__META_CLONE,
312 pcmk__xe_get(rsc->priv->orig_xml, PCMK__META_CLONE));
313
314 template_ops = pcmk__xe_first_child(rsc->priv->xml, PCMK_XE_OPERATIONS,
315 NULL, NULL);
316
317 for (xmlNode *child_xml = pcmk__xe_first_child(rsc->priv->orig_xml, NULL,
318 NULL, NULL);
319 child_xml != NULL; child_xml = pcmk__xe_next(child_xml, NULL)) {
320
321 xmlNode *new_child = pcmk__xml_copy(rsc->priv->xml, child_xml);
322
323 if ((rsc_ops == NULL) && pcmk__xe_is(new_child, PCMK_XE_OPERATIONS)) {
324 /* Multiple PCMK_XE_OPERATIONS children are not possible with schema
325 * validation enabled. However, in pe__unpack_resource(), we use
326 * only the first in case of multiple. Do the same here.
327 */
328 rsc_ops = new_child;
329 }
330 }
331
332 if ((template_ops == NULL) || (rsc_ops == NULL)) {
333 goto done;
334 }
335
336 // Operations configured in the resource override those in the template
337 rsc_ops_hash = pcmk__strkey_table(free, NULL);
338
339 for (const xmlNode *op = pcmk__xe_first_child(rsc_ops, NULL, NULL, NULL);
340 op != NULL; op = pcmk__xe_next(op, NULL)) {
341
342 g_hash_table_insert(rsc_ops_hash, template_op_key(op), (void *) op);
343 }
344
345 for (xmlNode *op = pcmk__xe_first_child(template_ops, NULL, NULL, NULL);
346 op != NULL; op = pcmk__xe_next(op, NULL)) {
347
348 char *key = template_op_key(op);
349
350 if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) {
351 pcmk__xml_copy(rsc_ops, op);
352 }
353
354 free(key);
355 }
356
357 // template_ops has been replaced by rsc_ops
358 pcmk__xml_free(template_ops);
359
360 done:
361 if (rc == pcmk_rc_ok) {
362 pcmk__log_xml_trace(rsc->priv->xml, "[expanded XML]");
363 }
364
365 g_clear_pointer(&rsc_ops_hash, g_hash_table_destroy);
366 return rc;
367 }
368
369 static bool
370 add_template_rsc(const xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
371 {
372 const char *template_ref = NULL;
373 const char *id = NULL;
374
375 if (xml_obj == NULL) {
376 pcmk__config_err("No resource object for processing resource list "
377 "of template");
378 return false;
379 }
380
381 template_ref = pcmk__xe_get(xml_obj, PCMK_XA_TEMPLATE);
382 if (template_ref == NULL) {
383 return true;
384 }
385
386 id = pcmk__xe_id(xml_obj);
387 if (id == NULL) {
388 pcmk__config_err("'%s' object must have a id", xml_obj->name);
389 return false;
390 }
391
392 if (pcmk__str_eq(template_ref, id, pcmk__str_none)) {
393 pcmk__config_err("The resource object '%s' should not reference itself",
394 id);
395 return false;
396 }
397
398 pcmk__add_idref(scheduler->priv->templates, template_ref, id);
399 return true;
400 }
401
402 /*!
403 * \internal
404 * \brief Check whether a clone or instance being unpacked is globally unique
405 *
406 * \param[in] rsc Clone or clone instance to check
407 *
408 * \return \c true if \p rsc is globally unique according to its
409 * meta-attributes, otherwise \c false
410 */
411 static bool
412 detect_unique(const pcmk_resource_t *rsc)
413 {
414 const char *value = g_hash_table_lookup(rsc->priv->meta,
415 PCMK_META_GLOBALLY_UNIQUE);
416
417 if (value == NULL) { // Default to true if clone-node-max > 1
418 value = g_hash_table_lookup(rsc->priv->meta,
419 PCMK_META_CLONE_NODE_MAX);
420 if (value != NULL) {
421 int node_max = 1;
422
423 if ((pcmk__scan_min_int(value, &node_max, 0) == pcmk_rc_ok)
424 && (node_max > 1)) {
425 return true;
426 }
427 }
428 return false;
429 }
430 return pcmk__is_true(value);
431 }
432
433 /*!
434 * \brief Get a table of resource parameters
435 *
436 * \param[in,out] rsc Resource to query
437 * \param[in] node Node for evaluating rules (NULL for defaults)
438 * \param[in,out] scheduler Scheduler data
439 *
440 * \return Hash table containing resource parameter names and values
441 * (or NULL if \p rsc or \p scheduler is NULL)
442 * \note The returned table will be destroyed when the resource is freed, so
443 * callers should not destroy it.
444 */
445 GHashTable *
446 pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node,
447 pcmk_scheduler_t *scheduler)
448 {
449 GHashTable *params_on_node = NULL;
450
451 /* A NULL node is used to request the resource's default parameters
452 * (not evaluated for node), but we always want something non-NULL
453 * as a hash table key.
454 */
455 const char *node_name = "";
456
457 // Sanity check
458 if ((rsc == NULL) || (scheduler == NULL)) {
459 return NULL;
460 }
461 if ((node != NULL) && (node->priv->name != NULL)) {
462 node_name = node->priv->name;
463 }
464
465 // Find the parameter table for given node
466 if (rsc->priv->parameter_cache == NULL) {
467 rsc->priv->parameter_cache =
468 pcmk__strikey_table(free, (GDestroyNotify) g_hash_table_destroy);
469
470 } else {
471 params_on_node = g_hash_table_lookup(rsc->priv->parameter_cache,
472 node_name);
473 }
474
475 // If none exists yet, create one with parameters evaluated for node
476 if (params_on_node == NULL) {
477 params_on_node = pcmk__strkey_table(free, free);
478 get_rsc_attributes(params_on_node, rsc, node, scheduler);
479 g_hash_table_insert(rsc->priv->parameter_cache, strdup(node_name),
480 params_on_node);
481 }
482 return params_on_node;
483 }
484
485 /*!
486 * \internal
487 * \brief Unpack a resource's \c PCMK_META_REQUIRES meta-attribute
488 *
489 * \param[in,out] rsc Resource being unpacked
490 * \param[in] value Value of \c PCMK_META_REQUIRES meta-attribute
491 * \param[in] is_default Whether \p value was selected by default
492 */
493 static void
494 unpack_requires(pcmk_resource_t *rsc, const char *value, bool is_default)
495 {
496 const pcmk_scheduler_t *scheduler = rsc->priv->scheduler;
497
498 if (pcmk__str_eq(value, PCMK_VALUE_NOTHING, pcmk__str_casei)) {
499
500 } else if (pcmk__str_eq(value, PCMK_VALUE_QUORUM, pcmk__str_casei)) {
501 pcmk__set_rsc_flags(rsc, pcmk__rsc_needs_quorum);
502
503 } else if (pcmk__str_eq(value, PCMK_VALUE_FENCING, pcmk__str_casei)) {
504 pcmk__set_rsc_flags(rsc, pcmk__rsc_needs_fencing);
505 if (!pcmk__is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
506 pcmk__config_warn("%s requires fencing but fencing is disabled",
507 rsc->id);
508 }
509
510 } else if (pcmk__str_eq(value, PCMK_VALUE_UNFENCING, pcmk__str_casei)) {
511 if (pcmk__is_set(rsc->flags, pcmk__rsc_fence_device)) {
512 pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
513 "to \"" PCMK_VALUE_QUORUM "\" because fencing "
514 "devices cannot require unfencing", rsc->id);
515 unpack_requires(rsc, PCMK_VALUE_QUORUM, true);
516 return;
517 }
518
519 if (!pcmk__is_set(scheduler->flags, pcmk__sched_fencing_enabled)) {
520 pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s "
521 "to \"" PCMK_VALUE_QUORUM "\" because fencing is "
522 "disabled", rsc->id);
523 unpack_requires(rsc, PCMK_VALUE_QUORUM, true);
524 return;
525 }
526
527 pcmk__set_rsc_flags(rsc,
528 pcmk__rsc_needs_fencing|pcmk__rsc_needs_unfencing);
529
530 } else {
531 const char *orig_value = value;
532
533 if (pcmk__is_set(rsc->flags, pcmk__rsc_fence_device)) {
534 value = PCMK_VALUE_QUORUM;
535
536 } else if (pcmk__is_primitive(rsc)
537 && xml_contains_remote_node(rsc->priv->xml)) {
538 value = PCMK_VALUE_QUORUM;
539
540 } else if (pcmk__is_set(scheduler->flags,
541 pcmk__sched_enable_unfencing)) {
542 value = PCMK_VALUE_UNFENCING;
543
544 } else if (pcmk__is_set(scheduler->flags,
545 pcmk__sched_fencing_enabled)) {
546 value = PCMK_VALUE_FENCING;
547
548 } else if (scheduler->no_quorum_policy == pcmk_no_quorum_ignore) {
549 value = PCMK_VALUE_NOTHING;
550
551 } else {
552 value = PCMK_VALUE_QUORUM;
553 }
554
555 if (orig_value != NULL) {
556 pcmk__config_err("Resetting '" PCMK_META_REQUIRES "' for %s "
557 "to '%s' because '%s' is not valid",
558 rsc->id, value, orig_value);
559 }
560 unpack_requires(rsc, value, true);
561 return;
562 }
563
564 pcmk__rsc_trace(rsc, "\tRequired to start: %s%s", value,
565 (is_default? " (default)" : ""));
566 }
567
568 /*!
569 * \internal
570 * \brief Parse resource priority from meta-attribute
571 *
572 * \param[in,out] rsc Resource being unpacked
573 */
574 static void
575 unpack_priority(pcmk_resource_t *rsc)
576 {
577 const char *value = g_hash_table_lookup(rsc->priv->meta,
578 PCMK_META_PRIORITY);
579 int rc = pcmk_parse_score(value, &(rsc->priv->priority), 0);
580
581 if (rc != pcmk_rc_ok) {
582 pcmk__config_warn("Using default (0) for resource %s "
583 PCMK_META_PRIORITY
584 " because '%s' is not a valid value: %s",
585 rsc->id, value, pcmk_rc_str(rc));
586 }
587 }
588
589 /*!
590 * \internal
591 * \brief Parse resource stickiness from meta-attribute
592 *
593 * \param[in,out] rsc Resource being unpacked
594 */
595 static void
596 unpack_stickiness(pcmk_resource_t *rsc)
597 {
598 const char *value = g_hash_table_lookup(rsc->priv->meta,
599 PCMK_META_RESOURCE_STICKINESS);
600
601 if (pcmk__str_eq(value, PCMK_VALUE_DEFAULT, pcmk__str_casei)) {
602 // @COMPAT Deprecated since 2.1.8
603 pcmk__config_warn("Support for setting "
604 PCMK_META_RESOURCE_STICKINESS
605 " to the explicit value '" PCMK_VALUE_DEFAULT
606 "' is deprecated and will be removed in a "
607 "future release (just leave it unset)");
608 } else {
609 int rc = pcmk_parse_score(value, &(rsc->priv->stickiness), 0);
610
611 if (rc != pcmk_rc_ok) {
612 pcmk__config_warn("Using default (0) for resource %s "
613 PCMK_META_RESOURCE_STICKINESS
614 " because '%s' is not a valid value: %s",
615 rsc->id, value, pcmk_rc_str(rc));
616 }
617 }
618 }
619
620 /*!
621 * \internal
622 * \brief Parse resource migration threshold from meta-attribute
623 *
624 * \param[in,out] rsc Resource being unpacked
625 */
626 static void
627 unpack_migration_threshold(pcmk_resource_t *rsc)
628 {
629 const char *value = g_hash_table_lookup(rsc->priv->meta,
630 PCMK_META_MIGRATION_THRESHOLD);
631
632 if (pcmk__str_eq(value, PCMK_VALUE_DEFAULT, pcmk__str_casei)) {
633 // @COMPAT Deprecated since 2.1.8
634 pcmk__config_warn("Support for setting "
635 PCMK_META_MIGRATION_THRESHOLD
636 " to the explicit value '" PCMK_VALUE_DEFAULT
637 "' is deprecated and will be removed in a "
638 "future release (just leave it unset)");
639 rsc->priv->ban_after_failures = PCMK_SCORE_INFINITY;
640 } else {
641 int rc = pcmk_parse_score(value, &(rsc->priv->ban_after_failures),
642 PCMK_SCORE_INFINITY);
643
644 if ((rc != pcmk_rc_ok) || (rsc->priv->ban_after_failures < 0)) {
645 pcmk__config_warn("Using default (" PCMK_VALUE_INFINITY
646 ") for resource %s meta-attribute "
647 PCMK_META_MIGRATION_THRESHOLD
648 " because '%s' is not a valid value: %s",
649 rsc->id, value, pcmk_rc_str(rc));
650 rsc->priv->ban_after_failures = PCMK_SCORE_INFINITY;
651 }
652 }
653 }
654
655 /*!
656 * \internal
657 * \brief Unpack configuration XML for a given resource
658 *
659 * Unpack the XML object containing a resource's configuration into a new
660 * \c pcmk_resource_t object.
661 *
662 * \param[in] xml_obj XML node containing the resource's configuration
663 * \param[out] rsc Where to store the unpacked resource information
664 * \param[in] parent Resource's parent, if any
665 * \param[in,out] scheduler Scheduler data
666 *
667 * \return Standard Pacemaker return code
668 * \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and
669 * the caller is responsible for freeing it using its variant-specific
670 * free() method. Otherwise, \p *rsc is guaranteed to be NULL.
671 */
672 int
673 pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc,
674 pcmk_resource_t *parent, pcmk_scheduler_t *scheduler)
675 {
676 int rc = pcmk_rc_ok;
677 xmlNode *ops = NULL;
678 const char *value = NULL;
679 const char *id = NULL;
680 bool guest_node = false;
681 bool remote_node = false;
682 pcmk__resource_private_t *rsc_private = NULL;
683
684 pcmk_rule_input_t rule_input = {
685 .now = NULL,
686 };
687
688 pcmk__assert((xml_obj != NULL) && (rsc != NULL) && (scheduler != NULL));
689
690 rule_input.now = scheduler->priv->now;
691
692 pcmk__log_xml_trace(xml_obj, "[raw XML]");
693
694 id = pcmk__xe_get(xml_obj, PCMK_XA_ID);
695 if (id == NULL) {
696 pcmk__config_err("Ignoring <%s> configuration without " PCMK_XA_ID,
697 xml_obj->name);
698 rc = pcmk_rc_unpack_error;
699 goto done;
700 }
701
702 *rsc = pcmk__assert_alloc(1, sizeof(pcmk_resource_t));
703 (*rsc)->priv = pcmk__assert_alloc(1, sizeof(pcmk__resource_private_t));
704
705 rsc_private = (*rsc)->priv;
706 rsc_private->scheduler = scheduler;
707 rsc_private->orig_xml = pcmk__xml_copy(NULL, xml_obj);
708 rsc_private->xml = rsc_private->orig_xml;
709
710 rc = unpack_template(*rsc, scheduler);
711 if (rc != pcmk_rc_ok) {
712 goto done;
713 }
714
715 /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */
716
717 rsc_private->parent = parent;
718
719 ops = pcmk__xe_first_child(rsc_private->xml, PCMK_XE_OPERATIONS, NULL,
720 NULL);
721 rsc_private->ops_xml = pcmk__xe_resolve_idref(ops, scheduler->input->doc);
722
723 rsc_private->variant = get_resource_type((const char *)
724 rsc_private->xml->name);
725 if (rsc_private->variant == pcmk__rsc_variant_unknown) {
726 pcmk__config_err("Ignoring resource '%s' of unknown type '%s'",
727 id, rsc_private->xml->name);
728 rc = pcmk_rc_unpack_error;
729 goto done;
730 }
731
732 rsc_private->meta = pcmk__strkey_table(free, free);
733 rsc_private->utilization = pcmk__strkey_table(free, free);
734 rsc_private->probed_nodes = pcmk__strkey_table(NULL, pcmk__free_node_copy);
735 rsc_private->allowed_nodes = pcmk__strkey_table(NULL, pcmk__free_node_copy);
736
737 value = pcmk__xe_get(rsc_private->xml, PCMK__META_CLONE);
738 if (value != NULL) {
739 (*rsc)->id = pcmk__assert_asprintf("%s:%s", id, value);
740 pcmk__insert_meta(rsc_private, PCMK__META_CLONE, value);
741
742 } else {
743 (*rsc)->id = pcmk__str_copy(id);
744 }
745
746 rsc_private->fns = &resource_class_functions[rsc_private->variant];
747
748 get_meta_attributes(rsc_private->meta, *rsc, NULL, scheduler);
749
750 (*rsc)->flags = 0;
751 pcmk__set_rsc_flags(*rsc, pcmk__rsc_unassigned);
752
753 if (!pcmk__is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
754 pcmk__set_rsc_flags(*rsc, pcmk__rsc_managed);
755 }
756
757 rsc_private->orig_role = pcmk_role_stopped;
758 rsc_private->next_role = pcmk_role_unknown;
759
760 unpack_priority(*rsc);
761
762 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_CRITICAL);
763 if ((value == NULL) || pcmk__is_true(value)) {
764 pcmk__set_rsc_flags(*rsc, pcmk__rsc_critical);
765 }
766
767 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_NOTIFY);
768 if (pcmk__is_true(value)) {
769 pcmk__set_rsc_flags(*rsc, pcmk__rsc_notify);
770 }
771
772 if (xml_contains_remote_node(rsc_private->xml)) {
773 pcmk__set_rsc_flags(*rsc, pcmk__rsc_is_remote_connection);
774 if (g_hash_table_lookup(rsc_private->meta, PCMK__META_CONTAINER)) {
775 guest_node = true;
776 } else {
777 remote_node = true;
778 }
779 }
780
781 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_ALLOW_MIGRATE);
782 if (pcmk__is_true(value)) {
783 pcmk__set_rsc_flags(*rsc, pcmk__rsc_migratable);
784 } else if ((value == NULL) && remote_node) {
785 /* By default, we want remote nodes to be able
786 * to float around the cluster without having to stop all the
787 * resources within the remote-node before moving. Allowing
788 * migration support enables this feature. If this ever causes
789 * problems, migration support can be explicitly turned off with
790 * PCMK_META_ALLOW_MIGRATE=false.
791 */
792 pcmk__set_rsc_flags(*rsc, pcmk__rsc_migratable);
793 }
794
795 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_IS_MANAGED);
796 if (value != NULL) {
797 if (pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) {
798 // @COMPAT Deprecated since 2.1.8
799 pcmk__config_warn("Support for setting " PCMK_META_IS_MANAGED
800 " to the explicit value '" PCMK_VALUE_DEFAULT
801 "' is deprecated and will be removed in a "
802 "future release (just leave it unset)");
803 } else if (pcmk__is_true(value)) {
804 pcmk__set_rsc_flags(*rsc, pcmk__rsc_managed);
805 } else {
806 pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
807 }
808 }
809
810 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_MAINTENANCE);
811 if (pcmk__is_true(value)) {
812 pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
813 pcmk__set_rsc_flags(*rsc, pcmk__rsc_maintenance);
814 }
815 if (pcmk__is_set(scheduler->flags, pcmk__sched_in_maintenance)) {
816 pcmk__clear_rsc_flags(*rsc, pcmk__rsc_managed);
817 pcmk__set_rsc_flags(*rsc, pcmk__rsc_maintenance);
818 }
819
820 if (pcmk__is_clone(pe__const_top_resource(*rsc, false))) {
821 if (detect_unique(*rsc)) {
822 pcmk__set_rsc_flags(*rsc, pcmk__rsc_unique);
823 }
824 if (pcmk__is_true(g_hash_table_lookup((*rsc)->priv->meta,
825 PCMK_META_PROMOTABLE))) {
826 pcmk__set_rsc_flags(*rsc, pcmk__rsc_promotable);
827 }
828 } else {
829 pcmk__set_rsc_flags(*rsc, pcmk__rsc_unique);
830 }
831
832 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_MULTIPLE_ACTIVE);
833 if (pcmk__str_eq(value, PCMK_VALUE_STOP_ONLY, pcmk__str_casei)) {
834 rsc_private->multiply_active_policy = pcmk__multiply_active_stop;
835 pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop only",
836 (*rsc)->id);
837
838 } else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) {
839 rsc_private->multiply_active_policy = pcmk__multiply_active_block;
840 pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block",
841 (*rsc)->id);
842
843 } else if (pcmk__str_eq(value, PCMK_VALUE_STOP_UNEXPECTED,
844 pcmk__str_casei)) {
845 rsc_private->multiply_active_policy = pcmk__multiply_active_unexpected;
846 pcmk__rsc_trace(*rsc,
847 "%s multiple running resource recovery: "
848 "stop unexpected instances",
849 (*rsc)->id);
850
851 } else { // PCMK_VALUE_STOP_START
852 if (!pcmk__str_eq(value, PCMK_VALUE_STOP_START,
853 pcmk__str_casei|pcmk__str_null_matches)) {
854 pcmk__config_warn("%s is not a valid value for "
855 PCMK_META_MULTIPLE_ACTIVE
856 ", using default of "
857 "\"" PCMK_VALUE_STOP_START "\"",
858 value);
859 }
860 rsc_private->multiply_active_policy = pcmk__multiply_active_restart;
861 pcmk__rsc_trace(*rsc,
862 "%s multiple running resource recovery: stop/start",
863 (*rsc)->id);
864 }
865
866 unpack_stickiness(*rsc);
867 unpack_migration_threshold(*rsc);
868
869 if (pcmk__str_eq(pcmk__xe_get(rsc_private->xml, PCMK_XA_CLASS),
870 PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) {
871 pcmk__set_scheduler_flags(scheduler, pcmk__sched_have_fencing);
872 pcmk__set_rsc_flags(*rsc, pcmk__rsc_fence_device);
873 }
874
875 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_REQUIRES);
876 unpack_requires(*rsc, value, false);
877
878 value = g_hash_table_lookup(rsc_private->meta, PCMK_META_FAILURE_TIMEOUT);
879 if (value != NULL) {
880 pcmk_parse_interval_spec(value, &(rsc_private->failure_expiration_ms));
881 }
882
883 if (remote_node) {
884 GHashTable *params = pe_rsc_params(*rsc, NULL, scheduler);
885
886 /* Grabbing the value now means that any rules based on node attributes
887 * will evaluate to false, so such rules should not be used with
888 * PCMK_REMOTE_RA_RECONNECT_INTERVAL.
889 *
890 * @TODO Evaluate per node before using
891 */
892 value = g_hash_table_lookup(params, PCMK_REMOTE_RA_RECONNECT_INTERVAL);
893 if (value) {
894 /* reconnect delay works by setting failure_timeout and preventing the
895 * connection from starting until the failure is cleared. */
896 pcmk_parse_interval_spec(value,
897 &(rsc_private->remote_reconnect_ms));
898
899 /* We want to override any default failure_timeout in use when remote
900 * PCMK_REMOTE_RA_RECONNECT_INTERVAL is in use.
901 */
902 rsc_private->failure_expiration_ms =
903 rsc_private->remote_reconnect_ms;
904 }
905 }
906
907 get_target_role(*rsc, &(rsc_private->next_role));
908 pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id,
909 (rsc_private->next_role == pcmk_role_unknown)?
910 "default" : pcmk_role_text(rsc_private->next_role));
911
912 if (!rsc_private->fns->unpack(*rsc)) {
913 rc = pcmk_rc_unpack_error;
914 goto done;
915 }
916
917 if (pcmk__is_set(scheduler->flags, pcmk__sched_symmetric_cluster)) {
918 // This tag must stay exactly the same because it is tested elsewhere
919 resource_location(*rsc, NULL, 0, "symmetric_default", scheduler);
920 } else if (guest_node) {
921 /* remote resources tied to a container resource must always be allowed
922 * to opt-in to the cluster. Whether the connection resource is actually
923 * allowed to be placed on a node is dependent on the container resource */
924 resource_location(*rsc, NULL, 0, "remote_connection_default",
925 scheduler);
926 }
927
928 if (pcmk__is_set((*rsc)->flags, pcmk__rsc_notify)) {
929 pcmk__rsc_trace(*rsc, "%s action notification: required", (*rsc)->id);
930 } else {
931 pcmk__rsc_trace(*rsc, "%s action notification: not required",
932 (*rsc)->id);
933 }
934
935 pe__unpack_dataset_nvpairs(rsc_private->xml, PCMK_XE_UTILIZATION,
936 &rule_input, rsc_private->utilization, NULL,
937 scheduler);
938
939 // First condition means resource was expanded from a template
940 if ((rsc_private->xml != rsc_private->orig_xml)
941 && !add_template_rsc(rsc_private->orig_xml, scheduler)) {
942
943 rc = pcmk_rc_unpack_error;
944 }
945
946 done:
947 if (rc != pcmk_rc_ok) {
948 g_clear_pointer(rsc, pcmk__free_resource);
949 }
950 return rc;
951 }
952
953 gboolean
954 is_parent(pcmk_resource_t *child, pcmk_resource_t *rsc)
955 {
956 pcmk_resource_t *parent = child;
957
958 if (parent == NULL || rsc == NULL) {
959 return FALSE;
960 }
961 while (parent->priv->parent != NULL) {
962 if (parent->priv->parent == rsc) {
963 return TRUE;
964 }
965 parent = parent->priv->parent;
966 }
967 return FALSE;
968 }
969
970 pcmk_resource_t *
971 uber_parent(pcmk_resource_t *rsc)
972 {
973 pcmk_resource_t *parent = rsc;
974
975 if (parent == NULL) {
976 return NULL;
977 }
978 while ((parent->priv->parent != NULL)
979 && !pcmk__is_bundle(parent->priv->parent)) {
980 parent = parent->priv->parent;
981 }
982 return parent;
983 }
984
985 /*!
986 * \internal
987 * \brief Get the topmost parent of a resource as a const pointer
988 *
989 * \param[in] rsc Resource to check
990 * \param[in] include_bundle If true, go all the way to bundle
991 *
992 * \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent,
993 * the bundle if \p rsc is bundled and \p include_bundle is true,
994 * otherwise the topmost parent of \p rsc up to a clone
995 */
996 const pcmk_resource_t *
997 pe__const_top_resource(const pcmk_resource_t *rsc, bool include_bundle)
998 {
999 const pcmk_resource_t *parent = rsc;
1000
1001 if (parent == NULL) {
1002 return NULL;
1003 }
1004 while (parent->priv->parent != NULL) {
1005 if (!include_bundle && pcmk__is_bundle(parent->priv->parent)) {
1006 break;
1007 }
1008 parent = parent->priv->parent;
1009 }
1010 return parent;
1011 }
1012
1013 void
1014 common_free(pcmk_resource_t * rsc)
1015 {
|
(1) Event path: |
Condition "rsc == NULL", taking false branch. |
1016 if (rsc == NULL) {
1017 return;
1018 }
1019
|
(2) Event path: |
Switch case default. |
|
(3) Event path: |
Condition "trace_tag_cs == NULL", taking true branch. |
|
(4) Event path: |
Condition "crm_is_callsite_active(trace_tag_cs, _level, converted_tag)", taking false branch. |
1020 pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
1021
|
(5) Event path: |
Condition "_p", taking true branch. |
1022 g_clear_pointer(&rsc->priv->parameter_cache, g_hash_table_destroy);
1023
1024 free(rsc->id);
1025
1026 free(rsc->priv->variant_opaque);
1027 free(rsc->priv->history_id);
1028 free(rsc->priv->pending_action);
1029 pcmk__free_node_copy(rsc->priv->assigned_node);
1030
|
(6) Event path: |
Condition "rsc->priv->orig_xml != rsc->priv->xml", taking true branch. |
1031 if (rsc->priv->orig_xml != rsc->priv->xml) {
1032 pcmk__xml_free(rsc->priv->orig_xml);
1033 }
1034 pcmk__xml_free(rsc->priv->xml);
1035
1036 g_list_free(rsc->priv->actions);
1037 g_list_free(rsc->priv->active_nodes);
1038 g_list_free(rsc->priv->launched);
1039 g_list_free(rsc->priv->dangling_migration_sources);
1040 g_list_free(rsc->priv->with_this_colocations);
1041 g_list_free(rsc->priv->this_with_colocations);
1042 g_list_free(rsc->priv->location_constraints);
1043 g_list_free_full(rsc->priv->ticket_constraints, free);
1044
|
(7) Event path: |
Condition "_p", taking true branch. |
1045 g_clear_pointer(&rsc->priv->meta, g_hash_table_destroy);
|
CID (unavailable; MK=ec988721f11324ef1a87e9a02af888a9) (#3 of 5): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(8) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(9) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
1046 g_clear_pointer(&rsc->priv->utilization, g_hash_table_destroy);
1047 g_clear_pointer(&rsc->priv->probed_nodes, g_hash_table_destroy);
1048 g_clear_pointer(&rsc->priv->allowed_nodes, g_hash_table_destroy);
1049
1050 free(rsc->priv);
1051 free(rsc);
1052 }
1053
1054 /*!
1055 * \internal
1056 * \brief Count a node and update most preferred to it as appropriate
1057 *
1058 * \param[in] rsc An active resource
1059 * \param[in] node A node that \p rsc is active on
1060 * \param[in,out] active This will be set to \p node if \p node is more
1061 * preferred than the current value
1062 * \param[in,out] count_all If not NULL, this will be incremented
1063 * \param[in,out] count_clean If not NULL, this will be incremented if \p node
1064 * is online and clean
1065 *
1066 * \return true if the count should continue, or false if sufficiently known
1067 */
1068 bool
1069 pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node,
1070 pcmk_node_t **active, unsigned int *count_all,
1071 unsigned int *count_clean)
1072 {
1073 bool keep_looking = false;
1074 bool is_happy = false;
1075
1076 CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL),
1077 return false);
1078
1079 is_happy = node->details->online && !node->details->unclean;
1080
1081 if (count_all != NULL) {
1082 ++*count_all;
1083 }
1084 if ((count_clean != NULL) && is_happy) {
1085 ++*count_clean;
1086 }
1087 if ((count_all != NULL) || (count_clean != NULL)) {
1088 keep_looking = true; // We're counting, so go through entire list
1089 }
1090
1091 if (rsc->priv->partial_migration_source != NULL) {
1092 if (pcmk__same_node(node, rsc->priv->partial_migration_source)) {
1093 *active = node; // This is the migration source
1094 } else {
1095 keep_looking = true;
1096 }
1097 } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
1098 if (is_happy && ((*active == NULL) || !(*active)->details->online
1099 || (*active)->details->unclean)) {
1100 *active = node; // This is the first clean node
1101 } else {
1102 keep_looking = true;
1103 }
1104 }
1105 if (*active == NULL) {
1106 *active = node; // This is the first node checked
1107 }
1108 return keep_looking;
1109 }
1110
1111 // Shared implementation of pcmk__rsc_methods_t:active_node()
1112 static pcmk_node_t *
1113 active_node(const pcmk_resource_t *rsc, unsigned int *count_all,
1114 unsigned int *count_clean)
1115 {
1116 pcmk_node_t *active = NULL;
1117
1118 if (count_all != NULL) {
1119 *count_all = 0;
1120 }
1121 if (count_clean != NULL) {
1122 *count_clean = 0;
1123 }
1124 if (rsc == NULL) {
1125 return NULL;
1126 }
1127 for (GList *iter = rsc->priv->active_nodes;
1128 iter != NULL; iter = iter->next) {
1129
1130 if (!pe__count_active_node(rsc, (pcmk_node_t *) iter->data, &active,
1131 count_all, count_clean)) {
1132 break; // Don't waste time iterating if we don't have to
1133 }
1134 }
1135 return active;
1136 }
1137
1138 /*!
1139 * \brief
1140 * \internal Find and count active nodes according to \c PCMK_META_REQUIRES
1141 *
1142 * \param[in] rsc Resource to check
1143 * \param[out] count If not NULL, will be set to count of active nodes
1144 *
1145 * \return An active node (or NULL if resource is not active anywhere)
1146 *
1147 * \note This is a convenience wrapper for active_node() where the count of all
1148 * active nodes or only clean active nodes is desired according to the
1149 * \c PCMK_META_REQUIRES meta-attribute.
1150 */
1151 pcmk_node_t *
1152 pe__find_active_requires(const pcmk_resource_t *rsc, unsigned int *count)
1153 {
1154 if (rsc == NULL) {
1155 if (count != NULL) {
1156 *count = 0;
1157 }
1158 return NULL;
1159 }
1160
1161 if (pcmk__is_set(rsc->flags, pcmk__rsc_needs_fencing)) {
1162 return rsc->priv->fns->active_node(rsc, count, NULL);
1163 } else {
1164 return rsc->priv->fns->active_node(rsc, NULL, count);
1165 }
1166 }
1167
1168 void
1169 pe__count_common(pcmk_resource_t *rsc)
1170 {
1171 if (rsc->priv->children != NULL) {
1172 for (GList *item = rsc->priv->children;
1173 item != NULL; item = item->next) {
1174 pcmk_resource_t *child = item->data;
1175
1176 child->priv->fns->count(item->data);
1177 }
1178
1179 } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_removed)
1180 || (rsc->priv->orig_role > pcmk_role_stopped)) {
1181 rsc->priv->scheduler->priv->ninstances++;
1182 if (pe__resource_is_disabled(rsc)) {
1183 rsc->priv->scheduler->priv->disabled_resources++;
1184 }
1185 if (pcmk__is_set(rsc->flags, pcmk__rsc_blocked)) {
1186 rsc->priv->scheduler->priv->blocked_resources++;
1187 }
1188 }
1189 }
1190
1191 /*!
1192 * \internal
1193 * \brief Update a resource's next role
1194 *
1195 * \param[in,out] rsc Resource to be updated
1196 * \param[in] role Resource's new next role
1197 * \param[in] why Human-friendly reason why role is changing (for logs)
1198 */
1199 void
1200 pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role, const char *why)
1201 {
1202 pcmk__assert((rsc != NULL) && (why != NULL));
1203 if (rsc->priv->next_role != role) {
1204 pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)",
1205 rsc->id, pcmk_role_text(rsc->priv->next_role),
1206 pcmk_role_text(role), why);
1207 rsc->priv->next_role = role;
1208 }
1209 }
1210