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