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-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 ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
     /* [previous][next][first][last][top][bottom][index][help] */
  48 {
  49     if ((rsc_ticket->role == pcmk_role_unknown)
  50         || (rsc_ticket->role == rsc->role)) {
  51         return true;
  52     }
  53     pcmk__rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
  54                     pcmk_role_text(rsc_ticket->role));
  55     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 constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
     /* [previous][next][first][last][top][bottom][index][help] */
  66 {
  67     GList *iter = NULL;
  68 
  69     CRM_CHECK((rsc != NULL) && (rsc_ticket != NULL), return);
  70 
  71     if (rsc_ticket->ticket->granted && !rsc_ticket->ticket->standby) {
  72         return;
  73     }
  74 
  75     if (rsc->children) {
  76         pcmk__rsc_trace(rsc, "Processing ticket dependencies from %s", rsc->id);
  77         for (iter = rsc->children; iter != NULL; iter = iter->next) {
  78             constraints_for_ticket((pcmk_resource_t *) iter->data, rsc_ticket);
  79         }
  80         return;
  81     }
  82 
  83     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     if (!rsc_ticket->ticket->granted && (rsc->running_on != NULL)) {
  88 
  89         switch (rsc_ticket->loss_policy) {
  90             case loss_ticket_stop:
  91                 resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
  92                                   "__loss_of_ticket__", rsc->cluster);
  93                 break;
  94 
  95             case loss_ticket_demote:
  96                 // Promotion score will be set to -INFINITY in promotion_order()
  97                 if (rsc_ticket->role != pcmk_role_promoted) {
  98                     resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
  99                                       "__loss_of_ticket__", rsc->cluster);
 100                 }
 101                 break;
 102 
 103             case loss_ticket_fence:
 104                 if (!ticket_role_matches(rsc, rsc_ticket)) {
 105                     return;
 106                 }
 107 
 108                 resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 109                                   "__loss_of_ticket__", rsc->cluster);
 110 
 111                 for (iter = rsc->running_on; iter != NULL; iter = iter->next) {
 112                     pe_fence_node(rsc->cluster, (pcmk_node_t *) iter->data,
 113                                   "deadman ticket was lost", FALSE);
 114                 }
 115                 break;
 116 
 117             case loss_ticket_freeze:
 118                 if (!ticket_role_matches(rsc, rsc_ticket)) {
 119                     return;
 120                 }
 121                 if (rsc->running_on != NULL) {
 122                     pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed);
 123                     pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked);
 124                 }
 125                 break;
 126         }
 127 
 128     } else if (!rsc_ticket->ticket->granted) {
 129 
 130         if ((rsc_ticket->role != pcmk_role_promoted)
 131             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
 132             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 133                               "__no_ticket__", rsc->cluster);
 134         }
 135 
 136     } else if (rsc_ticket->ticket->standby) {
 137 
 138         if ((rsc_ticket->role != pcmk_role_promoted)
 139             || (rsc_ticket->loss_policy == loss_ticket_stop)) {
 140             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 141                               "__ticket_standby__", rsc->cluster);
 142         }
 143     }
 144 }
 145 
 146 static void
 147 rsc_ticket_new(const char *id, pcmk_resource_t *rsc, pcmk_ticket_t *ticket,
     /* [previous][next][first][last][top][bottom][index][help] */
 148                const char *state, const char *loss_policy)
 149 {
 150     rsc_ticket_t *new_rsc_ticket = NULL;
 151 
 152     if (rsc == NULL) {
 153         pcmk__config_err("Ignoring ticket '%s' because resource "
 154                          "does not exist", id);
 155         return;
 156     }
 157 
 158     new_rsc_ticket = calloc(1, sizeof(rsc_ticket_t));
 159     if (new_rsc_ticket == NULL) {
 160         return;
 161     }
 162 
 163     if (pcmk__str_eq(state, PCMK_ROLE_STARTED,
 164                      pcmk__str_null_matches|pcmk__str_casei)) {
 165         state = PCMK__ROLE_UNKNOWN;
 166     }
 167 
 168     new_rsc_ticket->id = id;
 169     new_rsc_ticket->ticket = ticket;
 170     new_rsc_ticket->rsc = rsc;
 171     new_rsc_ticket->role = pcmk_parse_role(state);
 172 
 173     if (pcmk__str_eq(loss_policy, PCMK_VALUE_FENCE, pcmk__str_casei)) {
 174         if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) {
 175             new_rsc_ticket->loss_policy = loss_ticket_fence;
 176         } else {
 177             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             loss_policy = PCMK_VALUE_STOP;
 181         }
 182     }
 183 
 184     if (new_rsc_ticket->loss_policy == loss_ticket_fence) {
 185         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     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_FREEZE, pcmk__str_casei)) {
 190         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         new_rsc_ticket->loss_policy = loss_ticket_freeze;
 194 
 195     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_DEMOTE, pcmk__str_casei)) {
 196         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         new_rsc_ticket->loss_policy = loss_ticket_demote;
 200 
 201     } else if (pcmk__str_eq(loss_policy, PCMK_VALUE_STOP, pcmk__str_casei)) {
 202         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         new_rsc_ticket->loss_policy = loss_ticket_stop;
 206 
 207     } else {
 208         if (new_rsc_ticket->role == pcmk_role_promoted) {
 209             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             new_rsc_ticket->loss_policy = loss_ticket_demote;
 213 
 214         } else {
 215             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             new_rsc_ticket->loss_policy = loss_ticket_stop;
 219         }
 220     }
 221 
 222     pcmk__rsc_trace(rsc, "%s (%s) ==> %s",
 223                     rsc->id, pcmk_role_text(new_rsc_ticket->role), ticket->id);
 224 
 225     rsc->rsc_tickets = g_list_append(rsc->rsc_tickets, new_rsc_ticket);
 226 
 227     rsc->cluster->ticket_constraints = g_list_append(
 228         rsc->cluster->ticket_constraints, new_rsc_ticket);
 229 
 230     if (!(new_rsc_ticket->ticket->granted) || new_rsc_ticket->ticket->standby) {
 231         constraints_for_ticket(rsc, new_rsc_ticket);
 232     }
 233 }
 234 
 235 // \return Standard Pacemaker return code
 236 static int
 237 unpack_rsc_ticket_set(xmlNode *set, pcmk_ticket_t *ticket,
     /* [previous][next][first][last][top][bottom][index][help] */
 238                       const char *loss_policy, pcmk_scheduler_t *scheduler)
 239 {
 240     const char *set_id = NULL;
 241     const char *role = NULL;
 242 
 243     CRM_CHECK(set != NULL, return EINVAL);
 244     CRM_CHECK(ticket != NULL, return EINVAL);
 245 
 246     set_id = pcmk__xe_id(set);
 247     if (set_id == NULL) {
 248         pcmk__config_err("Ignoring <" PCMK_XE_RESOURCE_SET "> without "
 249                          PCMK_XA_ID);
 250         return pcmk_rc_unpack_error;
 251     }
 252 
 253     role = crm_element_value(set, PCMK_XA_ROLE);
 254 
 255     for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
 256                                                  NULL, NULL);
 257          xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 258 
 259         pcmk_resource_t *resource = NULL;
 260 
 261         resource = pcmk__find_constraint_resource(scheduler->resources,
 262                                                   pcmk__xe_id(xml_rsc));
 263         if (resource == NULL) {
 264             pcmk__config_err("%s: No resource found for %s",
 265                              set_id, pcmk__xe_id(xml_rsc));
 266             return pcmk_rc_unpack_error;
 267         }
 268         pcmk__rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
 269                         resource->id, ticket->id);
 270         rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
 271     }
 272 
 273     return pcmk_rc_ok;
 274 }
 275 
 276 static void
 277 unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 278 {
 279     const char *id = NULL;
 280     const char *ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
 281     const char *loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
 282 
 283     pcmk_ticket_t *ticket = NULL;
 284 
 285     const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
 286     const char *state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 287 
 288     // @COMPAT: Deprecated since 2.1.5
 289     const char *instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
 290 
 291     pcmk_resource_t *rsc = NULL;
 292 
 293     if (instance != NULL) {
 294         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     CRM_CHECK(xml_obj != NULL, return);
 300 
 301     id = pcmk__xe_id(xml_obj);
 302     if (id == NULL) {
 303         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
 304                          xml_obj->name);
 305         return;
 306     }
 307 
 308     if (ticket_str == NULL) {
 309         pcmk__config_err("Ignoring constraint '%s' without ticket specified",
 310                          id);
 311         return;
 312     } else {
 313         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
 314     }
 315 
 316     if (ticket == NULL) {
 317         pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
 318                          "does not exist", id, ticket_str);
 319         return;
 320     }
 321 
 322     if (rsc_id == NULL) {
 323         pcmk__config_err("Ignoring constraint '%s' without resource", id);
 324         return;
 325     } else {
 326         rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
 327     }
 328 
 329     if (rsc == NULL) {
 330         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 331                          "does not exist", id, rsc_id);
 332         return;
 333 
 334     } else if ((instance != NULL) && !pcmk__is_clone(rsc)) {
 335         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         return;
 339     }
 340 
 341     if (instance != NULL) {
 342         rsc = find_clone_instance(rsc, instance);
 343         if (rsc == NULL) {
 344             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
 345                               "does not have an instance '%s'",
 346                               id, rsc_id, instance);
 347             return;
 348         }
 349     }
 350 
 351     rsc_ticket_new(id, rsc, ticket, state, loss_policy);
 352 }
 353 
 354 // \return Standard Pacemaker return code
 355 static int
 356 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
     /* [previous][next][first][last][top][bottom][index][help] */
 357                        pcmk_scheduler_t *scheduler)
 358 {
 359     const char *id = NULL;
 360     const char *rsc_id = NULL;
 361     const char *state = NULL;
 362 
 363     pcmk_resource_t *rsc = NULL;
 364     pcmk_tag_t *tag = NULL;
 365 
 366     xmlNode *rsc_set = NULL;
 367 
 368     *expanded_xml = NULL;
 369 
 370     CRM_CHECK(xml_obj != NULL, return EINVAL);
 371 
 372     id = pcmk__xe_id(xml_obj);
 373     if (id == NULL) {
 374         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
 375                          xml_obj->name);
 376         return pcmk_rc_unpack_error;
 377     }
 378 
 379     // Check whether there are any resource sets with template or tag references
 380     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
 381     if (*expanded_xml != NULL) {
 382         crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
 383         return pcmk_rc_ok;
 384     }
 385 
 386     rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC);
 387     if (rsc_id == NULL) {
 388         return pcmk_rc_ok;
 389     }
 390 
 391     if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
 392         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
 393                          "valid resource or tag", id, rsc_id);
 394         return pcmk_rc_unpack_error;
 395 
 396     } else if (rsc != NULL) {
 397         // No template or tag is referenced
 398         return pcmk_rc_ok;
 399     }
 400 
 401     state = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 402 
 403     *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     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false,
 409                           scheduler)) {
 410         free_xml(*expanded_xml);
 411         *expanded_xml = NULL;
 412         return pcmk_rc_unpack_error;
 413     }
 414 
 415     if (rsc_set != NULL) {
 416         if (state != NULL) {
 417             /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as a
 418              * PCMK_XA_ROLE attribute
 419              */
 420             crm_xml_add(rsc_set, PCMK_XA_ROLE, state);
 421             pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
 422         }
 423 
 424     } else {
 425         free_xml(*expanded_xml);
 426         *expanded_xml = NULL;
 427     }
 428 
 429     return pcmk_rc_ok;
 430 }
 431 
 432 void
 433 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 434 {
 435     xmlNode *set = NULL;
 436     bool any_sets = false;
 437 
 438     const char *id = NULL;
 439     const char *ticket_str = NULL;
 440 
 441     pcmk_ticket_t *ticket = NULL;
 442 
 443     xmlNode *orig_xml = NULL;
 444     xmlNode *expanded_xml = NULL;
 445 
 446     CRM_CHECK(xml_obj != NULL, return);
 447 
 448     id = pcmk__xe_id(xml_obj);
 449     if (id == NULL) {
 450         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
 451                          xml_obj->name);
 452         return;
 453     }
 454 
 455     if (scheduler->tickets == NULL) {
 456         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
 457     }
 458 
 459     ticket_str = crm_element_value(xml_obj, PCMK_XA_TICKET);
 460     if (ticket_str == NULL) {
 461         pcmk__config_err("Ignoring constraint '%s' without ticket", id);
 462         return;
 463     } else {
 464         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
 465     }
 466 
 467     if (ticket == NULL) {
 468         ticket = ticket_new(ticket_str, scheduler);
 469         if (ticket == NULL) {
 470             return;
 471         }
 472     }
 473 
 474     if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
 475                                scheduler) != pcmk_rc_ok) {
 476         return;
 477     }
 478     if (expanded_xml != NULL) {
 479         orig_xml = xml_obj;
 480         xml_obj = expanded_xml;
 481     }
 482 
 483     for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
 484          set != NULL; set = pcmk__xe_next_same(set)) {
 485 
 486         const char *loss_policy = NULL;
 487 
 488         any_sets = true;
 489         set = expand_idref(set, scheduler->input);
 490         loss_policy = crm_element_value(xml_obj, PCMK_XA_LOSS_POLICY);
 491 
 492         if ((set == NULL) // Configuration error, message already logged
 493             || (unpack_rsc_ticket_set(set, ticket, loss_policy,
 494                                       scheduler) != pcmk_rc_ok)) {
 495             if (expanded_xml != NULL) {
 496                 free_xml(expanded_xml);
 497             }
 498             return;
 499         }
 500     }
 501 
 502     if (expanded_xml) {
 503         free_xml(expanded_xml);
 504         xml_obj = orig_xml;
 505     }
 506 
 507     if (!any_sets) {
 508         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 pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 523 {
 524     for (GList *item = rsc->rsc_tickets; item != NULL; item = item->next) {
 525         rsc_ticket_t *rsc_ticket = (rsc_ticket_t *) item->data;
 526 
 527         if ((rsc_ticket->role == pcmk_role_promoted)
 528             && (!rsc_ticket->ticket->granted || rsc_ticket->ticket->standby)) {
 529             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 530                               "__stateful_without_ticket__", rsc->cluster);
 531         }
 532     }
 533 }

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