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