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> // bool, true, false
13 #include <stdint.h>
14
15 #include <crm/pengine/status.h>
16 #include <crm/pengine/internal.h>
17 #include <pe_status_private.h>
18 #include <crm/common/xml.h>
19 #include <crm/common/output.h>
20
21 typedef struct {
22 int clone_max;
23 int clone_node_max;
24
25 int promoted_max;
26 int promoted_node_max;
27
28 int total_clones;
29
30 uint32_t flags; // Group of enum pcmk__clone_flags
31
32 notify_data_t *stop_notify;
33 notify_data_t *start_notify;
34 notify_data_t *demote_notify;
35 notify_data_t *promote_notify;
36
37 xmlNode *xml_obj_child;
38 } clone_variant_data_t;
39
40 #define get_clone_variant_data(data, rsc) do { \
41 pcmk__assert(pcmk__is_clone(rsc)); \
42 data = rsc->priv->variant_opaque; \
43 } while (0)
44
45 /*!
46 * \internal
47 * \brief Return the maximum number of clone instances allowed to be run
48 *
49 * \param[in] clone Clone or clone instance to check
50 *
51 * \return Maximum instances for \p clone
52 */
53 int
54 pe__clone_max(const pcmk_resource_t *clone)
55 {
56 const clone_variant_data_t *clone_data = NULL;
57
58 get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
59 return clone_data->clone_max;
60 }
61
62 /*!
63 * \internal
64 * \brief Return the maximum number of clone instances allowed per node
65 *
66 * \param[in] clone Promotable clone or clone instance to check
67 *
68 * \return Maximum allowed instances per node for \p clone
69 */
70 int
71 pe__clone_node_max(const pcmk_resource_t *clone)
72 {
73 const clone_variant_data_t *clone_data = NULL;
74
75 get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
76 return clone_data->clone_node_max;
77 }
78
79 /*!
80 * \internal
81 * \brief Return the maximum number of clone instances allowed to be promoted
82 *
83 * \param[in] clone Promotable clone or clone instance to check
84 *
85 * \return Maximum promoted instances for \p clone
86 */
87 int
88 pe__clone_promoted_max(const pcmk_resource_t *clone)
89 {
90 clone_variant_data_t *clone_data = NULL;
91
92 get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
93 return clone_data->promoted_max;
94 }
95
96 /*!
97 * \internal
98 * \brief Return the maximum number of clone instances allowed to be promoted
99 *
100 * \param[in] clone Promotable clone or clone instance to check
101 *
102 * \return Maximum promoted instances for \p clone
103 */
104 int
105 pe__clone_promoted_node_max(const pcmk_resource_t *clone)
106 {
107 clone_variant_data_t *clone_data = NULL;
108
109 get_clone_variant_data(clone_data, pe__const_top_resource(clone, false));
110 return clone_data->promoted_node_max;
111 }
112
113 static GList *
114 sorted_hash_table_values(GHashTable *table)
115 {
116 GList *retval = NULL;
117 GHashTableIter iter;
118 gpointer key, value;
119
120 g_hash_table_iter_init(&iter, table);
121 while (g_hash_table_iter_next(&iter, &key, &value)) {
122 if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) {
123 retval = g_list_prepend(retval, (char *) value);
124 }
125 }
126
127 retval = g_list_sort(retval, (GCompareFunc) strcmp);
128 return retval;
129 }
130
131 static GList *
132 nodes_with_status(GHashTable *table, const char *status)
133 {
134 GList *retval = NULL;
135 GHashTableIter iter;
136 gpointer key, value;
137
138 g_hash_table_iter_init(&iter, table);
139 while (g_hash_table_iter_next(&iter, &key, &value)) {
140 if (!strcmp((char *) value, status)) {
141 retval = g_list_prepend(retval, key);
142 }
143 }
144
145 retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp);
146 return retval;
147 }
148
149 static GString *
150 node_list_to_str(const GList *list)
151 {
152 GString *retval = NULL;
153
154 for (const GList *iter = list; iter != NULL; iter = iter->next) {
155 pcmk__add_word(&retval, 1024, (const char *) iter->data);
156 }
157
158 return retval;
159 }
160
161 static void
162 clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
163 clone_variant_data_t *clone_data, const char *desc)
164 {
165 GString *attrs = NULL;
166
167 if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
168 pcmk__add_separated_word(&attrs, 64, "promotable", ", ");
169 }
170
171 if (pcmk__is_set(rsc->flags, pcmk__rsc_unique)) {
172 pcmk__add_separated_word(&attrs, 64, "unique", ", ");
173 }
174
175 if (pe__resource_is_disabled(rsc)) {
176 pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
177 }
178
179 if (pcmk__is_set(rsc->flags, pcmk__rsc_maintenance)) {
180 pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
181
182 } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_managed)) {
183 pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
184 }
185
186 if (attrs != NULL) {
187 PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s",
188 rsc->id,
189 pcmk__xe_id(clone_data->xml_obj_child),
190 (const char *) attrs->str, desc ? " (" : "",
191 desc ? desc : "", desc ? ")" : "");
192 g_string_free(attrs, TRUE);
193 } else {
194 PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s",
195 rsc->id,
196 pcmk__xe_id(clone_data->xml_obj_child),
197 desc ? " (" : "", desc ? desc : "",
198 desc ? ")" : "");
199 }
200 }
201
202 void
203 pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid,
204 pcmk_scheduler_t *scheduler)
205 {
206 if (pcmk__is_clone(rsc)) {
207 clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
208
209 pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s "
210 "because %s resources such as %s can be used only as "
211 "anonymous clones", rsc->id, standard, rid);
212
213 clone_data->clone_node_max = 1;
214 clone_data->clone_max = QB_MIN(clone_data->clone_max,
215 g_list_length(scheduler->nodes));
216 }
217 }
218
219 pcmk_resource_t *
220 pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler)
221 {
222 bool removed = false;
223 char *inc_num = NULL;
224 char *inc_max = NULL;
225 pcmk_resource_t *child_rsc = NULL;
226 xmlNode *child_copy = NULL;
227 clone_variant_data_t *clone_data = NULL;
228
229 get_clone_variant_data(clone_data, rsc);
230
231 CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE);
232
233 if (clone_data->total_clones >= clone_data->clone_max) {
234 /* If we've already used all available instances, this instance is
235 * treated as removed
236 */
237 removed = true;
238 }
239
240 // Allocate instance numbers in numerical order (starting at 0)
241 inc_num = pcmk__itoa(clone_data->total_clones);
242 inc_max = pcmk__itoa(clone_data->clone_max);
243
244 // Set PCMK__META_CLONE in a copy, not the original element
245 child_copy = pcmk__xml_copy(NULL, clone_data->xml_obj_child);
246 pcmk__xe_set(child_copy, PCMK__META_CLONE, inc_num);
247
248 if (pe__unpack_resource(child_copy, &child_rsc, rsc,
249 scheduler) != pcmk_rc_ok) {
250 goto bail;
251 }
252 /* child_rsc->globally_unique = rsc->globally_unique; */
253
254 pcmk__assert(child_rsc != NULL);
255 clone_data->total_clones += 1;
256 pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s",
257 child_rsc->id);
258 rsc->priv->children = g_list_append(rsc->priv->children, child_rsc);
259 if (removed) {
260 pe__set_resource_flags_recursive(child_rsc, pcmk__rsc_removed);
261 }
262
263 pcmk__insert_meta(child_rsc->priv, PCMK_META_CLONE_MAX, inc_max);
264 pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id);
265
266 bail:
267 free(inc_num);
268 free(inc_max);
269 pcmk__xml_free(child_copy);
270
271 return child_rsc;
272 }
273
274 /*!
275 * \internal
276 * \brief Unpack a nonnegative integer value from a resource meta-attribute
277 *
278 * \param[in] rsc Resource with meta-attribute
279 * \param[in] meta_name Name of meta-attribute to unpack
280 * \param[in] deprecated_name If not NULL, try unpacking this
281 * if \p meta_name is unset
282 * \param[in] default_value Value to use if unset
283 *
284 * \return Integer parsed from resource's specified meta-attribute if a valid
285 * nonnegative integer, \p default_value if unset, or 0 if invalid
286 */
287 static int
288 unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name,
289 const char *deprecated_name, int default_value)
290 {
291 int integer = default_value;
292 const char *value = g_hash_table_lookup(rsc->priv->meta, meta_name);
293
294 if ((value == NULL) && (deprecated_name != NULL)) {
295 value = g_hash_table_lookup(rsc->priv->meta, deprecated_name);
296
297 if (value != NULL) {
298 if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_MAX_LEGACY,
299 pcmk__str_none)) {
300 pcmk__warn_once(pcmk__wo_clone_master_max,
301 "Support for the " PCMK__META_PROMOTED_MAX_LEGACY
302 " meta-attribute (such as in %s) is deprecated "
303 "and will be removed in a future release. Use the "
304 PCMK_META_PROMOTED_MAX " meta-attribute instead.",
305 rsc->id);
306 } else if (pcmk__str_eq(deprecated_name, PCMK__META_PROMOTED_NODE_MAX_LEGACY,
307 pcmk__str_none)) {
308 pcmk__warn_once(pcmk__wo_clone_master_node_max,
309 "Support for the " PCMK__META_PROMOTED_NODE_MAX_LEGACY
310 " meta-attribute (such as in %s) is deprecated "
311 "and will be removed in a future release. Use the "
312 PCMK_META_PROMOTED_NODE_MAX " meta-attribute instead.",
313 rsc->id);
314 }
315 }
316 }
317 if (value != NULL) {
318 pcmk__scan_min_int(value, &integer, 0);
319 }
320 return integer;
321 }
322
323 bool
324 clone_unpack(pcmk_resource_t *rsc)
325 {
326 int lpc = 0;
327 int num_nodes = g_list_length(rsc->priv->scheduler->nodes);
328 xmlNode *a_child = NULL;
329 xmlNode *xml_obj = rsc->priv->xml;
330 clone_variant_data_t *clone_data = NULL;
331
332 pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
333
334 clone_data = pcmk__assert_alloc(1, sizeof(clone_variant_data_t));
335 rsc->priv->variant_opaque = clone_data;
336
337 if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
338 // Use 1 as default but 0 for minimum and invalid
339 // @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0
340 clone_data->promoted_max =
341 unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX,
342 PCMK__META_PROMOTED_MAX_LEGACY, 1);
343
344 // Use 1 as default but 0 for minimum and invalid
345 // @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0
346 clone_data->promoted_node_max =
347 unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX,
348 PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1);
349 }
350
351 // Use 1 as default but 0 for minimum and invalid
352 clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX,
353 NULL, 1);
354
355 /* Use number of nodes (but always at least 1, which is handy for crm_verify
356 * for a CIB without nodes) as default, but 0 for minimum and invalid
357 *
358 * @TODO Exclude bundle nodes when counting
359 */
360 clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL,
361 QB_MAX(1, num_nodes));
362
363 if (pcmk__is_true(g_hash_table_lookup(rsc->priv->meta,
364 PCMK_META_ORDERED))) {
365 clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
366 "Clone", rsc->id,
367 clone_data->flags,
368 pcmk__clone_ordered,
369 "pcmk__clone_ordered");
370 }
371
372 if (!pcmk__is_set(rsc->flags, pcmk__rsc_unique)
373 && (clone_data->clone_node_max > 1)) {
374
375 pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s "
376 "because anonymous clones support only one instance "
377 "per node", clone_data->clone_node_max, rsc->id);
378 clone_data->clone_node_max = 1;
379 }
380
381 pcmk__rsc_trace(rsc, "Options for %s", rsc->id);
382 pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max);
383 pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max);
384 pcmk__rsc_trace(rsc, "\tClone is unique: %s",
385 pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
386 pcmk__rsc_trace(rsc, "\tClone is promotable: %s",
387 pcmk__flag_text(rsc->flags, pcmk__rsc_promotable));
388
389 // Clones may contain a single group or primitive
390 for (a_child = pcmk__xe_first_child(xml_obj, NULL, NULL, NULL);
391 a_child != NULL; a_child = pcmk__xe_next(a_child, NULL)) {
392
393 if (pcmk__str_any_of((const char *) a_child->name,
394 PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) {
395 clone_data->xml_obj_child = a_child;
396 break;
397 }
398 }
399
400 if (clone_data->xml_obj_child == NULL) {
401 pcmk__config_err("%s has nothing to clone", rsc->id);
402 return FALSE;
403 }
404
405 /*
406 * Make clones ever so slightly sticky by default
407 *
408 * This helps ensure clone instances are not shuffled around the cluster
409 * for no benefit in situations when pre-allocation is not appropriate
410 */
411 if (g_hash_table_lookup(rsc->priv->meta,
412 PCMK_META_RESOURCE_STICKINESS) == NULL) {
413 pcmk__insert_meta(rsc->priv, PCMK_META_RESOURCE_STICKINESS, "1");
414 }
415
416 /* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for
417 * children to inherit when being unpacked, as well as in resource agents'
418 * environment.
419 */
420 pcmk__insert_meta(rsc->priv, PCMK_META_GLOBALLY_UNIQUE,
421 pcmk__flag_text(rsc->flags, pcmk__rsc_unique));
422
423 if (clone_data->clone_max <= 0) {
424 /* Create one child instance so that unpack_find_resource() will hook up
425 * any removed instances up to the parent correctly.
426 */
427 if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
428 return FALSE;
429 }
430
431 } else {
432 // Create a child instance for each available instance number
433 for (lpc = 0; lpc < clone_data->clone_max; lpc++) {
434 if (pe__create_clone_child(rsc, rsc->priv->scheduler) == NULL) {
435 return FALSE;
436 }
437 }
438 }
439
440 pcmk__rsc_trace(rsc, "Added %d children to resource %s...",
441 clone_data->clone_max, rsc->id);
442 return TRUE;
443 }
444
445 bool
446 clone_active(const pcmk_resource_t *rsc, bool all)
447 {
448 for (GList *gIter = rsc->priv->children;
449 gIter != NULL; gIter = gIter->next) {
450
451 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
452 bool child_active = child_rsc->priv->fns->active(child_rsc, all);
453
454 if (all == FALSE && child_active) {
455 return TRUE;
456 } else if (all && child_active == FALSE) {
457 return FALSE;
458 }
459 }
460
461 if (all) {
462 return TRUE;
463 } else {
464 return FALSE;
465 }
466 }
467
468 static const char *
469 configured_role_str(pcmk_resource_t * rsc)
470 {
471 const char *target_role = g_hash_table_lookup(rsc->priv->meta,
472 PCMK_META_TARGET_ROLE);
473
474 if ((target_role == NULL) && (rsc->priv->children != NULL)) {
475 // Any instance will do
476 pcmk_resource_t *instance = rsc->priv->children->data;
477
478 target_role = g_hash_table_lookup(instance->priv->meta,
479 PCMK_META_TARGET_ROLE);
480 }
481 return target_role;
482 }
483
484 static enum rsc_role_e
485 configured_role(pcmk_resource_t *rsc)
486 {
487 enum rsc_role_e role = pcmk_role_unknown;
488 const char *target_role = configured_role_str(rsc);
489
490 if (target_role != NULL) {
491 role = pcmk_parse_role(target_role);
492 if (role == pcmk_role_unknown) {
493 pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE
494 " for resource %s", rsc->id);
495 }
496 }
497 return role;
498 }
499
500 bool
501 is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any)
502 {
503 bool all = !any;
504
505 if (pcmk__is_set(rsc->flags, flag)) {
506 if(any) {
507 return TRUE;
508 }
509 } else if(all) {
510 return FALSE;
511 }
512
513 for (GList *gIter = rsc->priv->children;
514 gIter != NULL; gIter = gIter->next) {
515
516 if(is_set_recursive(gIter->data, flag, any)) {
517 if(any) {
518 return TRUE;
519 }
520
521 } else if(all) {
522 return FALSE;
523 }
524 }
525
526 if(all) {
527 return TRUE;
528 }
529 return FALSE;
530 }
531
532 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
533 "GList *")
534 int
535 pe__clone_xml(pcmk__output_t *out, va_list args)
536 {
537 uint32_t show_opts = va_arg(args, uint32_t);
538 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
539 GList *only_node = va_arg(args, GList *);
540 GList *only_rsc = va_arg(args, GList *);
541
542 GList *all = NULL;
543 int rc = pcmk_rc_no_output;
544 gboolean printed_header = FALSE;
545 bool print_everything = true;
546
547 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
548 return rc;
549 }
550
551 print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
552 pcmk__str_star_matches)
553 || ((strchr(rsc->id, ':') != NULL)
554 && pcmk__str_in_list(rsc->id, only_rsc,
555 pcmk__str_star_matches));
556
557 all = g_list_prepend(all, (gpointer) "*");
558
559 for (GList *gIter = rsc->priv->children;
560 gIter != NULL; gIter = gIter->next) {
561
562 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
563
564 if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
565 continue;
566 }
567
568 if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
569 print_everything)) {
570 continue;
571 }
572
573 if (!printed_header) {
574 const char *multi_state = pcmk__flag_text(rsc->flags,
575 pcmk__rsc_promotable);
576 const char *unique = pcmk__flag_text(rsc->flags, pcmk__rsc_unique);
577 const char *maintenance = pcmk__flag_text(rsc->flags,
578 pcmk__rsc_maintenance);
579 const char *managed = pcmk__flag_text(rsc->flags,
580 pcmk__rsc_managed);
581 const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
582 const char *failed = pcmk__flag_text(rsc->flags, pcmk__rsc_failed);
583 const char *ignored = pcmk__flag_text(rsc->flags,
584 pcmk__rsc_ignore_failure);
585 const char *target_role = configured_role_str(rsc);
586 const char *desc = pe__resource_description(rsc, show_opts);
587
588 printed_header = TRUE;
589
590 rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE,
591 PCMK_XA_ID, rsc->id,
592 PCMK_XA_MULTI_STATE, multi_state,
593 PCMK_XA_UNIQUE, unique,
594 PCMK_XA_MAINTENANCE, maintenance,
595 PCMK_XA_MANAGED, managed,
596 PCMK_XA_DISABLED, disabled,
597 PCMK_XA_FAILED, failed,
598 PCMK_XA_FAILURE_IGNORED, ignored,
599 PCMK_XA_TARGET_ROLE, target_role,
600 PCMK_XA_DESCRIPTION, desc,
601 NULL);
602 pcmk__assert(rc == pcmk_rc_ok);
603 }
604
605 out->message(out, (const char *) child_rsc->priv->xml->name,
606 show_opts, child_rsc, only_node, all);
607 }
608
609 if (printed_header) {
610 pcmk__output_xml_pop_parent(out);
611 }
612
613 g_list_free(all);
614 return rc;
615 }
616
617 PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *",
618 "GList *")
619 int
620 pe__clone_default(pcmk__output_t *out, va_list args)
621 {
622 uint32_t show_opts = va_arg(args, uint32_t);
623 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
624 GList *only_node = va_arg(args, GList *);
625 GList *only_rsc = va_arg(args, GList *);
626
627 GHashTable *stopped = NULL;
628
629 GString *list_text = NULL;
630
631 GList *promoted_list = NULL;
632 GList *started_list = NULL;
633 GList *gIter = NULL;
634
635 const char *desc = NULL;
636
637 clone_variant_data_t *clone_data = NULL;
638 int active_instances = 0;
639 int rc = pcmk_rc_no_output;
640 gboolean print_everything = TRUE;
641
642 desc = pe__resource_description(rsc, show_opts);
643
644 get_clone_variant_data(clone_data, rsc);
645
646 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
647 return rc;
648 }
649
650 print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
651 pcmk__str_star_matches)
652 || ((strchr(rsc->id, ':') != NULL)
653 && pcmk__str_in_list(rsc->id, only_rsc,
654 pcmk__str_star_matches));
655
656 for (gIter = rsc->priv->children; gIter != NULL; gIter = gIter->next) {
657 gboolean print_full = FALSE;
658 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
659 bool partially_active = child_rsc->priv->fns->active(child_rsc, false);
660
661 if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) {
662 continue;
663 }
664
665 if (child_rsc->priv->fns->is_filtered(child_rsc, only_rsc,
666 print_everything)) {
667 continue;
668 }
669
670 if (pcmk__is_set(show_opts, pcmk_show_clone_detail)) {
671 print_full = TRUE;
672 }
673
674 if (pcmk__is_set(rsc->flags, pcmk__rsc_unique)) {
675 // Print individual instance when unique, unless stopped and removed
676 if (partially_active
677 || !pcmk__is_set(rsc->flags, pcmk__rsc_removed)) {
678 print_full = TRUE;
679 }
680
681 // Everything else in this block is for anonymous clones
682
683 } else if (pcmk__is_set(show_opts, pcmk_show_pending)
684 && (child_rsc->priv->pending_action != NULL)
685 && (strcmp(child_rsc->priv->pending_action,
686 "probe") != 0)) {
687 // Print individual instance when non-probe action is pending
688 print_full = TRUE;
689
690 } else if (partially_active == FALSE) {
691 // List stopped instances when requested (except removed instances)
692 if (!pcmk__is_set(child_rsc->flags, pcmk__rsc_removed)
693 && !pcmk__is_set(show_opts, pcmk_show_clone_detail)
694 && pcmk__is_set(show_opts, pcmk_show_inactive_rscs)) {
695 if (stopped == NULL) {
696 stopped = pcmk__strkey_table(free, free);
697 }
698 pcmk__insert_dup(stopped, child_rsc->id, "Stopped");
699 }
700
701 } else if (is_set_recursive(child_rsc, pcmk__rsc_removed, TRUE)
702 || !is_set_recursive(child_rsc, pcmk__rsc_managed, FALSE)
703 || is_set_recursive(child_rsc, pcmk__rsc_failed, TRUE)) {
704
705 // Print individual instance when active removed/unmanaged/failed
706 print_full = TRUE;
707
708 } else if (child_rsc->priv->fns->active(child_rsc, true)) {
709 // Instance of fully active anonymous clone
710
711 pcmk_node_t *location = NULL;
712
713 location = child_rsc->priv->fns->location(child_rsc, NULL,
714 pcmk__rsc_node_current);
715 if (location) {
716 // Instance is active on a single node
717
718 enum rsc_role_e a_role;
719
720 a_role = child_rsc->priv->fns->state(child_rsc, TRUE);
721
722 if (location->details->online == FALSE && location->details->unclean) {
723 print_full = TRUE;
724
725 } else if (a_role > pcmk_role_unpromoted) {
726 promoted_list = g_list_append(promoted_list, location);
727
728 } else {
729 started_list = g_list_append(started_list, location);
730 }
731
732 } else {
733 /* uncolocated group - bleh */
734 print_full = TRUE;
735 }
736
737 } else {
738 // Instance of partially active anonymous clone
739 print_full = TRUE;
740 }
741
742 if (print_full) {
743 GList *all = NULL;
744
745 clone_header(out, &rc, rsc, clone_data, desc);
746
747 /* Print every resource that's a child of this clone. */
748 all = g_list_prepend(all, (gpointer) "*");
749 out->message(out, (const char *) child_rsc->priv->xml->name,
750 show_opts, child_rsc, only_node, all);
751 g_list_free(all);
752 }
753 }
754
755 if (pcmk__is_set(show_opts, pcmk_show_clone_detail)) {
756 PCMK__OUTPUT_LIST_FOOTER(out, rc);
757
758 g_list_free(promoted_list);
759 g_list_free(started_list);
760 return pcmk_rc_ok;
761 }
762
763 /* Promoted */
764 promoted_list = g_list_sort(promoted_list, pe__cmp_node_name);
765 for (gIter = promoted_list; gIter; gIter = gIter->next) {
766 pcmk_node_t *host = gIter->data;
767
768 if (!pcmk__str_in_list(host->priv->name, only_node,
769 pcmk__str_star_matches|pcmk__str_casei)) {
770 continue;
771 }
772
773 pcmk__add_word(&list_text, 1024, host->priv->name);
774 active_instances++;
775 }
776 g_list_free(promoted_list);
777
778 if ((list_text != NULL) && (list_text->len > 0)) {
779 clone_header(out, &rc, rsc, clone_data, desc);
780
781 out->list_item(out, NULL, PCMK_ROLE_PROMOTED ": [ %s ]",
782 (const char *) list_text->str);
783 g_string_truncate(list_text, 0);
784 }
785
786 /* Started/Unpromoted */
787 started_list = g_list_sort(started_list, pe__cmp_node_name);
788 for (gIter = started_list; gIter; gIter = gIter->next) {
789 pcmk_node_t *host = gIter->data;
790
791 if (!pcmk__str_in_list(host->priv->name, only_node,
792 pcmk__str_star_matches|pcmk__str_casei)) {
793 continue;
794 }
795
796 pcmk__add_word(&list_text, 1024, host->priv->name);
797 active_instances++;
798 }
799 g_list_free(started_list);
800
801 if ((list_text != NULL) && (list_text->len > 0)) {
802 clone_header(out, &rc, rsc, clone_data, desc);
803
804 if (pcmk__is_set(rsc->flags, pcmk__rsc_promotable)) {
805 enum rsc_role_e role = configured_role(rsc);
806
807 if (role == pcmk_role_unpromoted) {
808 out->list_item(out, NULL,
809 PCMK_ROLE_UNPROMOTED
810 " (" PCMK_META_TARGET_ROLE "): [ %s ]",
811 (const char *) list_text->str);
812 } else {
813 out->list_item(out, NULL, PCMK_ROLE_UNPROMOTED ": [ %s ]",
814 (const char *) list_text->str);
815 }
816
817 } else {
818 out->list_item(out, NULL, "Started: [ %s ]",
819 (const char *) list_text->str);
820 }
821 }
822
823 if (list_text != NULL) {
824 g_string_free(list_text, TRUE);
825 }
826
827 if (pcmk__is_set(show_opts, pcmk_show_inactive_rscs)) {
828 if (!pcmk__is_set(rsc->flags, pcmk__rsc_unique)
829 && (clone_data->clone_max > active_instances)) {
830
831 GList *nIter;
832 GList *list = g_hash_table_get_values(rsc->priv->allowed_nodes);
833
834 /* Custom stopped table for non-unique clones */
835 g_clear_pointer(&stopped, g_hash_table_destroy);
836
837 if (list == NULL) {
838 /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't
839 * calculated allowed nodes yet. If we've not probed for them
840 * yet, the Stopped list will be empty.
841 */
842 list = g_hash_table_get_values(rsc->priv->probed_nodes);
843 }
844
845 list = g_list_sort(list, pe__cmp_node_name);
846 for (nIter = list; nIter != NULL; nIter = nIter->next) {
847 pcmk_node_t *node = (pcmk_node_t *) nIter->data;
848
849 if ((pcmk__find_node_in_list(rsc->priv->active_nodes,
850 node->priv->name) == NULL)
851 && pcmk__str_in_list(node->priv->name, only_node,
852 pcmk__str_star_matches|pcmk__str_casei)) {
853
854 xmlNode *probe_op = NULL;
855 const char *state = "Stopped";
856
857 if (configured_role(rsc) == pcmk_role_stopped) {
858 state = "Stopped (disabled)";
859 }
860
861 if (stopped == NULL) {
862 stopped = pcmk__strkey_table(free, free);
863 }
864
865 probe_op = pe__failed_probe_for_rsc(rsc,
866 node->priv->name);
867 if (probe_op != NULL) {
868 int rc = pcmk_rc_ok;
869 char *key = pcmk__str_copy(node->priv->name);
870 char *value = NULL;
871
872 pcmk__scan_min_int(pcmk__xe_get(probe_op,
873 PCMK__XA_RC_CODE),
874 &rc, 0);
875 value = pcmk__assert_asprintf("Stopped (%s)",
876 crm_exit_str(rc));
877 g_hash_table_insert(stopped, key, value);
878
879 } else {
880 pcmk__insert_dup(stopped, node->priv->name, state);
881 }
882 }
883 }
884 g_list_free(list);
885 }
886
887 if (stopped != NULL) {
888 GList *list = sorted_hash_table_values(stopped);
889
890 clone_header(out, &rc, rsc, clone_data, desc);
891
892 for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) {
893 const char *status = status_iter->data;
894 GList *nodes = nodes_with_status(stopped, status);
895 GString *nodes_str = node_list_to_str(nodes);
896
897 if (nodes_str != NULL) {
898 if (nodes_str->len > 0) {
899 out->list_item(out, NULL, "%s: [ %s ]", status,
900 (const char *) nodes_str->str);
901 }
902 g_string_free(nodes_str, TRUE);
903 }
904
905 g_list_free(nodes);
906 }
907
908 g_list_free(list);
909 g_hash_table_destroy(stopped);
910
911 /* If there are no instances of this clone (perhaps because there are no
912 * nodes configured), simply output the clone header by itself. This can
913 * come up in PCS testing.
914 */
915 } else if (active_instances == 0) {
916 clone_header(out, &rc, rsc, clone_data, desc);
917 PCMK__OUTPUT_LIST_FOOTER(out, rc);
918 return rc;
919 }
920 }
921
922 PCMK__OUTPUT_LIST_FOOTER(out, rc);
923 return rc;
924 }
925
926 void
927 clone_free(pcmk_resource_t * rsc)
928 {
929 clone_variant_data_t *clone_data = NULL;
930
931 get_clone_variant_data(clone_data, rsc);
932
933 pcmk__rsc_trace(rsc, "Freeing clone %s, starting with child list", rsc->id);
934
935 g_list_free_full(rsc->priv->children, pcmk__free_resource);
936
937 if (clone_data) {
938 pcmk__assert((clone_data->demote_notify == NULL)
939 && (clone_data->stop_notify == NULL)
940 && (clone_data->start_notify == NULL)
941 && (clone_data->promote_notify == NULL));
942 }
943
944 common_free(rsc);
945 }
946
947 enum rsc_role_e
948 clone_resource_state(const pcmk_resource_t *rsc, bool current)
949 {
950 enum rsc_role_e clone_role = pcmk_role_unknown;
951
952 for (GList *gIter = rsc->priv->children;
953 gIter != NULL; gIter = gIter->next) {
954
955 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
956 enum rsc_role_e a_role = child_rsc->priv->fns->state(child_rsc,
957 current);
958
959 if (a_role > clone_role) {
960 clone_role = a_role;
961 }
962 }
963
964 pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role));
965 return clone_role;
966 }
967
968 /*!
969 * \internal
970 * \brief Check whether a clone has an instance for every node
971 *
972 * \param[in] rsc Clone to check
973 * \param[in] scheduler Scheduler data
974 */
975 bool
976 pe__is_universal_clone(const pcmk_resource_t *rsc,
977 const pcmk_scheduler_t *scheduler)
978 {
979 if (pcmk__is_clone(rsc)) {
980 clone_variant_data_t *clone_data = rsc->priv->variant_opaque;
981
982 if (clone_data->clone_max == g_list_length(scheduler->nodes)) {
983 return TRUE;
984 }
985 }
986 return FALSE;
987 }
988
989 bool
990 pe__clone_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
991 bool check_parent)
992 {
993 clone_variant_data_t *clone_data = NULL;
994
995 if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
996 pcmk__str_star_matches)) {
997 return false;
998 }
999
1000 get_clone_variant_data(clone_data, rsc);
1001
1002 if (pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child), only_rsc,
1003 pcmk__str_star_matches)) {
1004 return false;
1005 }
1006
1007 for (const GList *iter = rsc->priv->children; iter != NULL;
1008 iter = iter->next) {
1009
1010 const pcmk_resource_t *child_rsc = iter->data;
1011
1012 if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, false)) {
1013 return false;
1014 }
1015 }
1016
1017 return true;
1018 }
1019
1020 const char *
1021 pe__clone_child_id(const pcmk_resource_t *rsc)
1022 {
1023 clone_variant_data_t *clone_data = NULL;
1024 get_clone_variant_data(clone_data, rsc);
1025 return pcmk__xe_id(clone_data->xml_obj_child);
1026 }
1027
1028 /*!
1029 * \internal
1030 * \brief Check whether a clone is ordered
1031 *
1032 * \param[in] clone Clone resource to check
1033 *
1034 * \return true if clone is ordered, otherwise false
1035 */
1036 bool
1037 pe__clone_is_ordered(const pcmk_resource_t *clone)
1038 {
1039 clone_variant_data_t *clone_data = NULL;
1040
1041 get_clone_variant_data(clone_data, clone);
1042 return pcmk__is_set(clone_data->flags, pcmk__clone_ordered);
1043 }
1044
1045 /*!
1046 * \internal
1047 * \brief Set a clone flag
1048 *
1049 * \param[in,out] clone Clone resource to set flag for
1050 * \param[in] flag Clone flag to set
1051 *
1052 * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not
1053 * already set or pcmk_rc_already if it was)
1054 */
1055 int
1056 pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag)
1057 {
1058 clone_variant_data_t *clone_data = NULL;
1059
1060 get_clone_variant_data(clone_data, clone);
1061 if (pcmk__is_set(clone_data->flags, flag)) {
1062 return pcmk_rc_already;
1063 }
1064 clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE,
1065 "Clone", clone->id,
1066 clone_data->flags, flag, "flag");
1067 return pcmk_rc_ok;
1068 }
1069
1070 /*!
1071 * \internal
1072 * \brief Check whether a clone flag is set
1073 *
1074 * \param[in] group Clone resource to check
1075 * \param[in] flags Flag or flags to check
1076 *
1077 * \return \c true if all \p flags are set for \p clone, otherwise \c false
1078 */
1079 bool
1080 pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags)
1081 {
1082 clone_variant_data_t *clone_data = NULL;
1083
1084 get_clone_variant_data(clone_data, clone);
1085 pcmk__assert(clone_data != NULL);
1086
1087 return pcmk__all_flags_set(clone_data->flags, flags);
1088 }
1089
1090 /*!
1091 * \internal
1092 * \brief Create pseudo-actions needed for promotable clones
1093 *
1094 * \param[in,out] clone Promotable clone to create actions for
1095 * \param[in] any_promoting Whether any instances will be promoted
1096 * \param[in] any_demoting Whether any instance will be demoted
1097 */
1098 void
1099 pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting,
1100 bool any_demoting)
1101 {
1102 pcmk_action_t *action = NULL;
1103 pcmk_action_t *action_complete = NULL;
1104 clone_variant_data_t *clone_data = NULL;
1105
1106 get_clone_variant_data(clone_data, clone);
1107
1108 // Create a "promote" action for the clone itself
1109 action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE,
1110 !any_promoting, true);
1111
1112 // Create a "promoted" action for when all promotions are done
1113 action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED,
1114 !any_promoting, true);
1115 action_complete->priority = PCMK_SCORE_INFINITY;
1116
1117 // Create notification pseudo-actions for promotion
1118 if (clone_data->promote_notify == NULL) {
1119 clone_data->promote_notify = pe__action_notif_pseudo_ops(clone,
1120 PCMK_ACTION_PROMOTE,
1121 action,
1122 action_complete);
1123 }
1124
1125 // Create a "demote" action for the clone itself
1126 action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE,
1127 !any_demoting, true);
1128
1129 // Create a "demoted" action for when all demotions are done
1130 action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED,
1131 !any_demoting, true);
1132 action_complete->priority = PCMK_SCORE_INFINITY;
1133
1134 // Create notification pseudo-actions for demotion
1135 if (clone_data->demote_notify == NULL) {
1136 clone_data->demote_notify = pe__action_notif_pseudo_ops(clone,
1137 PCMK_ACTION_DEMOTE,
1138 action,
1139 action_complete);
1140
1141 if (clone_data->promote_notify != NULL) {
1142 order_actions(clone_data->stop_notify->post_done,
1143 clone_data->promote_notify->pre, pcmk__ar_ordered);
1144 order_actions(clone_data->start_notify->post_done,
1145 clone_data->promote_notify->pre, pcmk__ar_ordered);
1146 order_actions(clone_data->demote_notify->post_done,
1147 clone_data->promote_notify->pre, pcmk__ar_ordered);
1148 order_actions(clone_data->demote_notify->post_done,
1149 clone_data->start_notify->pre, pcmk__ar_ordered);
1150 order_actions(clone_data->demote_notify->post_done,
1151 clone_data->stop_notify->pre, pcmk__ar_ordered);
1152 }
1153 }
1154 }
1155
1156 /*!
1157 * \internal
1158 * \brief Create all notification data and actions for a clone
1159 *
1160 * \param[in,out] clone Clone to create notifications for
1161 */
1162 void
1163 pe__create_clone_notifications(pcmk_resource_t *clone)
1164 {
1165 clone_variant_data_t *clone_data = NULL;
1166
1167 get_clone_variant_data(clone_data, clone);
1168
1169 pe__create_action_notifications(clone, clone_data->start_notify);
1170 pe__create_action_notifications(clone, clone_data->stop_notify);
1171 pe__create_action_notifications(clone, clone_data->promote_notify);
1172 pe__create_action_notifications(clone, clone_data->demote_notify);
1173 }
1174
1175 /*!
1176 * \internal
1177 * \brief Free all notification data for a clone
1178 *
1179 * \param[in,out] clone Clone to free notification data for
1180 */
1181 void
1182 pe__free_clone_notification_data(pcmk_resource_t *clone)
1183 {
1184 clone_variant_data_t *clone_data = NULL;
1185
|
(1) Event path: |
Condition "!pcmk__is_clone(clone)", taking false branch. |
1186 get_clone_variant_data(clone_data, clone);
1187
|
CID (unavailable; MK=e0f99bd5274b09d62fbbc5450fbda9a2) (#1 of 4): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(2) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(3) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
1188 g_clear_pointer(&clone_data->demote_notify,
1189 pe__free_action_notification_data);
1190 g_clear_pointer(&clone_data->stop_notify,
1191 pe__free_action_notification_data);
1192 g_clear_pointer(&clone_data->start_notify,
1193 pe__free_action_notification_data);
1194 g_clear_pointer(&clone_data->promote_notify,
1195 pe__free_action_notification_data);
1196 }
1197
1198 /*!
1199 * \internal
1200 * \brief Create pseudo-actions for clone start/stop notifications
1201 *
1202 * \param[in,out] clone Clone to create pseudo-actions for
1203 * \param[in,out] start Start action for \p clone
1204 * \param[in,out] stop Stop action for \p clone
1205 * \param[in,out] started Started action for \p clone
1206 * \param[in,out] stopped Stopped action for \p clone
1207 */
1208 void
1209 pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone,
1210 pcmk_action_t *start, pcmk_action_t *started,
1211 pcmk_action_t *stop, pcmk_action_t *stopped)
1212 {
1213 clone_variant_data_t *clone_data = NULL;
1214
1215 get_clone_variant_data(clone_data, clone);
1216
1217 if (clone_data->start_notify == NULL) {
1218 clone_data->start_notify = pe__action_notif_pseudo_ops(clone,
1219 PCMK_ACTION_START,
1220 start, started);
1221 }
1222
1223 if (clone_data->stop_notify == NULL) {
1224 clone_data->stop_notify = pe__action_notif_pseudo_ops(clone,
1225 PCMK_ACTION_STOP,
1226 stop, stopped);
1227 if ((clone_data->start_notify != NULL)
1228 && (clone_data->stop_notify != NULL)) {
1229 order_actions(clone_data->stop_notify->post_done,
1230 clone_data->start_notify->pre, pcmk__ar_ordered);
1231 }
1232 }
1233 }
1234
1235 /*!
1236 * \internal
1237 * \brief Get maximum clone resource instances per node
1238 *
1239 * \param[in] rsc Clone resource to check
1240 *
1241 * \return Maximum number of \p rsc instances that can be active on one node
1242 */
1243 unsigned int
1244 pe__clone_max_per_node(const pcmk_resource_t *rsc)
1245 {
1246 const clone_variant_data_t *clone_data = NULL;
1247
1248 get_clone_variant_data(clone_data, rsc);
1249 return clone_data->clone_node_max;
1250 }
1251