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