root/lib/pacemaker/pcmk_sched_tickets.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ticket_role_matches
  2. constraints_for_ticket
  3. rsc_ticket_new
  4. unpack_rsc_ticket_set
  5. unpack_simple_rsc_ticket
  6. unpack_rsc_ticket_tags
  7. pcmk__unpack_rsc_ticket
  8. pcmk__require_promotion_tickets

   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 
  21 enum loss_ticket_policy {
  22     loss_ticket_stop,
  23     loss_ticket_demote,
  24     loss_ticket_fence,
  25     loss_ticket_freeze
  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 
  36 /*!
  37  * \brief Check whether a ticket constraint matches a resource by role
  38  *
  39  * \param[in] rsc_ticket  Ticket constraint
  40  * \param[in] rsc         Resource to compare with ticket
  41  *
  42  * \param[in] true if constraint has no role or resource's role matches
  43  *            constraint's, otherwise false
  44  */
  45 static bool
  46 ticket_role_matches(const pe_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
  57 /*!
  58  * \brief Create location constraints and fencing as needed for a ticket
  59  *
  60  * \param[in,out] rsc         Resource affected by ticket
  61  * \param[in]     rsc_ticket  Ticket
  62  * \param[in,out] data_set    Cluster working set
  63  */
  64 static void
  65 constraints_for_ticket(pe_resource_t *rsc, const rsc_ticket_t *rsc_ticket,
     /* [previous][next][first][last][top][bottom][index][help] */
  66                        pe_working_set_t *data_set)
  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) {
 125                     pe__clear_resource_flags(rsc, pe_rsc_managed);
 126                     pe__set_resource_flags(rsc, pe_rsc_block);
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 151                const char *state, const char *loss_policy,
 152                pe_working_set_t *data_set)
 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,
 168                      pcmk__str_null_matches|pcmk__str_casei)) {
 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)) {
 178         if (pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) {
 179             new_rsc_ticket->loss_policy = loss_ticket_fence;
 180         } else {
 181             pcmk__config_err("Resetting '" XML_TICKET_ATTR_LOSS_POLICY
 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 
 231     data_set->ticket_constraints = g_list_append(data_set->ticket_constraints,
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 264         resource = pcmk__find_constraint_resource(data_set->resources,
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
 285                                                 XML_TICKET_ATTR_LOSS_POLICY);
 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,
 291                                              XML_COLOC_ATTR_SOURCE_ROLE);
 292 
 293     // @COMPAT: Deprecated since 2.1.5
 294     const char *instance = crm_element_value(xml_obj,
 295                                              XML_COLOC_ATTR_SOURCE_INSTANCE);
 296 
 297     pe_resource_t *rsc = NULL;
 298 
 299     if (instance != NULL) {
 300         pe_warn_once(pe_wo_coloc_inst,
 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 {
 332         rsc = pcmk__find_constraint_resource(data_set->resources, rsc_id);
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 363                        pe_working_set_t *data_set)
 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 
 407     state = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE);
 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);
 423             xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_SOURCE_ROLE);
 424         }
 425 
 426     } else {
 427         free_xml(*expanded_xml);
 428         *expanded_xml = NULL;
 429     }
 430 
 431     return pcmk_rc_ok;
 432 }
 433 
 434 void
 435 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
 459         data_set->tickets = pcmk__strkey_table(free, destroy_ticket);
 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 
 510 /*!
 511  * \internal
 512  * \brief Ban resource from a node if it doesn't have a promotion ticket
 513  *
 514  * If a resource has tickets for the promoted role, and the ticket is either not
 515  * granted or set to standby, then ban the resource from all nodes.
 516  *
 517  * \param[in,out] rsc  Resource to check
 518  */
 519 void
 520 pcmk__require_promotion_tickets(pe_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 }

/* [previous][next][first][last][top][bottom][index][help] */