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. |
|
(7) 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 /* If clone_id is non-NULL, then the group XML (with its children) is a
211 * copy created while unpacking a clone. So setting the PCMK__META_CLONE
212 * attribute here does not affect the original CIB XML.
213 */
214 pcmk__xe_set(xml_native_rsc, PCMK__META_CLONE, clone_id);
215
|
(5) Event path: |
Condition "pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, rsc->priv->scheduler) != pcmk_rc_ok", taking true branch. |
216 if (pe__unpack_resource(xml_native_rsc, &new_rsc, rsc,
217 rsc->priv->scheduler) != pcmk_rc_ok) {
|
(6) Event path: |
Continuing loop. |
218 continue;
219 }
220
221 rsc->priv->children = g_list_append(rsc->priv->children, new_rsc);
222 group_data->last_child = new_rsc;
223 pcmk__rsc_trace(rsc, "Added %s member %s", rsc->id, new_rsc->id);
224 }
225
|
(8) Event path: |
Condition "rsc->priv->children == NULL", taking true branch. |
226 if (rsc->priv->children == NULL) {
227 // Not possible with schema validation enabled
|
CID (unavailable; MK=7793849108895c5ffa254533e4da0326) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(9) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(10) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
228 g_clear_pointer(&rsc->priv->variant_opaque, free);
229 pcmk__config_err("Group %s has no members", rsc->id);
230 return FALSE;
231 }
232
233 return TRUE;
234 }
235
236 bool
237 group_active(const pcmk_resource_t *rsc, bool all)
238 {
239 gboolean c_all = TRUE;
240 gboolean c_any = FALSE;
241
242 for (GList *gIter = rsc->priv->children;
243 gIter != NULL; gIter = gIter->next) {
244
245 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
246
247 if (child_rsc->priv->fns->active(child_rsc, all)) {
248 c_any = TRUE;
249 } else {
250 c_all = FALSE;
251 }
252 }
253
254 if (c_any == FALSE) {
255 return FALSE;
256 } else if (all && c_all == FALSE) {
257 return FALSE;
258 }
259 return TRUE;
260 }
261
262 PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
263 "GList *")
264 int
265 pe__group_xml(pcmk__output_t *out, va_list args)
266 {
267 uint32_t show_opts = va_arg(args, uint32_t);
268 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
269 GList *only_node = va_arg(args, GList *);
270 GList *only_rsc = va_arg(args, GList *);
271
272 const char *desc = NULL;
273
274 int rc = pcmk_rc_no_output;
275
276 bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
277 pcmk__str_star_matches)
278 || ((strchr(rsc->id, ':') != NULL)
279 && pcmk__str_in_list(rsc->id, only_rsc,
280 pcmk__str_star_matches));
281
282 desc = pe__resource_description(rsc, show_opts);
283
284 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
285 return rc;
286 }
287
288 for (GList *gIter = rsc->priv->children;
289 gIter != NULL; gIter = gIter->next) {
290
291 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
292
293 if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
294 continue;
295 }
296
297 if (rc == pcmk_rc_no_output) {
298 char *count = pcmk__itoa(g_list_length(gIter));
299 const char *maintenance = pcmk__flag_text(rsc->flags,
300 pcmk__rsc_maintenance);
301 const char *managed = pcmk__flag_text(rsc->flags,
302 pcmk__rsc_managed);
303 const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc));
304
305 rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_GROUP,
306 PCMK_XA_ID, rsc->id,
307 PCMK_XA_NUMBER_RESOURCES, count,
308 PCMK_XA_MAINTENANCE, maintenance,
309 PCMK_XA_MANAGED, managed,
310 PCMK_XA_DISABLED, disabled,
311 PCMK_XA_DESCRIPTION, desc,
312 NULL);
313 free(count);
314 pcmk__assert(rc == pcmk_rc_ok);
315 }
316
317 out->message(out, (const char *) child_rsc->priv->xml->name,
318 show_opts, child_rsc, only_node, only_rsc);
319 }
320
321 if (rc == pcmk_rc_ok) {
322 pcmk__output_xml_pop_parent(out);
323 }
324
325 return rc;
326 }
327
328 PCMK__OUTPUT_ARGS("group", "uint32_t", "pcmk_resource_t *", "GList *",
329 "GList *")
330 int
331 pe__group_default(pcmk__output_t *out, va_list args)
332 {
333 uint32_t show_opts = va_arg(args, uint32_t);
334 pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *);
335 GList *only_node = va_arg(args, GList *);
336 GList *only_rsc = va_arg(args, GList *);
337
338 const char *desc = NULL;
339 int rc = pcmk_rc_no_output;
340
341 bool parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
342 pcmk__str_star_matches)
343 || ((strchr(rsc->id, ':') != NULL)
344 && pcmk__str_in_list(rsc->id, only_rsc,
345 pcmk__str_star_matches));
346
347 const bool active = rsc->priv->fns->active(rsc, true);
348 const bool partially_active = rsc->priv->fns->active(rsc, false);
349 const bool count_inactive = !active && partially_active;
350
351 desc = pe__resource_description(rsc, show_opts);
352
353 if (rsc->priv->fns->is_filtered(rsc, only_rsc, true)) {
354 return rc;
355 }
356
357 if (pcmk__is_set(show_opts, pcmk_show_brief)) {
358 GList *rscs = pe__filter_rsc_list(rsc->priv->children, only_rsc);
359
360 if (rscs != NULL) {
361 group_header(out, &rc, rsc,
362 (count_inactive? inactive_resources(rsc) : 0),
363 pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
364 desc);
365 pe__rscs_brief_output(out, rscs, show_opts | pcmk_show_inactive_rscs);
366
367 rc = pcmk_rc_ok;
368 g_list_free(rscs);
369 }
370
371 } else {
372 for (GList *gIter = rsc->priv->children;
373 gIter != NULL; gIter = gIter->next) {
374 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
375
376 if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) {
377 continue;
378 }
379
380 group_header(out, &rc, rsc,
381 (count_inactive? inactive_resources(rsc) : 0),
382 pcmk__is_set(show_opts, pcmk_show_inactive_rscs),
383 desc);
384 out->message(out, (const char *) child_rsc->priv->xml->name,
385 show_opts, child_rsc, only_node, only_rsc);
386 }
387 }
388
389 PCMK__OUTPUT_LIST_FOOTER(out, rc);
390
391 return rc;
392 }
393
394 void
395 group_free(pcmk_resource_t * rsc)
396 {
397 CRM_CHECK(rsc != NULL, return);
398
399 pcmk__rsc_trace(rsc, "Freeing group %s, starting with child list", rsc->id);
400 g_list_free_full(rsc->priv->children, pcmk__free_resource);
401 common_free(rsc);
402 }
403
404 enum rsc_role_e
405 group_resource_state(const pcmk_resource_t *rsc, bool current)
406 {
407 enum rsc_role_e group_role = pcmk_role_unknown;
408
409 for (GList *gIter = rsc->priv->children;
410 gIter != NULL; gIter = gIter->next) {
411
412 pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data;
413 enum rsc_role_e role = child_rsc->priv->fns->state(child_rsc,
414 current);
415
416 if (role > group_role) {
417 group_role = role;
418 }
419 }
420
421 pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(group_role));
422 return group_role;
423 }
424
425 bool
426 pe__group_is_filtered(const pcmk_resource_t *rsc, const GList *only_rsc,
427 bool check_parent)
428 {
429 if (check_parent) {
430 const pcmk_resource_t *parent = pe__const_top_resource(rsc, false);
431
432 if (pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
433 pcmk__str_star_matches)) {
434 return false;
435 }
436 }
437
438 if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
439 pcmk__str_star_matches)) {
440 return false;
441 }
442
443 if ((strchr(rsc->id, ':') != NULL)
444 && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) {
445 return false;
446 }
447
448 for (const GList *iter = rsc->priv->children; iter != NULL;
449 iter = iter->next) {
450
451 const pcmk_resource_t *child_rsc = iter->data;
452
453 if (!child_rsc->priv->fns->is_filtered(child_rsc, only_rsc, false)) {
454 return false;
455 }
456 }
457
458 return true;
459 }
460
461 /*!
462 * \internal
463 * \brief Get maximum group resource instances per node
464 *
465 * \param[in] rsc Group resource to check
466 *
467 * \return Maximum number of \p rsc instances that can be active on one node
468 */
469 unsigned int
470 pe__group_max_per_node(const pcmk_resource_t *rsc)
471 {
472 pcmk__assert(pcmk__is_group(rsc));
473 return 1U;
474 }
475