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 <stdbool.h>
13 : #include <glib.h>
14 :
15 : #include <crm/crm.h>
16 : #include <crm/common/scheduler_internal.h>
17 : #include <crm/pengine/status.h>
18 : #include <pacemaker-internal.h>
19 :
20 : #include "libpacemaker_private.h"
21 :
22 : enum loss_ticket_policy {
23 : loss_ticket_stop,
24 : loss_ticket_demote,
25 : loss_ticket_fence,
26 : loss_ticket_freeze
27 : };
28 :
29 : typedef struct {
30 : const char *id;
31 : pcmk_resource_t *rsc;
32 : pcmk_ticket_t *ticket;
33 : enum loss_ticket_policy loss_policy;
34 : int role;
35 : } rsc_ticket_t;
36 :
37 : /*!
38 : * \brief Check whether a ticket constraint matches a resource by role
39 : *
40 : * \param[in] rsc_ticket Ticket constraint
41 : * \param[in] rsc Resource to compare with ticket
42 : *
43 : * \param[in] true if constraint has no role or resource's role matches
44 : * constraint's, otherwise false
45 : */
46 : static bool
47 0 : ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
48 : {
49 0 : if ((rsc_ticket->role == pcmk_role_unknown)
50 0 : || (rsc_ticket->role == rsc->role)) {
51 0 : return true;
52 : }
53 0 : pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
54 : pcmk_role_text(rsc_ticket->role));
55 0 : return false;
56 : }
57 :
58 : /*!
59 : * \brief Create location constraints and fencing as needed for a ticket
60 : *
61 : * \param[in,out] rsc Resource affected by ticket
62 : * \param[in] rsc_ticket Ticket
63 : */
64 : static void
65 0 : constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
66 : {
67 0 : GList *iter = NULL;
68 :
69 0 : CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
70 :
71 0 : if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
72 0 : return;
73 : }
74 :
75 0 : if (rsc->children) {
76 0 : pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
77 0 : for (iter = rsc->children; iter != NULL; iter = iter->next) {
78 0 : constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket);
79 : }
80 0 : return;
81 : }
82 :
83 0 : pcmk__rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
84 : rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
85 : pcmk_role_text(rsc_ticket->role));
86 :
87 0 : if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
88 :
89 0 : switch (rsc_ticket->loss_policy) {
90 0 : case loss_ticket_stop:
91 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
92 : "__loss_of_ticket__", rsc->cluster);
93 0 : break;
94 :
95 0 : case loss_ticket_demote:
96 : // Promotion score will be set to -INFINITY in promotion_order()
97 0 : if (rsc_ticket->role != pcmk_role_promoted) {
98 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
99 : "__loss_of_ticket__", rsc->cluster);
100 : }
101 0 : break;
102 :
103 0 : case loss_ticket_fence:
104 0 : if (!ticket_role_matches(rsc, rsc_ticket)) {
105 0 : return;
106 : }
107 :
108 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
109 : "__loss_of_ticket__", rsc->cluster);
110 :
111 0 : for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
112 0 : pe_fence_node(rsc->cluster, (pcmk_node_t *) iter->data,
113 : "deadman ticket was lost", FALSE);
114 : }
115 0 : break;
116 :
117 0 : case loss_ticket_freeze:
118 0 : if (!ticket_role_matches(rsc, rsc_ticket)) {
119 0 : return;
120 : }
121 0 : if (rsc->running_on != NULL) {
122 0 : pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
123 0 : pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
124 : }
125 0 : break;
126 : }
127 :
128 0 : } else if (!rsc_ticket->ticket->granted) {
129 :
130 0 : if ((rsc_ticket->role != pcmk_role_promoted)
131 0 : || (rsc_ticket->loss_policy == loss_ticket_stop)) {
132 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
133 : "__no_ticket__", rsc->cluster);
134 : }
135 :
136 0 : } else if (rsc_ticket->ticket->standby) {
137 :
138 0 : if ((rsc_ticket->role != pcmk_role_promoted)
139 0 : || (rsc_ticket->loss_policy == loss_ticket_stop)) {
140 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
141 : "__ticket_standby__", rsc->cluster);
142 : }
143 : }
144 : }
145 :
146 : static void
147 0 : rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk_ticket_t *ticket,
148 : const char *state, const char *loss_policy)
149 : {
150 0 : rsc_ticket_t *new_rsc_ticket = NULL;
151 :
152 0 : if (rsc == NULL) {
153 0 : pcmk__config_err("Ignoring ticket '%s' because resource "
154 : "does not exist", id);
155 0 : return;
156 : }
157 :
158 0 : new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
159 0 : if (new_rsc_ticket == NULL) {
160 0 : return;
161 : }
162 :
163 0 : if (pcmk__str_eq(state, PCMK_ROLE_STARTED,
164 : pcmk__str_null_matches|pcmk__str_casei)) {
165 0 : state = PCMK__ROLE_UNKNOWN;
166 : }
167 :
168 0 : new_rsc_ticket->id = id;
169 0 : new_rsc_ticket->ticket = ticket;
170 0 : new_rsc_ticket->rsc = rsc;
171 0 : new_rsc_ticket->role = pcmk_parse_role(state);
172 :
173 0 : if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) {
174 0 : if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
175 0 : new_rsc_ticket->loss_policy = loss_ticket_fence;
176 : } else {
177 0 : pcmk__config_err("Resetting '" PCMK_XA_LOSS_POLICY "' "
178 : "for ticket '%s' to '" PCMK_VALUE_STOP "' "
179 : "because fencing is not configured", ticket->id);
180 0 : loss_policy = PCMK_VALUE_STOP;
181 : }
182 : }
183 :
184 0 : if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
185 0 : crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
186 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
187 : pcmk_role_text(new_rsc_ticket->role));
188 :
189 0 : } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
190 0 : crm_debug("On loss of ticket '%s': Freeze %s (%s)",
191 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
192 : pcmk_role_text(new_rsc_ticket->role));
193 0 : new_rsc_ticket->loss_policy = loss_ticket_freeze;
194 :
195 0 : } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
196 0 : crm_debug("On loss of ticket '%s': Demote %s (%s)",
197 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
198 : pcmk_role_text(new_rsc_ticket->role));
199 0 : new_rsc_ticket->loss_policy = loss_ticket_demote;
200 :
201 0 : } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) {
202 0 : crm_debug("On loss of ticket '%s': Stop %s (%s)",
203 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
204 : pcmk_role_text(new_rsc_ticket->role));
205 0 : new_rsc_ticket->loss_policy = loss_ticket_stop;
206 :
207 : } else {
208 0 : if (new_rsc_ticket->role == pcmk_role_promoted) {
209 0 : crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
210 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
211 : pcmk_role_text(new_rsc_ticket->role));
212 0 : new_rsc_ticket->loss_policy = loss_ticket_demote;
213 :
214 : } else {
215 0 : crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
216 : new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
217 : pcmk_role_text(new_rsc_ticket->role));
218 0 : new_rsc_ticket->loss_policy = loss_ticket_stop;
219 : }
220 : }
221 :
222 0 : pcmk__rsc_trace(rsc, "%s (%s) ==> %s",
223 : rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id);
224 :
225 0 : rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
226 :
227 0 : rsc->cluster->ticket_constraints = g_list_append(
228 0 : rsc->cluster->ticket_constraints, new_rsc_ticket);
229 :
230 0 : if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
231 0 : constraints_for_ticket(rsc, new_rsc_ticket);
232 : }
233 : }
234 :
235 : // \return Standard Pacemaker return code
236 : static int
237 0 : unpack_rsc_ticket_set(xmlNode *set, pcmk_ticket_t *ticket,
238 : const char *loss_policy, pcmk_scheduler_t *scheduler)
239 : {
240 0 : const char *set_id = NULL;
241 0 : const char *role = NULL;
242 :
243 0 : CRM_CHECK(set != NULL, return EINVAL);
244 0 : CRM_CHECK(ticket != NULL, return EINVAL);
245 :
246 0 : set_id = pcmk__xe_id(set);
247 0 : if (set_id == NULL) {
248 0 : pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without "
249 : PCMK_XA_ID);
250 0 : return pcmk_rc_unpack_error;
251 : }
252 :
253 0 : role = crm_element_value(set, PCMK_XA_ROLE);
254 :
255 0 : for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
256 : NULL, NULL);
257 0 : xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
258 :
259 0 : pcmk_resource_t *resource = NULL;
260 :
261 0 : resource = pcmk__find_constraint_resource(scheduler->resources,
262 : pcmk__xe_id(xml_rsc));
263 0 : if (resource == NULL) {
264 0 : pcmk__config_err("%s: No resource found for %s",
265 : set_id, pcmk__xe_id(xml_rsc));
266 0 : return pcmk_rc_unpack_error;
267 : }
268 0 : pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
269 : resource->id, ticket->id);
270 0 : rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
271 : }
272 :
273 0 : return pcmk_rc_ok;
274 : }
275 :
276 : static void
277 0 : unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
278 : {
279 0 : const char *id = NULL;
280 0 : const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
281 0 : const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
282 :
283 0 : pcmk_ticket_t *ticket = NULL;
284 :
285 0 : const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
286 0 : const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
287 :
288 : // @COMPAT: Deprecated since 2.1.5
289 0 : const char *instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
290 :
291 0 : pcmk_resource_t *rsc = NULL;
292 :
293 0 : if (instance != NULL) {
294 0 : pcmk__warn_once(pcmk__wo_coloc_inst,
295 : "Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
296 : "and will be removed in a future release");
297 : }
298 :
299 0 : CRM_CHECK(xml_obj != NULL, return);
300 :
301 0 : id = pcmk__xe_id(xml_obj);
302 0 : if (id == NULL) {
303 0 : pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
304 : xml_obj->name);
305 0 : return;
306 : }
307 :
308 0 : if (ticket_str == NULL) {
309 0 : pcmk__config_err("Ignoring constraint '%s' without ticket specified",
310 : id);
311 0 : return;
312 : } else {
313 0 : ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
314 : }
315 :
316 0 : if (ticket == NULL) {
317 0 : pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
318 : "does not exist", id, ticket_str);
319 0 : return;
320 : }
321 :
322 0 : if (rsc_id == NULL) {
323 0 : pcmk__config_err("Ignoring constraint '%s' without resource", id);
324 0 : return;
325 : } else {
326 0 : rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
327 : }
328 :
329 0 : if (rsc == NULL) {
330 0 : pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
331 : "does not exist", id, rsc_id);
332 0 : return;
333 :
334 0 : } else if ((instance != NULL) && !pcmk__is_clone(rsc)) {
335 0 : pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
336 : "is not a clone but instance '%s' was requested",
337 : id, rsc_id, instance);
338 0 : return;
339 : }
340 :
341 0 : if (instance != NULL) {
342 0 : rsc = find_clone_instance(rsc, instance);
343 0 : if (rsc == NULL) {
344 0 : pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
345 : "does not have an instance '%s'",
346 : "'%s'", id, rsc_id, instance);
347 0 : return;
348 : }
349 : }
350 :
351 0 : rsc_ticket_new(id, rsc, ticket, state, loss_policy);
352 : }
353 :
354 : // \return Standard Pacemaker return code
355 : static int
356 0 : unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
357 : pcmk_scheduler_t *scheduler)
358 : {
359 0 : const char *id = NULL;
360 0 : const char *rsc_id = NULL;
361 0 : const char *state = NULL;
362 :
363 0 : pcmk_resource_t *rsc = NULL;
364 0 : pcmk_tag_t *tag = NULL;
365 :
366 0 : xmlNode *rsc_set = NULL;
367 :
368 0 : *expanded_xml = NULL;
369 :
370 0 : CRM_CHECK(xml_obj != NULL, return EINVAL);
371 :
372 0 : id = pcmk__xe_id(xml_obj);
373 0 : if (id == NULL) {
374 0 : pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
375 : xml_obj->name);
376 0 : return pcmk_rc_unpack_error;
377 : }
378 :
379 : // Check whether there are any resource sets with template or tag references
380 0 : *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
381 0 : if (*expanded_xml != NULL) {
382 0 : crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
383 0 : return pcmk_rc_ok;
384 : }
385 :
386 0 : rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
387 0 : if (rsc_id == NULL) {
388 0 : return pcmk_rc_ok;
389 : }
390 :
391 0 : if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
392 0 : pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
393 : "valid resource or tag", id, rsc_id);
394 0 : return pcmk_rc_unpack_error;
395 :
396 0 : } else if (rsc != NULL) {
397 : // No template or tag is referenced
398 0 : return pcmk_rc_ok;
399 : }
400 :
401 0 : state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
402 :
403 0 : *expanded_xml = pcmk__xml_copy(NULL, xml_obj);
404 :
405 : /* Convert any template or tag reference in "rsc" into ticket
406 : * PCMK_XE_RESOURCE_SET
407 : */
408 0 : if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false,
409 : scheduler)) {
410 0 : free_xml(*expanded_xml);
411 0 : *expanded_xml = NULL;
412 0 : return pcmk_rc_unpack_error;
413 : }
414 :
415 0 : if (rsc_set != NULL) {
416 0 : if (state != NULL) {
417 : /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a
418 : * PCMK_XA_ROLE attribute
419 : */
420 0 : crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
421 0 : pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
422 : }
423 :
424 : } else {
425 0 : free_xml(*expanded_xml);
426 0 : *expanded_xml = NULL;
427 : }
428 :
429 0 : return pcmk_rc_ok;
430 : }
431 :
432 : void
433 0 : pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
434 : {
435 0 : xmlNode *set = NULL;
436 0 : bool any_sets = false;
437 :
438 0 : const char *id = NULL;
439 0 : const char *ticket_str = NULL;
440 :
441 0 : pcmk_ticket_t *ticket = NULL;
442 :
443 0 : xmlNode *orig_xml = NULL;
444 0 : xmlNode *expanded_xml = NULL;
445 :
446 0 : CRM_CHECK(xml_obj != NULL, return);
447 :
448 0 : id = pcmk__xe_id(xml_obj);
449 0 : if (id == NULL) {
450 0 : pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
451 : xml_obj->name);
452 0 : return;
453 : }
454 :
455 0 : if (scheduler->tickets == NULL) {
456 0 : scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
457 : }
458 :
459 0 : ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
460 0 : if (ticket_str == NULL) {
461 0 : pcmk__config_err("Ignoring constraint '%s' without ticket", id);
462 0 : return;
463 : } else {
464 0 : ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
465 : }
466 :
467 0 : if (ticket == NULL) {
468 0 : ticket = ticket_new(ticket_str, scheduler);
469 0 : if (ticket == NULL) {
470 0 : return;
471 : }
472 : }
473 :
474 0 : if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
475 : scheduler) != pcmk_rc_ok) {
476 0 : return;
477 : }
478 0 : if (expanded_xml != NULL) {
479 0 : orig_xml = xml_obj;
480 0 : xml_obj = expanded_xml;
481 : }
482 :
483 0 : for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
484 0 : set != NULL; set = pcmk__xe_next_same(set)) {
485 :
486 0 : const char *loss_policy = NULL;
487 :
488 0 : any_sets = true;
489 0 : set = expand_idref(set, scheduler->input);
490 0 : loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
491 :
492 0 : if ((set == NULL) // Configuration error, message already logged
493 0 : || (unpack_rsc_ticket_set(set, ticket, loss_policy,
494 : scheduler) != pcmk_rc_ok)) {
495 0 : if (expanded_xml != NULL) {
496 0 : free_xml(expanded_xml);
497 : }
498 0 : return;
499 : }
500 : }
501 :
502 0 : if (expanded_xml) {
503 0 : free_xml(expanded_xml);
504 0 : xml_obj = orig_xml;
505 : }
506 :
507 0 : if (!any_sets) {
508 0 : unpack_simple_rsc_ticket(xml_obj, scheduler);
509 : }
510 : }
511 :
512 : /*!
513 : * \internal
514 : * \brief Ban resource from a node if it doesn't have a promotion ticket
515 : *
516 : * If a resource has tickets for the promoted role, and the ticket is either not
517 : * granted or set to standby, then ban the resource from all nodes.
518 : *
519 : * \param[in,out] rsc Resource to check
520 : */
521 : void
522 0 : pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
523 : {
524 0 : for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
525 0 : rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
526 :
527 0 : if ((rsc_ticket->role == pcmk_role_promoted)
528 0 : && (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
529 0 : resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
530 : "__stateful_without_ticket__", rsc->cluster);
531 : }
532 : }
533 0 : }
|