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 <crm/common/xml.h>
18 #include <crm/common/output.h>
19 #include <pe_status_private.h>
20
21 typedef struct {
22 pcmk_resource_t *last_child; // Last group member
23 uint32_t flags; // Group of enum pcmk__group_flags
24 } group_variant_data_t;
25
26 /*!
27 * \internal
28 * \brief Get a group's last member
29 *
30 * \param[in] group Group resource to check
31 *
32 * \return Last member of \p group if any, otherwise NULL
33 */
34 pcmk_resource_t *
35 pe__last_group_member(const pcmk_resource_t *group)
36 {
37 if (group != NULL) {
38 const group_variant_data_t *group_data = NULL;
39
40 CRM_CHECK(pcmk__is_group(group), return NULL);
41 group_data = group->priv->variant_opaque;
42 return group_data->last_child;
43 }
44 return NULL;
45 }
46
47 /*!
48 * \internal
49 * \brief Check whether a group flag is set
50 *
51 * \param[in] group Group resource to check
52 * \param[in] flags Flag or flags to check
53 *
54 * \return true if all \p flags are set for \p group, otherwise false
55 */
56 bool
57 pe__group_flag_is_set(const pcmk_resource_t *group, uint32_t flags)
58 {
59 const group_variant_data_t *group_data = NULL;
60
61 CRM_CHECK(pcmk__is_group(group), return false);
62 group_data = group->priv->variant_opaque;
63 return pcmk__all_flags_set(group_data->flags, flags);
64 }
65
66 /*!
67 * \internal
68 * \brief Set a (deprecated) group flag
69 *
70 * \param[in,out] group Group resource to check
71 * \param[in] option Name of boolean configuration option
72 * \param[in] flag Flag to set if \p option is true (which is default)
73 * \param[in] wo_bit "Warn once" flag to use for deprecation warning
74 */
75 static void
76 set_group_flag(pcmk_resource_t *group, const char *option, uint32_t flag,
77 uint32_t wo_bit)
78 {
79 const char *value_s = g_hash_table_lookup(group->priv->meta, option);
80 bool value = false;
81
82 if ((value_s == NULL) || (pcmk__parse_bool(value_s, &value) != pcmk_rc_ok)
83 || value) {
84 // Set flag if value is unset, invalid, or true
85 group_variant_data_t *group_data = group->priv->variant_opaque;
86
87 group_data->flags |= flag;
88
89 } else {
90 pcmk__warn_once(wo_bit,
91 "Support for the '%s' group meta-attribute is "
92 "deprecated and will be removed in a future release "
93 "(use a resource set instead)", option);
94 }
95 }
96
97 static int
98 inactive_resources(pcmk_resource_t *rsc)
99 {
100 int retval = 0;
101
102 for (GList *gIter = rsc->priv->children;
103 gIter != NULL; gIter = gIter->next) {
104
105 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
106
107 if (!child_rsc->priv->fns->active(child_rsc, true)) {
108 retval++;
109 }
110 }
111
112 return retval;
113 }
114
115 static void
116 group_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc,
117 int n_inactive, bool show_inactive, const char *desc)
118 {
119 GString *attrs = NULL;
120
121 if (n_inactive > 0 && !show_inactive) {
122 attrs = g_string_sized_new(64);
123 g_string_append_printf(attrs, "%d member%s inactive", n_inactive,
124 pcmk__plural_s(n_inactive));
125 }
126
127 if (pe__resource_is_disabled(rsc)) {
128 pcmk__add_separated_word(&attrs, 64, "disabled", ", ");
129 }
130
131 if (pcmk__is_set(rsc->flags, pcmk__rsc_maintenance)) {
132 pcmk__add_separated_word(&attrs, 64, "maintenance", ", ");
133
134 } else if (!pcmk__is_set(rsc->flags, pcmk__rsc_managed)) {
135 pcmk__add_separated_word(&attrs, 64, "unmanaged", ", ");
136 }
137
138 if (attrs != NULL) {
139 PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s (%s)%s%s%s",
140 rsc->id,
141 (const char *) attrs->str, desc ? " (" : "",
142 desc ? desc : "", desc ? ")" : "");
143 g_string_free(attrs, TRUE);
144 } else {
145 PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s%s%s%s",
146 rsc->id,
147 desc ? " (" : "", desc ? desc : "",
148 desc ? ")" : "");
149 }
150 }
151
152 static bool
153 skip_child_rsc(pcmk_resource_t *rsc, pcmk_resource_t *child, bool parent_passes,
154 GList *only_rsc, uint32_t show_opts)
155 {
156 const bool star_list = pcmk__list_of_1(only_rsc)
157 && pcmk__str_eq("*", g_list_first(only_rsc)->data,
158 pcmk__str_none);
159 const bool child_filtered = child->priv->fns->is_filtered(child, only_rsc,
160 false);
161 const bool child_active = child->priv->fns->active(child, false);
162 const bool show_inactive = pcmk__is_set(show_opts, pcmk_show_inactive_rscs);
163
164 /* If the resource is in only_rsc by name (so, ignoring "*") then allow
165 * it regardless of if it's active or not.
166 */
167 if (!star_list && !child_filtered) {
168 return false;
169
170 } else if (!child_filtered && (child_active || show_inactive)) {
171 return false;
172
173 } else if (parent_passes && (child_active || show_inactive)) {
174 return false;
175
176 }
177
178 return true;
179 }
180
181 bool
182 group_unpack(pcmk_resource_t *rsc)
183 {
184 xmlNode *xml_obj = rsc->priv->xml;
185 xmlNode *xml_native_rsc = NULL;
186 group_variant_data_t *group_data = NULL;
187 const char *clone_id = NULL;
188
|
(1) Event path: |
Switch case default. |
|
(2) Event path: |
Condition "trace_tag_cs == NULL", taking true branch. |
|
(3) Event path: |
Condition "crm_is_callsite_active(trace_tag_cs, _level, converted_tag)", taking false branch. |
189 pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id);
190
191 group_data = pcmk__assert_alloc(1, sizeof(group_variant_data_t));
192 group_data->last_child = NULL;
193 rsc->priv->variant_opaque = group_data;
194
195 // @COMPAT These are deprecated since 2.1.5
196 set_group_flag(rsc, PCMK_META_ORDERED, pcmk__group_ordered,
197 pcmk__wo_group_order);
198 set_group_flag(rsc, "collocated", pcmk__group_colocated,
199 pcmk__wo_group_coloc);
200
201 clone_id = pcmk__xe_get(rsc->priv->xml, PCMK__META_CLONE);
202
|
(4) Event path: |
Condition "xml_native_rsc != NULL", taking true branch. |
|
(10) Event path: |
Condition "xml_native_rsc != NULL", taking true branch. |
|
(13) Event path: |
Condition "xml_native_rsc != NULL", taking false branch. |
203 for (xml_native_rsc = pcmk__xe_first_child(xml_obj, PCMK_XE_PRIMITIVE,
204 NULL, NULL);
205 xml_native_rsc != NULL;
206 xml_native_rsc = pcmk__xe_next(xml_native_rsc, PCMK_XE_PRIMITIVE)) {
207
208 pcmk_resource_t *new_rsc = NULL;
209
210 pcmk__xe_set(xml_native_rsc, PCMK__META_CLONE, clone_id);
|
(5) Event path: |
Condition "pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, rsc->priv->scheduler) != pcmk_rc_ok", taking false branch. |
|
(11) Event path: |
Condition "pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, rsc->priv->scheduler) != pcmk_rc_ok", taking true branch. |
211 if (pe__unpack_resource(xml_native_rsc, &new_rsc, rsc,
212 rsc->priv->scheduler) != pcmk_rc_ok) {
|
(12) Event path: |
Continuing loop. |
213 continue;
214 }
215
216 rsc->priv->children = g_list_append(rsc->priv->children, new_rsc);
217 group_data->last_child = new_rsc;
|
(6) Event path: |
Switch case default. |
|
(7) Event path: |
Condition "trace_tag_cs == NULL", taking true branch. |
|
(8) Event path: |
Condition "crm_is_callsite_active(trace_tag_cs, _level, converted_tag)", taking false branch. |
218 pcmk__rsc_trace(rsc, "Added %s member %s", rsc->id, new_rsc->id);
|
(9) Event path: |
Jumping back to the beginning of the loop. |
219 }
220
|
(14) Event path: |
Condition "rsc->priv->children == NULL", taking true branch. |
221 if (rsc->priv->children == NULL) {
222 // Not possible with schema validation enabled
|
CID (unavailable; MK=7793849108895c5ffa254533e4da0326) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(15) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(16) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
223 g_clear_pointer(&rsc->priv->variant_opaque, free);
224 pcmk__config_err("Group %s has no members", rsc->id);
225 return FALSE;
226 }
227
228 return TRUE;
229 }
230
231 bool
232 group_active(const pcmk_resource_t *rsc, bool all)
233 {
234 gboolean c_all = TRUE;
235 gboolean c_any = FALSE;
236
237 for (GList *gIter = rsc->priv->children;
238 gIter != NULL; gIter = gIter->next) {
239
240 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
241
242 if (child_rsc->priv->fns->active(child_rsc, all)) {
243 c_any = TRUE;
244 } else {
245 c_all = FALSE;
246 }
247 }
248
249 if (c_any == FALSE) {
250 return FALSE;
251 } else if (all && c_all == FALSE) {
252 return FALSE;
253 }
254 return TRUE;
255 }
256
257 PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
258 "GList *")
259 int
260 pe__group_xml(pcmk__output_t *out, va_list args)
261 {
262 uint32_t show_opts = va_arg(args, uint32_t);
263 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
264 GList *only_node = va_arg(args, GList *);
265 GList *only_rsc = va_arg(args, GList *);
266
267 const char *desc = NULL;
268
269 int rc = pcmk_rc_no_output;
270
271 bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
272 pcmk__str_star_matches)
273 || ((strchr(rsc->id, ':') != NULL)
274 && pcmk__str_in_list(rsc->id, only_rsc,
275 pcmk__str_star_matches));
276
277 desc = pe__resource_description(rsc, show_opts);
278
279 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
280 return rc;
281 }
282
283 for (GList *gIter = rsc->priv->children;
284 gIter != NULL; gIter = gIter->next) {
285
286 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
287
288 if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
289 continue;
290 }
291
292 if (rc == pcmk_rc_no_output) {
293 char *count = pcmk__itoa(g_list_length(gIter));
294 const char *maintenance = pcmk__flag_text(rsc->flags,
295 pcmk__rsc_maintenance);
296 const char *managed = pcmk__flag_text(rsc->flags,
297 pcmk__rsc_managed);
298 const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
299
300 rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_GROUP,
301 PCMK_XA_ID, rsc->id,
302 PCMK_XA_NUMBER_RESOURCES, count,
303 PCMK_XA_MAINTENANCE, maintenance,
304 PCMK_XA_MANAGED, managed,
305 PCMK_XA_DISABLED, disabled,
306 PCMK_XA_DESCRIPTION, desc,
307 NULL);
308 free(count);
309 pcmk__assert(rc == pcmk_rc_ok);
310 }
311
312 out->message(out, (const char *) child_rsc->priv->xml->name,
313 show_opts, child_rsc, only_node, only_rsc);
314 }
315
316 if (rc == pcmk_rc_ok) {
317 pcmk__output_xml_pop_parent(out);
318 }
319
320 return rc;
321 }
322
323 PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
324 "GList *")
325 int
326 pe__group_default(pcmk__output_t *out, va_list args)
327 {
328 uint32_t show_opts = va_arg(args, uint32_t);
329 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
330 GList *only_node = va_arg(args, GList *);
331 GList *only_rsc = va_arg(args, GList *);
332
333 const char *desc = NULL;
334 int rc = pcmk_rc_no_output;
335
336 bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
337 pcmk__str_star_matches)
338 || ((strchr(rsc->id, ':') != NULL)
339 && pcmk__str_in_list(rsc->id, only_rsc,
340 pcmk__str_star_matches));
341
342 const bool active = rsc->priv->fns->active(rsc, true);
343 const bool partially_active = rsc->priv->fns->active(rsc, false);
344 const bool count_inactive = !active && partially_active;
345
346 desc = pe__resource_description(rsc, show_opts);
347
348 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
349 return rc;
350 }
351
352 if (pcmk__is_set(show_opts, pcmk_show_brief)) {
353 GList *rscs = pe__filter_rsc_list(rsc->priv->children, only_rsc);
354
355 if (rscs != NULL) {
356 group_header(out, &rc, rsc,
357 (count_inactive? inactive_resources(rsc) : 0),
358 pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
359 desc);
360 pe__rscs_brief_output(out, rscs, show_opts | pcmk_show_inactive_rscs);
361
362 rc = pcmk_rc_ok;
363 g_list_free(rscs);
364 }
365
366 } else {
367 for (GList *gIter = rsc->priv->children;
368 gIter != NULL; gIter = gIter->next) {
369 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
370
371 if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
372 continue;
373 }
374
375 group_header(out, &rc, rsc,
376 (count_inactive? inactive_resources(rsc) : 0),
377 pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
378 desc);
379 out->message(out, (const char *) child_rsc->priv->xml->name,
380 show_opts, child_rsc, only_node, only_rsc);
381 }
382 }
383
384 PCMK__OUTPUT_LIST_FOOTER(out, rc);
385
386 return rc;
387 }
388
389 void
390 group_free(pcmk_resource_t * rsc)
391 {
392 CRM_CHECK(rsc != NULL, return);
393
394 pcmk__rsc_trace(rsc, "Freeing %s", rsc->id);
395
396 for (GList *gIter = rsc->priv->children;
397 gIter != NULL; gIter = gIter->next) {
398
399 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
400
401 pcmk__assert(child_rsc != NULL);
402 pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id);
403 pcmk__free_resource(child_rsc);
404 }
405
406 pcmk__rsc_trace(rsc, "Freeing child list");
407 g_list_free(rsc->priv->children);
408
409 common_free(rsc);
410 }
411
412 enum rsc_role_e
413 group_resource_state(const pcmk_resource_t *rsc, bool current)
414 {
415 enum rsc_role_e group_role = pcmk_role_unknown;
416
417 for (GList *gIter = rsc->priv->children;
418 gIter != NULL; gIter = gIter->next) {
419
420 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
421 enum rsc_role_e role = child_rsc->priv->fns->state(child_rsc,
422 current);
423
424 if (role > group_role) {
425 group_role = role;
426 }
427 }
428
429 pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(group_role));
430 return group_role;
431 }
432
433 bool
434 pe__group_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
435 bool check_parent)
436 {
437 if (check_parent) {
438 const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
439
440 if (pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
441 pcmk__str_star_matches)) {
442 return false;
443 }
444 }
445
446 if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
447 pcmk__str_star_matches)) {
448 return false;
449 }
450
451 if ((strchr(rsc->id, ':') != NULL)
452 && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) {
453 return false;
454 }
455
456 for (const GList *iter = rsc->priv->children; iter != NULL;
457 iter = iter->next) {
458
459 const pcmk_resource_t *child_rsc = iter->data;
460
461 if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, false)) {
462 return false;
463 }
464 }
465
466 return true;
467 }
468
469 /*!
470 * \internal
471 * \brief Get maximum group resource instances per node
472 *
473 * \param[in] rsc Group resource to check
474 *
475 * \return Maximum number of \p rsc instances that can be active on one node
476 */
477 unsigned int
478 pe__group_max_per_node(const pcmk_resource_t *rsc)
479 {
480 pcmk__assert(pcmk__is_group(rsc));
481 return 1U;
482 }
483