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 General Public License version 2
7 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <sys/param.h>
13 #include <sys/types.h>
14 #include <stdbool.h>
15 #include <regex.h>
16 #include <glib.h>
17
18 #include <crm/crm.h>
19 #include <crm/cib.h>
20 #include <crm/common/scheduler.h>
21 #include <crm/common/xml.h>
22 #include <crm/common/iso8601.h>
23 #include <crm/pengine/status.h>
24 #include <crm/pengine/internal.h>
25 #include <pacemaker-internal.h>
26 #include "libpacemaker_private.h"
27
28 /*!
29 * \internal
30 * \brief Unpack constraints from XML
31 *
32 * Given scheduler data, unpack all constraints from its input XML into
33 * data structures.
34 *
35 * \param[in,out] scheduler Scheduler data
36 */
37 void
38 pcmk__unpack_constraints(pcmk_scheduler_t *scheduler)
39 {
40 xmlNode *xml_constraints = pcmk_find_cib_element(scheduler->input,
41 PCMK_XE_CONSTRAINTS);
42
43 for (xmlNode *xml_obj = pcmk__xe_first_child(xml_constraints, NULL, NULL,
44 NULL);
45 xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj, NULL)) {
46
47 const char *id = pcmk__xe_get(xml_obj, PCMK_XA_ID);
48 const char *tag = (const char *) xml_obj->name;
49
50 if (id == NULL) {
51 pcmk__config_err("Ignoring <%s> constraint without "
52 PCMK_XA_ID, tag);
53 continue;
54 }
55
56 pcmk__trace("Unpacking %s constraint '%s'", tag, id);
57
58 if (pcmk__str_eq(PCMK_XE_RSC_ORDER, tag, pcmk__str_none)) {
59 pcmk__unpack_ordering(xml_obj, scheduler);
60
61 } else if (pcmk__str_eq(PCMK_XE_RSC_COLOCATION, tag, pcmk__str_none)) {
62 pcmk__unpack_colocation(xml_obj, scheduler);
63
64 } else if (pcmk__str_eq(PCMK_XE_RSC_LOCATION, tag, pcmk__str_none)) {
65 pcmk__unpack_location(xml_obj, scheduler);
66
67 } else if (pcmk__str_eq(PCMK_XE_RSC_TICKET, tag, pcmk__str_none)) {
68 pcmk__unpack_rsc_ticket(xml_obj, scheduler);
69
70 } else {
71 pcmk__config_err("Unsupported constraint type: %s", tag);
72 }
73 }
74 }
75
76 pcmk_resource_t *
77 pcmk__find_constraint_resource(GList *rsc_list, const char *id)
78 {
79 if (id == NULL) {
80 return NULL;
81 }
82 for (GList *iter = rsc_list; iter != NULL; iter = iter->next) {
83 pcmk_resource_t *parent = iter->data;
84 pcmk_resource_t *match = NULL;
85
86 match = parent->priv->fns->find_rsc(parent, id, NULL,
87 pcmk_rsc_match_history);
88 if (match != NULL) {
89 if (!pcmk__str_eq(match->id, id, pcmk__str_none)) {
90 /* We found an instance of a clone instead */
91 match = uber_parent(match);
92 pcmk__debug("Found %s for %s", match->id, id);
93 }
94 return match;
95 }
96 }
97 pcmk__trace("No match for %s", id);
98 return NULL;
99 }
100
101 /*!
102 * \internal
103 * \brief Check whether an ID references a resource tag
104 *
105 * \param[in] scheduler Scheduler data
106 * \param[in] id Tag ID to search for
107 * \param[out] tag Where to store tag, if found
108 *
109 * \return true if ID refers to a tagged resource or resource set template,
110 * otherwise false
111 */
112 static bool
113 find_constraint_tag(const pcmk_scheduler_t *scheduler, const char *id,
114 pcmk__idref_t **tag)
115 {
116 *tag = NULL;
117
118 // Check whether id refers to a resource set template
119 if (g_hash_table_lookup_extended(scheduler->priv->templates, id,
120 NULL, (gpointer *) tag)) {
121 if (*tag == NULL) {
122 pcmk__notice("No resource is derived from template '%s'", id);
123 return false;
124 }
125 return true;
126 }
127
128 // If not, check whether id refers to a tag
129 if (g_hash_table_lookup_extended(scheduler->priv->tags, id,
130 NULL, (gpointer *) tag)) {
131 if (*tag == NULL) {
132 pcmk__notice("No resource is tagged with '%s'", id);
133 return false;
134 }
135 return true;
136 }
137
138 pcmk__config_warn("No resource, template, or tag named '%s'", id);
139 return false;
140 }
141
142 /*!
143 * \internal
144 * \brief Parse a role attribute from a constraint
145 *
146 * This is like pcmk_parse_role() except that started is treated as
147 * pcmk_role_unknown (indicating any role), and the return value is
148 * pcmk_rc_unpack_error for invalid specifications.
149 *
150 * \param[in] id ID of constraint being parsed (for logging only)
151 * \param[in] role_spec Role specification
152 * \param[in] role Where to store parsed role
153 *
154 * \return Standard Pacemaker return code
155 */
156 int
157 pcmk__parse_constraint_role(const char *id, const char *role_spec,
158 enum rsc_role_e *role)
159 {
160 *role = pcmk_parse_role(role_spec);
161 switch (*role) {
162 case pcmk_role_unknown:
163 if (role_spec != NULL) {
164 pcmk__config_err("Ignoring constraint %s: Invalid role '%s'",
165 id, role_spec);
166 return pcmk_rc_unpack_error;
167 }
168 break;
169
170 case pcmk_role_started:
171 *role = pcmk_role_unknown;
172 break;
173
174 default:
175 break;
176 }
177 return pcmk_rc_ok;
178 }
179
180 /*!
181 * \brief
182 * \internal Check whether an ID refers to a valid resource or tag
183 *
184 * \param[in] scheduler Scheduler data
185 * \param[in] id ID to search for
186 * \param[out] rsc Where to store resource, if found
187 * (or NULL to skip searching resources)
188 * \param[out] tag Where to store tag, if found
189 * (or NULL to skip searching tags)
190 *
191 * \return true if id refers to a resource (possibly indirectly via a tag)
192 */
193 bool
194 pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler, const char *id,
195 pcmk_resource_t **rsc, pcmk__idref_t **tag)
196 {
197 if (rsc != NULL) {
198 *rsc = pcmk__find_constraint_resource(scheduler->priv->resources, id);
199 if (*rsc != NULL) {
200 return true;
201 }
202 }
203
204 if ((tag != NULL) && find_constraint_tag(scheduler, id, tag)) {
205 return true;
206 }
207
208 return false;
209 }
210
211 /*!
212 * \internal
213 * \brief Replace any resource tags with equivalent \C PCMK_XE_RESOURCE_REF
214 * entries
215 *
216 * If a given constraint has resource sets, check each set for
217 * \c PCMK_XE_RESOURCE_REF entries that list tags rather than resource IDs, and
218 * replace any found with \c PCMK_XE_RESOURCE_REF entries for the corresponding
219 * resource IDs.
220 *
221 * \param[in,out] xml_obj Constraint XML
222 * \param[in] scheduler Scheduler data
223 *
224 * \return Equivalent XML with resource tags replaced (or NULL if none)
225 * \note It is the caller's responsibility to free the return value with
226 * \c pcmk__xml_free().
227 */
228 xmlNode *
229 pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pcmk_scheduler_t *scheduler)
230 {
231 xmlNode *new_xml = NULL;
232 bool any_refs = false;
233
234 // Short-circuit if there are no sets
|
(1) Event path: |
Condition "pcmk__xe_first_child(xml_obj, "resource_set", NULL, NULL) == NULL", taking false branch. |
235 if (pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL,
236 NULL) == NULL) {
237 return NULL;
238 }
239
240 new_xml = pcmk__xml_copy(NULL, xml_obj);
241
|
(2) Event path: |
Condition "set != NULL", taking true branch. |
|
(10) Event path: |
Condition "set != NULL", taking false branch. |
242 for (xmlNode *set = pcmk__xe_first_child(new_xml, PCMK_XE_RESOURCE_SET,
243 NULL, NULL);
244 set != NULL; set = pcmk__xe_next(set, PCMK_XE_RESOURCE_SET)) {
245
246 GList *tag_refs = NULL;
247 GList *iter = NULL;
248
|
(3) Event path: |
Condition "xml_rsc != NULL", taking true branch. |
|
(7) Event path: |
Condition "xml_rsc != NULL", taking false branch. |
249 for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
250 NULL, NULL);
251 xml_rsc != NULL;
252 xml_rsc = pcmk__xe_next(xml_rsc, PCMK_XE_RESOURCE_REF)) {
253
254 pcmk_resource_t *rsc = NULL;
255 pcmk__idref_t *tag = NULL;
256
|
(4) Event path: |
Condition "!pcmk__valid_resource_or_tag(scheduler, pcmk__xe_id(xml_rsc), &rsc, &tag)", taking false branch. |
257 if (!pcmk__valid_resource_or_tag(scheduler, pcmk__xe_id(xml_rsc),
258 &rsc, &tag)) {
259 pcmk__config_err("Ignoring resource sets for constraint '%s' "
260 "because '%s' is not a valid resource or tag",
261 pcmk__xe_id(xml_obj), pcmk__xe_id(xml_rsc));
262 pcmk__xml_free(new_xml);
263 return NULL;
264
|
(5) Event path: |
Condition "rsc", taking true branch. |
265 } else if (rsc) {
|
(6) Event path: |
Continuing loop. |
266 continue;
267
268 } else if (tag) {
269 /* PCMK_XE_RESOURCE_REF under PCMK_XE_RESOURCE_SET references
270 * template or tag
271 */
272 xmlNode *last_ref = xml_rsc;
273
274 /* For example, given the original XML:
275 *
276 * <resource_set id="tag1-colocation-0" sequential="true">
277 * <resource_ref id="rsc1"/>
278 * <resource_ref id="tag1"/>
279 * <resource_ref id="rsc4"/>
280 * </resource_set>
281 *
282 * If rsc2 and rsc3 are tagged with tag1, we add them after it:
283 *
284 * <resource_set id="tag1-colocation-0" sequential="true">
285 * <resource_ref id="rsc1"/>
286 * <resource_ref id="tag1"/>
287 * <resource_ref id="rsc2"/>
288 * <resource_ref id="rsc3"/>
289 * <resource_ref id="rsc4"/>
290 * </resource_set>
291 */
292
293 for (iter = tag->refs; iter != NULL; iter = iter->next) {
294 const char *ref_id = iter->data;
295 xmlNode *new_ref = pcmk__xe_create(set,
296 PCMK_XE_RESOURCE_REF);
297
298 pcmk__xe_set(new_ref, PCMK_XA_ID, ref_id);
299 xmlAddNextSibling(last_ref, new_ref);
300
301 last_ref = new_ref;
302 }
303
304 any_refs = true;
305
306 /* Freeing the resource_ref now would break the XML child
307 * iteration, so just remember it for freeing later.
308 */
309 tag_refs = g_list_append(tag_refs, xml_rsc);
310 }
311 }
312
313 /* Now free '<resource_ref id="tag1"/>', and finally get:
314
315 <resource_set id="tag1-colocation-0" sequential="true">
316 <resource_ref id="rsc1"/>
317 <resource_ref id="rsc2"/>
318 <resource_ref id="rsc3"/>
319 <resource_ref id="rsc4"/>
320 </resource_set>
321
322 */
|
(8) Event path: |
Condition "iter != NULL", taking false branch. |
323 for (iter = tag_refs; iter != NULL; iter = iter->next) {
324 xmlNode *tag_ref = iter->data;
325
326 pcmk__xml_free(tag_ref);
327 }
328 g_list_free(tag_refs);
|
(9) Event path: |
Jumping back to the beginning of the loop. |
329 }
330
|
(11) Event path: |
Condition "!any_refs", taking true branch. |
331 if (!any_refs) {
|
CID (unavailable; MK=d87191d8ef1350a91c240942edc91724) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(12) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(13) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
332 g_clear_pointer(&new_xml, pcmk__xml_free);
333 }
334
335 return new_xml;
336 }
337
338 /*!
339 * \internal
340 * \brief Convert a tag into a resource set of tagged resources
341 *
342 * \param[in,out] xml_obj Constraint XML
343 * \param[out] rsc_set Where to store resource set XML
344 * \param[in] attr Name of XML attribute with resource or tag ID
345 * \param[in] convert_rsc If true, convert to set even if \p attr
346 * references a resource
347 * \param[in] scheduler Scheduler data
348 */
349 bool
350 pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
351 bool convert_rsc, const pcmk_scheduler_t *scheduler)
352 {
353 const char *cons_id = NULL;
354 const char *id = NULL;
355
356 pcmk_resource_t *rsc = NULL;
357 pcmk__idref_t *tag = NULL;
358
359 *rsc_set = NULL;
360
361 CRM_CHECK((xml_obj != NULL) && (attr != NULL), return false);
362
363 cons_id = pcmk__xe_id(xml_obj);
364 if (cons_id == NULL) {
365 pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
366 xml_obj->name);
367 return false;
368 }
369
370 id = pcmk__xe_get(xml_obj, attr);
371 if (id == NULL) {
372 return true;
373 }
374
375 if (!pcmk__valid_resource_or_tag(scheduler, id, &rsc, &tag)) {
376 pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
377 "valid resource or tag", cons_id, id);
378 return false;
379
380 } else if (tag) {
381 /* The "attr" attribute (for a resource in a constraint) specifies a
382 * template or tag. Add the corresponding PCMK_XE_RESOURCE_SET
383 * containing the resources derived from or tagged with it.
384 */
385 *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
386 pcmk__xe_set(*rsc_set, PCMK_XA_ID, id);
387
388 for (GList *iter = tag->refs; iter != NULL; iter = iter->next) {
389 const char *obj_ref = iter->data;
390 xmlNode *rsc_ref = NULL;
391
392 rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
393 pcmk__xe_set(rsc_ref, PCMK_XA_ID, obj_ref);
394 }
395
396 // Set PCMK_XA_SEQUENTIAL=PCMK_VALUE_FALSE for the PCMK_XE_RESOURCE_SET
397 pcmk__xe_set_bool(*rsc_set, PCMK_XA_SEQUENTIAL, false);
398
399 } else if ((rsc != NULL) && convert_rsc) {
400 /* Even if a regular resource is referenced by "attr", convert it into a
401 * PCMK_XE_RESOURCE_SET, because the other resource reference in the
402 * constraint could be a template or tag.
403 */
404 xmlNode *rsc_ref = NULL;
405
406 *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
407 pcmk__xe_set(*rsc_set, PCMK_XA_ID, id);
408
409 rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
410 pcmk__xe_set(rsc_ref, PCMK_XA_ID, id);
411
412 } else {
413 return true;
414 }
415
416 /* Remove the "attr" attribute referencing the template/tag */
417 if (*rsc_set != NULL) {
418 pcmk__xe_remove_attr(xml_obj, attr);
419 }
420
421 return true;
422 }
423
424 /*!
425 * \internal
426 * \brief Create constraints inherent to resource types
427 *
428 * \param[in,out] scheduler Scheduler data
429 */
430 void
431 pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler)
432 {
433 pcmk__trace("Create internal constraints");
434 for (GList *iter = scheduler->priv->resources;
435 iter != NULL; iter = iter->next) {
436
437 pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
438
439 rsc->priv->cmds->internal_constraints(rsc);
440 }
441 }
442