pacemaker  2.1.6-802a72226b
Scalable High-Availability cluster resource manager
pcmk_sched_tickets.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2023 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/pengine/status.h>
17 #include <pacemaker-internal.h>
18 
19 #include "libpacemaker_private.h"
20 
26 };
27 
28 typedef struct {
29  const char *id;
30  pe_resource_t *rsc;
31  pe_ticket_t *ticket;
32  enum loss_ticket_policy loss_policy;
33  int role;
34 } rsc_ticket_t;
35 
45 static bool
46 ticket_role_matches(const pe_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
47 {
48  if ((rsc_ticket->role == RSC_ROLE_UNKNOWN)
49  || (rsc_ticket->role == rsc->role)) {
50  return true;
51  }
52  pe_rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
53  role2text(rsc_ticket->role));
54  return false;
55 }
56 
64 static void
65 constraints_for_ticket(pe_resource_t *rsc, const rsc_ticket_t *rsc_ticket,
67 {
68  GList *gIter = NULL;
69 
70  CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
71 
72  if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
73  return;
74  }
75 
76  if (rsc->children) {
77  pe_rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
78  for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
79  constraints_for_ticket((pe_resource_t *) gIter->data, rsc_ticket,
80  data_set);
81  }
82  return;
83  }
84 
85  pe_rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
86  rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
87  role2text(rsc_ticket->role));
88 
89  if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
90 
91  switch (rsc_ticket->loss_policy) {
92  case loss_ticket_stop:
93  resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
94  data_set);
95  break;
96 
97  case loss_ticket_demote:
98  // Promotion score will be set to -INFINITY in promotion_order()
99  if (rsc_ticket->role != RSC_ROLE_PROMOTED) {
100  resource_location(rsc, NULL, -INFINITY,
101  "__loss_of_ticket__", data_set);
102  }
103  break;
104 
105  case loss_ticket_fence:
106  if (!ticket_role_matches(rsc, rsc_ticket)) {
107  return;
108  }
109 
110  resource_location(rsc, NULL, -INFINITY, "__loss_of_ticket__",
111  data_set);
112 
113  for (gIter = rsc->running_on; gIter != NULL;
114  gIter = gIter->next) {
115  pe_fence_node(data_set, (pe_node_t *) gIter->data,
116  "deadman ticket was lost", FALSE);
117  }
118  break;
119 
120  case loss_ticket_freeze:
121  if (!ticket_role_matches(rsc, rsc_ticket)) {
122  return;
123  }
124  if (rsc->running_on != NULL) {
127  }
128  break;
129  }
130 
131  } else if (!rsc_ticket->ticket->granted) {
132 
133  if ((rsc_ticket->role != RSC_ROLE_PROMOTED)
134  || (rsc_ticket->loss_policy == loss_ticket_stop)) {
135  resource_location(rsc, NULL, -INFINITY, "__no_ticket__",
136  data_set);
137  }
138 
139  } else if (rsc_ticket->ticket->standby) {
140 
141  if ((rsc_ticket->role != RSC_ROLE_PROMOTED)
142  || (rsc_ticket->loss_policy == loss_ticket_stop)) {
143  resource_location(rsc, NULL, -INFINITY, "__ticket_standby__",
144  data_set);
145  }
146  }
147 }
148 
149 static void
150 rsc_ticket_new(const char *id, pe_resource_t *rsc, pe_ticket_t *ticket,
151  const char *state, const char *loss_policy,
153 {
154  rsc_ticket_t *new_rsc_ticket = NULL;
155 
156  if (rsc == NULL) {
157  pcmk__config_err("Ignoring ticket '%s' because resource "
158  "does not exist", id);
159  return;
160  }
161 
162  new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
163  if (new_rsc_ticket == NULL) {
164  return;
165  }
166 
167  if (pcmk__str_eq(state, RSC_ROLE_STARTED_S,
169  state = RSC_ROLE_UNKNOWN_S;
170  }
171 
172  new_rsc_ticket->id = id;
173  new_rsc_ticket->ticket = ticket;
174  new_rsc_ticket->rsc = rsc;
175  new_rsc_ticket->role = text2role(state);
176 
177  if (pcmk__str_eq(loss_policy, "fence", pcmk__str_casei)) {
179  new_rsc_ticket->loss_policy = loss_ticket_fence;
180  } else {
182  "' for ticket '%s' to 'stop' "
183  "because fencing is not configured", ticket->id);
184  loss_policy = "stop";
185  }
186  }
187 
188  if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
189  crm_debug("On loss of ticket '%s': Fence the nodes running %s (%s)",
190  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
191  role2text(new_rsc_ticket->role));
192 
193  } else if (pcmk__str_eq(loss_policy, "freeze", pcmk__str_casei)) {
194  crm_debug("On loss of ticket '%s': Freeze %s (%s)",
195  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
196  role2text(new_rsc_ticket->role));
197  new_rsc_ticket->loss_policy = loss_ticket_freeze;
198 
199  } else if (pcmk__str_eq(loss_policy, "demote", pcmk__str_casei)) {
200  crm_debug("On loss of ticket '%s': Demote %s (%s)",
201  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
202  role2text(new_rsc_ticket->role));
203  new_rsc_ticket->loss_policy = loss_ticket_demote;
204 
205  } else if (pcmk__str_eq(loss_policy, "stop", pcmk__str_casei)) {
206  crm_debug("On loss of ticket '%s': Stop %s (%s)",
207  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
208  role2text(new_rsc_ticket->role));
209  new_rsc_ticket->loss_policy = loss_ticket_stop;
210 
211  } else {
212  if (new_rsc_ticket->role == RSC_ROLE_PROMOTED) {
213  crm_debug("On loss of ticket '%s': Default to demote %s (%s)",
214  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
215  role2text(new_rsc_ticket->role));
216  new_rsc_ticket->loss_policy = loss_ticket_demote;
217 
218  } else {
219  crm_debug("On loss of ticket '%s': Default to stop %s (%s)",
220  new_rsc_ticket->ticket->id, new_rsc_ticket->rsc->id,
221  role2text(new_rsc_ticket->role));
222  new_rsc_ticket->loss_policy = loss_ticket_stop;
223  }
224  }
225 
226  pe_rsc_trace(rsc, "%s (%s) ==> %s",
227  rsc->id, role2text(new_rsc_ticket->role), ticket->id);
228 
229  rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
230 
232  new_rsc_ticket);
233 
234  if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
235  constraints_for_ticket(rsc, new_rsc_ticket, data_set);
236  }
237 }
238 
239 // \return Standard Pacemaker return code
240 static int
241 unpack_rsc_ticket_set(xmlNode *set, pe_ticket_t *ticket,
242  const char *loss_policy, pe_working_set_t *data_set)
243 {
244  const char *set_id = NULL;
245  const char *role = NULL;
246 
247  CRM_CHECK(set != NULL, return EINVAL);
248  CRM_CHECK(ticket != NULL, return EINVAL);
249 
250  set_id = ID(set);
251  if (set_id == NULL) {
252  pcmk__config_err("Ignoring <" XML_CONS_TAG_RSC_SET "> without "
253  XML_ATTR_ID);
254  return pcmk_rc_unpack_error;
255  }
256 
257  role = crm_element_value(set, "role");
258 
259  for (xmlNode *xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
260  xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
261 
262  pe_resource_t *resource = NULL;
263 
265  ID(xml_rsc));
266  if (resource == NULL) {
267  pcmk__config_err("%s: No resource found for %s",
268  set_id, ID(xml_rsc));
269  return pcmk_rc_unpack_error;
270  }
271  pe_rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
272  resource->id, ticket->id);
273  rsc_ticket_new(set_id, resource, ticket, role, loss_policy, data_set);
274  }
275 
276  return pcmk_rc_ok;
277 }
278 
279 static void
280 unpack_simple_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set)
281 {
282  const char *id = NULL;
283  const char *ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
284  const char *loss_policy = crm_element_value(xml_obj,
286 
287  pe_ticket_t *ticket = NULL;
288 
289  const char *rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
290  const char *state = crm_element_value(xml_obj,
292 
293  // @COMPAT: Deprecated since 2.1.5
294  const char *instance = crm_element_value(xml_obj,
296 
297  pe_resource_t *rsc = NULL;
298 
299  if (instance != NULL) {
301  "Support for " XML_COLOC_ATTR_SOURCE_INSTANCE " is "
302  "deprecated and will be removed in a future release.");
303  }
304 
305  CRM_CHECK(xml_obj != NULL, return);
306 
307  id = ID(xml_obj);
308  if (id == NULL) {
309  pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
310  crm_element_name(xml_obj));
311  return;
312  }
313 
314  if (ticket_str == NULL) {
315  pcmk__config_err("Ignoring constraint '%s' without ticket specified",
316  id);
317  return;
318  } else {
319  ticket = g_hash_table_lookup(data_set->tickets, ticket_str);
320  }
321 
322  if (ticket == NULL) {
323  pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
324  "does not exist", id, ticket_str);
325  return;
326  }
327 
328  if (rsc_id == NULL) {
329  pcmk__config_err("Ignoring constraint '%s' without resource", id);
330  return;
331  } else {
333  }
334 
335  if (rsc == NULL) {
336  pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
337  "does not exist", id, rsc_id);
338  return;
339 
340  } else if ((instance != NULL) && !pe_rsc_is_clone(rsc)) {
341  pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
342  "is not a clone but instance '%s' was requested",
343  id, rsc_id, instance);
344  return;
345  }
346 
347  if (instance != NULL) {
348  rsc = find_clone_instance(rsc, instance);
349  if (rsc == NULL) {
350  pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
351  "does not have an instance '%s'",
352  "'%s'", id, rsc_id, instance);
353  return;
354  }
355  }
356 
357  rsc_ticket_new(id, rsc, ticket, state, loss_policy, data_set);
358 }
359 
360 // \return Standard Pacemaker return code
361 static int
362 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
364 {
365  const char *id = NULL;
366  const char *rsc_id = NULL;
367  const char *state = NULL;
368 
369  pe_resource_t *rsc = NULL;
370  pe_tag_t *tag = NULL;
371 
372  xmlNode *rsc_set = NULL;
373 
374  *expanded_xml = NULL;
375 
376  CRM_CHECK(xml_obj != NULL, return EINVAL);
377 
378  id = ID(xml_obj);
379  if (id == NULL) {
380  pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
381  crm_element_name(xml_obj));
382  return pcmk_rc_unpack_error;
383  }
384 
385  // Check whether there are any resource sets with template or tag references
386  *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, data_set);
387  if (*expanded_xml != NULL) {
388  crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
389  return pcmk_rc_ok;
390  }
391 
392  rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
393  if (rsc_id == NULL) {
394  return pcmk_rc_ok;
395  }
396 
397  if (!pcmk__valid_resource_or_tag(data_set, rsc_id, &rsc, &tag)) {
398  pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
399  "valid resource or tag", id, rsc_id);
400  return pcmk_rc_unpack_error;
401 
402  } else if (rsc != NULL) {
403  // No template or tag is referenced
404  return pcmk_rc_ok;
405  }
406 
408 
409  *expanded_xml = copy_xml(xml_obj);
410 
411  // Convert template/tag reference in "rsc" into resource_set under rsc_ticket
412  if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_COLOC_ATTR_SOURCE,
413  false, data_set)) {
414  free_xml(*expanded_xml);
415  *expanded_xml = NULL;
416  return pcmk_rc_unpack_error;
417  }
418 
419  if (rsc_set != NULL) {
420  if (state != NULL) {
421  // Move "rsc-role" into converted resource_set as a "role" attribute
422  crm_xml_add(rsc_set, "role", state);
424  }
425 
426  } else {
427  free_xml(*expanded_xml);
428  *expanded_xml = NULL;
429  }
430 
431  return pcmk_rc_ok;
432 }
433 
434 void
436 {
437  xmlNode *set = NULL;
438  bool any_sets = false;
439 
440  const char *id = NULL;
441  const char *ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
442  const char *loss_policy = crm_element_value(xml_obj, XML_TICKET_ATTR_LOSS_POLICY);
443 
444  pe_ticket_t *ticket = NULL;
445 
446  xmlNode *orig_xml = NULL;
447  xmlNode *expanded_xml = NULL;
448 
449  CRM_CHECK(xml_obj != NULL, return);
450 
451  id = ID(xml_obj);
452  if (id == NULL) {
453  pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
454  crm_element_name(xml_obj));
455  return;
456  }
457 
458  if (data_set->tickets == NULL) {
460  }
461 
462  if (ticket_str == NULL) {
463  pcmk__config_err("Ignoring constraint '%s' without ticket", id);
464  return;
465  } else {
466  ticket = g_hash_table_lookup(data_set->tickets, ticket_str);
467  }
468 
469  if (ticket == NULL) {
470  ticket = ticket_new(ticket_str, data_set);
471  if (ticket == NULL) {
472  return;
473  }
474  }
475 
476  if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
477  data_set) != pcmk_rc_ok) {
478  return;
479  }
480  if (expanded_xml != NULL) {
481  orig_xml = xml_obj;
482  xml_obj = expanded_xml;
483  }
484 
485  for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
486  set = crm_next_same_xml(set)) {
487 
488  any_sets = true;
489  set = expand_idref(set, data_set->input);
490  if ((set == NULL) // Configuration error, message already logged
491  || (unpack_rsc_ticket_set(set, ticket, loss_policy,
492  data_set) != pcmk_rc_ok)) {
493  if (expanded_xml != NULL) {
494  free_xml(expanded_xml);
495  }
496  return;
497  }
498  }
499 
500  if (expanded_xml) {
501  free_xml(expanded_xml);
502  xml_obj = orig_xml;
503  }
504 
505  if (!any_sets) {
506  unpack_simple_rsc_ticket(xml_obj, data_set);
507  }
508 }
509 
519 void
521 {
522  for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
523  rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
524 
525  if ((rsc_ticket->role == RSC_ROLE_PROMOTED)
526  && (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
527  resource_location(rsc, NULL, -INFINITY,
528  "__stateful_without_ticket__", rsc->cluster);
529  }
530  }
531 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:235
A dumping ground.
GList * rsc_tickets
Definition: pe_types.h:392
#define INFINITY
Definition: crm.h:99
enum rsc_role_e role
Definition: pe_types.h:402
#define pcmk__config_warn(fmt...)
#define RSC_ROLE_STARTED_S
Definition: common.h:112
GList * children
Definition: pe_types.h:409
xmlNode * first_named_child(const xmlNode *parent, const char *name)
Definition: xml.c:2521
#define pcmk__config_err(fmt...)
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: nvpair.c:302
G_GNUC_INTERNAL xmlNode * pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pe_working_set_t *data_set)
void resource_location(pe_resource_t *rsc, const pe_node_t *node, int score, const char *tag, pe_working_set_t *data_set)
Definition: utils.c:398
GHashTable * tickets
Definition: pe_types.h:175
#define XML_CONS_TAG_RSC_SET
Definition: msg_xml.h:367
G_GNUC_INTERNAL bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr, bool convert_rsc, const pe_working_set_t *data_set)
#define pe__set_resource_flags(resource, flags_to_set)
Definition: internal.h:77
GList * resources
Definition: pe_types.h:181
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:819
const char * role2text(enum rsc_role_e role)
Definition: common.c:450
G_GNUC_INTERNAL bool pcmk__valid_resource_or_tag(const pe_working_set_t *data_set, const char *id, pe_resource_t **rsc, pe_tag_t **tag)
GList * ticket_constraints
Definition: pe_types.h:185
#define crm_debug(fmt, args...)
Definition: logging.h:382
#define XML_ATTR_ID
Definition: msg_xml.h:147
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:496
void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set)
loss_ticket_policy
#define pe_warn_once(pe_wo_bit, fmt...)
Definition: internal.h:177
pe_ticket_t * ticket_new(const char *ticket_id, pe_working_set_t *data_set)
Definition: utils.c:547
void pe_fence_node(pe_working_set_t *data_set, pe_node_t *node, const char *reason, bool priority_delay)
Schedule a fence action for a node.
Definition: unpack.c:113
#define pcmk_is_set(g, f)
Convenience alias for pcmk_all_flags_set(), to check single flag.
Definition: util.h:121
xmlNode * expand_idref(xmlNode *input, xmlNode *top)
Definition: xml.c:2593
pe_working_set_t * data_set
#define pe_flag_stonith_enabled
Definition: pe_types.h:115
#define XML_COLOC_ATTR_SOURCE_INSTANCE
Definition: msg_xml.h:380
#define XML_TAG_RESOURCE_REF
Definition: msg_xml.h:229
pe_resource_t * find_clone_instance(const pe_resource_t *rsc, const char *sub_id)
Definition: clone.c:226
void free_xml(xmlNode *child)
Definition: xml.c:813
enum rsc_role_e text2role(const char *role)
Definition: common.c:479
xmlNode * input
Definition: pe_types.h:160
G_GNUC_INTERNAL pe_resource_t * pcmk__find_constraint_resource(GList *rsc_list, const char *id)
uint32_t id
Definition: cpg.c:45
#define XML_COLOC_ATTR_SOURCE_ROLE
Definition: msg_xml.h:373
char * id
Definition: pe_types.h:480
void pcmk__require_promotion_tickets(pe_resource_t *rsc)
Cluster status and scheduling.
GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func)
Definition: strings.c:611
void xml_remove_prop(xmlNode *obj, const char *name)
Definition: xml.c:1735
#define pe__clear_resource_flags(resource, flags_to_clear)
Definition: internal.h:83
GList * running_on
Definition: pe_types.h:398
#define pe_rsc_block
Definition: pe_types.h:274
pe_working_set_t * cluster
Definition: pe_types.h:353
void destroy_ticket(gpointer data)
Definition: utils.c:535
#define RSC_ROLE_UNKNOWN_S
Definition: common.h:110
#define crm_log_xml_trace(xml, text)
Definition: logging.h:391
#define XML_TICKET_ATTR_TICKET
Definition: msg_xml.h:400
#define pe_rsc_trace(rsc, fmt, args...)
Definition: internal.h:50
#define ID(x)
Definition: msg_xml.h:480
unsigned long long flags
Definition: pe_types.h:169
#define pe_rsc_managed
Definition: pe_types.h:273
#define XML_COLOC_ATTR_SOURCE
Definition: msg_xml.h:372
char * id
Definition: pe_types.h:347
xmlNode * crm_next_same_xml(const xmlNode *sibling)
Get next instance of same XML tag.
Definition: xml.c:2547
#define XML_TICKET_ATTR_LOSS_POLICY
Definition: msg_xml.h:401