pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
pcmk_sched_tickets.c
Go to the documentation of this file.
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.h>
18 #include <crm/pengine/status.h>
19 #include <pacemaker-internal.h>
20 
21 #include "libpacemaker_private.h"
22 
28 };
29 
30 typedef struct {
31  const char *id;
32  pcmk_resource_t *rsc;
33  pcmk__ticket_t *ticket;
34  enum loss_ticket_policy loss_policy;
35  int role;
36 } rsc_ticket_t;
37 
47 static bool
48 ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
49 {
50  if ((rsc_ticket->role == pcmk_role_unknown)
51  || (rsc_ticket->role == rsc->priv->orig_role)) {
52  return true;
53  }
54  pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
55  pcmk_role_text(rsc_ticket->role));
56  return false;
57 }
58 
65 static void
66 constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
67 {
68  GList *iter = NULL;
69 
70  CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
71 
72  if (pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted)
73  && !pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_standby)) {
74  return;
75  }
76 
77  if (rsc->priv->children != NULL) {
78  pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
79  for (iter = rsc->priv->children; iter != NULL; iter = iter->next) {
80  constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket);
81  }
82  return;
83  }
84 
85  pcmk__rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
86  rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
87  pcmk_role_text(rsc_ticket->role));
88 
89  if (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted)
90  && (rsc->priv->active_nodes != NULL)) {
91 
92  switch (rsc_ticket->loss_policy) {
93  case loss_ticket_stop:
95  "__loss_of_ticket__",
96  rsc->priv->scheduler);
97  break;
98 
99  case loss_ticket_demote:
100  // Promotion score will be set to -INFINITY in promotion_order()
101  if (rsc_ticket->role != pcmk_role_promoted) {
103  "__loss_of_ticket__",
104  rsc->priv->scheduler);
105  }
106  break;
107 
108  case loss_ticket_fence:
109  if (!ticket_role_matches(rsc, rsc_ticket)) {
110  return;
111  }
112 
114  "__loss_of_ticket__",
115  rsc->priv->scheduler);
116 
117  for (iter = rsc->priv->active_nodes;
118  iter != NULL; iter = iter->next) {
119 
121  (pcmk_node_t *) iter->data,
122  "deadman ticket was lost", FALSE);
123  }
124  break;
125 
126  case loss_ticket_freeze:
127  if (!ticket_role_matches(rsc, rsc_ticket)) {
128  return;
129  }
130  if (rsc->priv->active_nodes != NULL) {
133  }
134  break;
135  }
136 
137  } else if (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted)) {
138 
139  if ((rsc_ticket->role != pcmk_role_promoted)
140  || (rsc_ticket->loss_policy == loss_ticket_stop)) {
142  "__no_ticket__", rsc->priv->scheduler);
143  }
144 
145  } else if (pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_standby)) {
146 
147  if ((rsc_ticket->role != pcmk_role_promoted)
148  || (rsc_ticket->loss_policy == loss_ticket_stop)) {
150  "__ticket_standby__", rsc->priv->scheduler);
151  }
152  }
153 }
154 
155 static void
156 rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk__ticket_t *ticket,
157  const char *role_spec, const char *loss_policy)
158 {
159  rsc_ticket_t *new_rsc_ticket = NULL;
160  enum rsc_role_e role = pcmk_role_unknown;
161 
162  if (rsc == NULL) {
163  pcmk__config_err("Ignoring ticket '%s' because resource "
164  "does not exist", id);
165  return;
166  }
167  if (pcmk__parse_constraint_role(id, role_spec, &role) != pcmk_rc_ok) {
168  // Not possible with schema validation enabled (error already logged)
169  return;
170  }
171 
172  new_rsc_ticket = pcmk__assert_alloc(1, sizeof(rsc_ticket_t));
173  new_rsc_ticket->id = id;
174  new_rsc_ticket->ticket = ticket;
175  new_rsc_ticket->rsc = rsc;
176  new_rsc_ticket->role = role;
177 
178  if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) {
179  if (pcmk_is_set(rsc->priv->scheduler->flags,
181  new_rsc_ticket->loss_policy = loss_ticket_fence;
182  } else {
183  pcmk__config_err("Resetting '" PCMK_XA_LOSS_POLICY "' "
184  "for ticket '%s' to '" PCMK_VALUE_STOP "' "
185  "because fencing is not configured", ticket->id);
186  loss_policy = PCMK_VALUE_STOP;
187  }
188  }
189 
190  if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
191  crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
192  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
193  pcmk_role_text(new_rsc_ticket->role));
194 
195  } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
196  crm_debug("On loss of ticket '%s': Freeze %s (%s)",
197  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
198  pcmk_role_text(new_rsc_ticket->role));
199  new_rsc_ticket->loss_policy = loss_ticket_freeze;
200 
201  } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
202  crm_debug("On loss of ticket '%s': Demote %s (%s)",
203  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
204  pcmk_role_text(new_rsc_ticket->role));
205  new_rsc_ticket->loss_policy = loss_ticket_demote;
206 
207  } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) {
208  crm_debug("On loss of ticket '%s': Stop %s (%s)",
209  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
210  pcmk_role_text(new_rsc_ticket->role));
211  new_rsc_ticket->loss_policy = loss_ticket_stop;
212 
213  } else {
214  if (new_rsc_ticket->role == pcmk_role_promoted) {
215  crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
216  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
217  pcmk_role_text(new_rsc_ticket->role));
218  new_rsc_ticket->loss_policy = loss_ticket_demote;
219 
220  } else {
221  crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
222  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
223  pcmk_role_text(new_rsc_ticket->role));
224  new_rsc_ticket->loss_policy = loss_ticket_stop;
225  }
226  }
227 
228  pcmk__rsc_trace(rsc, "%s (%s) ==> %s",
229  rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id);
230 
231  rsc->priv->ticket_constraints =
232  g_list_append(rsc->priv->ticket_constraints, new_rsc_ticket);
233 
234  if (!pcmk_is_set(new_rsc_ticket->ticket->flags, pcmk__ticket_granted)
235  || pcmk_is_set(new_rsc_ticket->ticket->flags, pcmk__ticket_standby)) {
236  constraints_for_ticket(rsc, new_rsc_ticket);
237  }
238 }
239 
240 // \return Standard Pacemaker return code
241 static int
242 unpack_rsc_ticket_set(xmlNode *set, pcmk__ticket_t *ticket,
243  const char *loss_policy, pcmk_scheduler_t *scheduler)
244 {
245  const char *set_id = NULL;
246  const char *role = NULL;
247 
248  CRM_CHECK(set != NULL, return EINVAL);
249  CRM_CHECK(ticket != NULL, return EINVAL);
250 
251  set_id = pcmk__xe_id(set);
252  if (set_id == NULL) {
253  pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without "
254  PCMK_XA_ID);
255  return pcmk_rc_unpack_error;
256  }
257 
258  role = crm_element_value(set, PCMK_XA_ROLE);
259 
260  for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
261  NULL, NULL);
262  xml_rsc != NULL;
263  xml_rsc = pcmk__xe_next(xml_rsc, PCMK_XE_RESOURCE_REF)) {
264 
265  pcmk_resource_t *resource = NULL;
266 
268  pcmk__xe_id(xml_rsc));
269  if (resource == NULL) {
270  pcmk__config_err("%s: No resource found for %s",
271  set_id, pcmk__xe_id(xml_rsc));
272  return pcmk_rc_unpack_error;
273  }
274  pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
275  resource->id, ticket->id);
276  rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
277  }
278 
279  return pcmk_rc_ok;
280 }
281 
282 static void
283 unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
284 {
285  const char *id = NULL;
286  const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
287  const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
288 
289  pcmk__ticket_t *ticket = NULL;
290 
291  const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
292  const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
293 
294  pcmk_resource_t *rsc = NULL;
295 
296  CRM_CHECK(xml_obj != NULL, return);
297 
298  id = pcmk__xe_id(xml_obj);
299  if (id == NULL) {
300  pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
301  xml_obj->name);
302  return;
303  }
304 
305  if (ticket_str == NULL) {
306  pcmk__config_err("Ignoring constraint '%s' without ticket specified",
307  id);
308  return;
309  } else {
310  ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints,
311  ticket_str);
312  }
313 
314  if (ticket == NULL) {
315  pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
316  "does not exist", id, ticket_str);
317  return;
318  }
319 
320  if (rsc_id == NULL) {
321  pcmk__config_err("Ignoring constraint '%s' without resource", id);
322  return;
323  } else {
325  rsc_id);
326  }
327 
328  if (rsc == NULL) {
329  pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
330  "does not exist", id, rsc_id);
331  return;
332  }
333 
334  rsc_ticket_new(id, rsc, ticket, state, loss_policy);
335 }
336 
337 // \return Standard Pacemaker return code
338 static int
339 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
341 {
342  const char *id = NULL;
343  const char *rsc_id = NULL;
344  const char *state = NULL;
345 
346  pcmk_resource_t *rsc = NULL;
347  pcmk__idref_t *tag = NULL;
348 
349  xmlNode *rsc_set = NULL;
350 
351  *expanded_xml = NULL;
352 
353  CRM_CHECK(xml_obj != NULL, return EINVAL);
354 
355  id = pcmk__xe_id(xml_obj);
356  if (id == NULL) {
357  pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
358  xml_obj->name);
359  return pcmk_rc_unpack_error;
360  }
361 
362  // Check whether there are any resource sets with template or tag references
363  *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
364  if (*expanded_xml != NULL) {
365  crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
366  return pcmk_rc_ok;
367  }
368 
369  rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
370  if (rsc_id == NULL) {
371  return pcmk_rc_ok;
372  }
373 
374  if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
375  pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
376  "valid resource or tag", id, rsc_id);
377  return pcmk_rc_unpack_error;
378 
379  } else if (rsc != NULL) {
380  // No template or tag is referenced
381  return pcmk_rc_ok;
382  }
383 
384  state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
385 
386  *expanded_xml = pcmk__xml_copy(NULL, xml_obj);
387 
388  /* Convert any template or tag reference in "rsc" into ticket
389  * PCMK_XE_RESOURCE_SET
390  */
391  if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false,
392  scheduler)) {
393  pcmk__xml_free(*expanded_xml);
394  *expanded_xml = NULL;
395  return pcmk_rc_unpack_error;
396  }
397 
398  if (rsc_set != NULL) {
399  if (state != NULL) {
400  /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a
401  * PCMK_XA_ROLE attribute
402  */
403  crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
404  pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
405  }
406 
407  } else {
408  pcmk__xml_free(*expanded_xml);
409  *expanded_xml = NULL;
410  }
411 
412  return pcmk_rc_ok;
413 }
414 
415 void
417 {
418  xmlNode *set = NULL;
419  bool any_sets = false;
420 
421  const char *id = NULL;
422  const char *ticket_str = NULL;
423 
424  pcmk__ticket_t *ticket = NULL;
425 
426  xmlNode *orig_xml = NULL;
427  xmlNode *expanded_xml = NULL;
428 
429  CRM_CHECK(xml_obj != NULL, return);
430 
431  id = pcmk__xe_id(xml_obj);
432  if (id == NULL) {
433  pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
434  xml_obj->name);
435  return;
436  }
437 
438  if (scheduler->priv->ticket_constraints == NULL) {
441  }
442 
443  ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
444  if (ticket_str == NULL) {
445  pcmk__config_err("Ignoring constraint '%s' without ticket", id);
446  return;
447  } else {
448  ticket = g_hash_table_lookup(scheduler->priv->ticket_constraints,
449  ticket_str);
450  }
451 
452  if (ticket == NULL) {
453  ticket = ticket_new(ticket_str, scheduler);
454  if (ticket == NULL) {
455  return;
456  }
457  }
458 
459  if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
460  scheduler) != pcmk_rc_ok) {
461  return;
462  }
463  if (expanded_xml != NULL) {
464  orig_xml = xml_obj;
465  xml_obj = expanded_xml;
466  }
467 
468  for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
469  set != NULL; set = pcmk__xe_next(set, PCMK_XE_RESOURCE_SET)) {
470 
471  const char *loss_policy = NULL;
472 
473  any_sets = true;
475  loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
476 
477  if ((set == NULL) // Configuration error, message already logged
478  || (unpack_rsc_ticket_set(set, ticket, loss_policy,
479  scheduler) != pcmk_rc_ok)) {
480  if (expanded_xml != NULL) {
481  pcmk__xml_free(expanded_xml);
482  }
483  return;
484  }
485  }
486 
487  if (expanded_xml) {
488  pcmk__xml_free(expanded_xml);
489  xml_obj = orig_xml;
490  }
491 
492  if (!any_sets) {
493  unpack_simple_rsc_ticket(xml_obj, scheduler);
494  }
495 }
496 
506 void
508 {
509  for (GList *item = rsc->priv->ticket_constraints;
510  item != NULL; item = item->next) {
511 
512  rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
513 
514  if ((rsc_ticket->role == pcmk_role_promoted)
515  && (!pcmk_is_set(rsc_ticket->ticket->flags, pcmk__ticket_granted)
516  || pcmk_is_set(rsc_ticket->ticket->flags,
519  "__stateful_without_ticket__",
520  rsc->priv->scheduler);
521  }
522  }
523 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
xmlNode * pcmk__xe_resolve_idref(xmlNode *xml, xmlNode *search)
Definition: xml_idref.c:85
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
A dumping ground.
xmlNode * pcmk__xe_first_child(const xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v)
Definition: xml_element.c:42
const char * pcmk_role_text(enum rsc_role_e role)
Get readable description of a resource role.
Definition: roles.c:23
G_GNUC_INTERNAL bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr, bool convert_rsc, const pcmk_scheduler_t *scheduler)
#define pcmk__rsc_trace(rsc, fmt, args...)
#define PCMK_XE_RESOURCE_REF
Definition: xml_names.h:177
#define pcmk__set_rsc_flags(resource, flags_to_set)
#define pcmk__config_err(fmt...)
pcmk__scheduler_private_t * priv
Definition: scheduler.h:99
Promoted.
Definition: roles.h:39
#define PCMK_XA_RSC
Definition: xml_names.h:388
uint64_t flags
Definition: scheduler.h:89
void pe_fence_node(pcmk_scheduler_t *scheduler, pcmk_node_t *node, const char *reason, bool priority_delay)
Schedule a fence action for a node.
Definition: unpack.c:114
void pcmk__xe_remove_attr(xmlNode *element, const char *name)
Definition: xml_element.c:339
Scheduler API.
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
#define PCMK_VALUE_FENCE
Definition: options.h:154
#define crm_debug(fmt, args...)
Definition: logging.h:370
pcmk_scheduler_t * scheduler
G_GNUC_INTERNAL pcmk_resource_t * pcmk__find_constraint_resource(GList *rsc_list, const char *id)
void resource_location(pcmk_resource_t *rsc, const pcmk_node_t *node, int score, const char *tag, pcmk_scheduler_t *scheduler)
Definition: utils.c:365
loss_ticket_policy
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:80
pcmk__resource_private_t * priv
Definition: resources.h:61
rsc_role_e
Definition: roles.h:34
#define pcmk__clear_rsc_flags(resource, flags_to_clear)
#define PCMK_XA_ID
Definition: xml_names.h:301
G_GNUC_INTERNAL int pcmk__parse_constraint_role(const char *id, const char *role_spec, enum rsc_role_e *role)
uint32_t id
Definition: cpg.c:48
void pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
xmlNode * pcmk__xe_next(const xmlNode *node, const char *element_name)
Definition: xml_element.c:106
Cluster status and scheduling.
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:685
pcmk_scheduler_t * scheduler
#define PCMK_XA_TICKET
Definition: xml_names.h:426
G_GNUC_INTERNAL bool pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler, const char *id, pcmk_resource_t **rsc, pcmk__idref_t **tag)
#define PCMK_XE_RESOURCE_SET
Definition: xml_names.h:178
xmlNode * input
Definition: scheduler.h:81
#define PCMK_XA_RSC_ROLE
Definition: xml_names.h:390
#define PCMK_VALUE_STOP
Definition: options.h:212
void destroy_ticket(gpointer data)
Definition: utils.c:513
#define crm_log_xml_trace(xml, text)
Definition: logging.h:380
pcmk__ticket_t * ticket_new(const char *ticket_id, pcmk_scheduler_t *scheduler)
Definition: utils.c:525
Resource role is unknown.
Definition: roles.h:35
#define PCMK_VALUE_FREEZE
Definition: options.h:156
#define pcmk__assert_alloc(nmemb, size)
Definition: internal.h:257
#define PCMK_XA_LOSS_POLICY
Definition: xml_names.h:320
G_GNUC_INTERNAL xmlNode * pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pcmk_scheduler_t *scheduler)
#define PCMK_XA_ROLE
Definition: xml_names.h:387
#define PCMK_VALUE_DEMOTE
Definition: options.h:146
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
#define PCMK_SCORE_INFINITY
Integer score to use to represent "infinity".
Definition: scores.h:26