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/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     pe_rsc_trace(rsc, "Skipping constraint: \"%s\" state filter",
  54                  role2text(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         pe_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     pe_rsc_trace(rsc, "%s: Processing ticket dependency on %s (%s, %s)",
  84                  rsc->id, rsc_ticket->ticket->id, rsc_ticket->id,
  85                  role2text(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, -INFINITY, "__loss_of_ticket__",
  92                                   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, -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, -INFINITY, "__loss_of_ticket__",
 109                                   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                     pe__clear_resource_flags(rsc, pcmk_rsc_managed);
 123                     pe__set_resource_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, -INFINITY, "__no_ticket__",
 133                               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, -INFINITY, "__ticket_standby__",
 141                               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 = text2role(state);
 172 
 173     if (pcmk__str_eq(loss_policy, "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 '" XML_TICKET_ATTR_LOSS_POLICY
 178                              "' for ticket '%s' to 'stop' "
 179                              "because fencing is not configured", ticket->id);
 180             loss_policy = "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                   role2text(new_rsc_ticket->role));
 188 
 189     } else if (pcmk__str_eq(loss_policy, "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                   role2text(new_rsc_ticket->role));
 193         new_rsc_ticket->loss_policy = loss_ticket_freeze;
 194 
 195     } else if (pcmk__str_eq(loss_policy, PCMK_ACTION_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                   role2text(new_rsc_ticket->role));
 199         new_rsc_ticket->loss_policy = loss_ticket_demote;
 200 
 201     } else if (pcmk__str_eq(loss_policy, "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                   role2text(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                       role2text(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                       role2text(new_rsc_ticket->role));
 218             new_rsc_ticket->loss_policy = loss_ticket_stop;
 219         }
 220     }
 221 
 222     pe_rsc_trace(rsc, "%s (%s) ==> %s",
 223                  rsc->id, role2text(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 = ID(set);
 247     if (set_id == NULL) {
 248         pcmk__config_err("Ignoring <" XML_CONS_TAG_RSC_SET "> without "
 249                          XML_ATTR_ID);
 250         return pcmk_rc_unpack_error;
 251     }
 252 
 253     role = crm_element_value(set, "role");
 254 
 255     for (xmlNode *xml_rsc = first_named_child(set, XML_TAG_RESOURCE_REF);
 256          xml_rsc != NULL; xml_rsc = crm_next_same_xml(xml_rsc)) {
 257 
 258         pcmk_resource_t *resource = NULL;
 259 
 260         resource = pcmk__find_constraint_resource(scheduler->resources,
 261                                                   ID(xml_rsc));
 262         if (resource == NULL) {
 263             pcmk__config_err("%s: No resource found for %s",
 264                              set_id, ID(xml_rsc));
 265             return pcmk_rc_unpack_error;
 266         }
 267         pe_rsc_trace(resource, "Resource '%s' depends on ticket '%s'",
 268                      resource->id, ticket->id);
 269         rsc_ticket_new(set_id, resource, ticket, role, loss_policy);
 270     }
 271 
 272     return pcmk_rc_ok;
 273 }
 274 
 275 static void
 276 unpack_simple_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 277 {
 278     const char *id = NULL;
 279     const char *ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
 280     const char *loss_policy = crm_element_value(xml_obj,
 281                                                 XML_TICKET_ATTR_LOSS_POLICY);
 282 
 283     pcmk_ticket_t *ticket = NULL;
 284 
 285     const char *rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
 286     const char *state = crm_element_value(xml_obj,
 287                                              XML_COLOC_ATTR_SOURCE_ROLE);
 288 
 289     // @COMPAT: Deprecated since 2.1.5
 290     const char *instance = crm_element_value(xml_obj,
 291                                              XML_COLOC_ATTR_SOURCE_INSTANCE);
 292 
 293     pcmk_resource_t *rsc = NULL;
 294 
 295     if (instance != NULL) {
 296         pe_warn_once(pcmk__wo_coloc_inst,
 297                      "Support for " XML_COLOC_ATTR_SOURCE_INSTANCE " is "
 298                      "deprecated and will be removed in a future release.");
 299     }
 300 
 301     CRM_CHECK(xml_obj != NULL, return);
 302 
 303     id = ID(xml_obj);
 304     if (id == NULL) {
 305         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
 306                          xml_obj->name);
 307         return;
 308     }
 309 
 310     if (ticket_str == NULL) {
 311         pcmk__config_err("Ignoring constraint '%s' without ticket specified",
 312                          id);
 313         return;
 314     } else {
 315         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
 316     }
 317 
 318     if (ticket == NULL) {
 319         pcmk__config_err("Ignoring constraint '%s' because ticket '%s' "
 320                          "does not exist", id, ticket_str);
 321         return;
 322     }
 323 
 324     if (rsc_id == NULL) {
 325         pcmk__config_err("Ignoring constraint '%s' without resource", id);
 326         return;
 327     } else {
 328         rsc = pcmk__find_constraint_resource(scheduler->resources, rsc_id);
 329     }
 330 
 331     if (rsc == NULL) {
 332         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 333                          "does not exist", id, rsc_id);
 334         return;
 335 
 336     } else if ((instance != NULL) && !pe_rsc_is_clone(rsc)) {
 337         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 338                          "is not a clone but instance '%s' was requested",
 339                          id, rsc_id, instance);
 340         return;
 341     }
 342 
 343     if (instance != NULL) {
 344         rsc = find_clone_instance(rsc, instance);
 345         if (rsc == NULL) {
 346             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
 347                               "does not have an instance '%s'",
 348                               "'%s'", id, rsc_id, instance);
 349             return;
 350         }
 351     }
 352 
 353     rsc_ticket_new(id, rsc, ticket, state, loss_policy);
 354 }
 355 
 356 // \return Standard Pacemaker return code
 357 static int
 358 unpack_rsc_ticket_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
     /* [previous][next][first][last][top][bottom][index][help] */
 359                        pcmk_scheduler_t *scheduler)
 360 {
 361     const char *id = NULL;
 362     const char *rsc_id = NULL;
 363     const char *state = NULL;
 364 
 365     pcmk_resource_t *rsc = NULL;
 366     pcmk_tag_t *tag = NULL;
 367 
 368     xmlNode *rsc_set = NULL;
 369 
 370     *expanded_xml = NULL;
 371 
 372     CRM_CHECK(xml_obj != NULL, return EINVAL);
 373 
 374     id = ID(xml_obj);
 375     if (id == NULL) {
 376         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
 377                          xml_obj->name);
 378         return pcmk_rc_unpack_error;
 379     }
 380 
 381     // Check whether there are any resource sets with template or tag references
 382     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
 383     if (*expanded_xml != NULL) {
 384         crm_log_xml_trace(*expanded_xml, "Expanded rsc_ticket");
 385         return pcmk_rc_ok;
 386     }
 387 
 388     rsc_id = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE);
 389     if (rsc_id == NULL) {
 390         return pcmk_rc_ok;
 391     }
 392 
 393     if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) {
 394         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
 395                          "valid resource or tag", id, rsc_id);
 396         return pcmk_rc_unpack_error;
 397 
 398     } else if (rsc != NULL) {
 399         // No template or tag is referenced
 400         return pcmk_rc_ok;
 401     }
 402 
 403     state = crm_element_value(xml_obj, XML_COLOC_ATTR_SOURCE_ROLE);
 404 
 405     *expanded_xml = copy_xml(xml_obj);
 406 
 407     // Convert any template or tag reference in "rsc" into ticket resource_set
 408     if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, XML_COLOC_ATTR_SOURCE,
 409                           false, 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 "rsc-role" into converted resource_set as a "role" attribute
 418             crm_xml_add(rsc_set, "role", state);
 419             xml_remove_prop(*expanded_xml, XML_COLOC_ATTR_SOURCE_ROLE);
 420         }
 421 
 422     } else {
 423         free_xml(*expanded_xml);
 424         *expanded_xml = NULL;
 425     }
 426 
 427     return pcmk_rc_ok;
 428 }
 429 
 430 void
 431 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 432 {
 433     xmlNode *set = NULL;
 434     bool any_sets = false;
 435 
 436     const char *id = NULL;
 437     const char *ticket_str = NULL;
 438 
 439     pcmk_ticket_t *ticket = NULL;
 440 
 441     xmlNode *orig_xml = NULL;
 442     xmlNode *expanded_xml = NULL;
 443 
 444     CRM_CHECK(xml_obj != NULL, return);
 445 
 446     id = ID(xml_obj);
 447     if (id == NULL) {
 448         pcmk__config_err("Ignoring <%s> constraint without " XML_ATTR_ID,
 449                          xml_obj->name);
 450         return;
 451     }
 452 
 453     if (scheduler->tickets == NULL) {
 454         scheduler->tickets = pcmk__strkey_table(free, destroy_ticket);
 455     }
 456 
 457     ticket_str = crm_element_value(xml_obj, XML_TICKET_ATTR_TICKET);
 458     if (ticket_str == NULL) {
 459         pcmk__config_err("Ignoring constraint '%s' without ticket", id);
 460         return;
 461     } else {
 462         ticket = g_hash_table_lookup(scheduler->tickets, ticket_str);
 463     }
 464 
 465     if (ticket == NULL) {
 466         ticket = ticket_new(ticket_str, scheduler);
 467         if (ticket == NULL) {
 468             return;
 469         }
 470     }
 471 
 472     if (unpack_rsc_ticket_tags(xml_obj, &expanded_xml,
 473                                scheduler) != pcmk_rc_ok) {
 474         return;
 475     }
 476     if (expanded_xml != NULL) {
 477         orig_xml = xml_obj;
 478         xml_obj = expanded_xml;
 479     }
 480 
 481     for (set = first_named_child(xml_obj, XML_CONS_TAG_RSC_SET); set != NULL;
 482          set = crm_next_same_xml(set)) {
 483 
 484         const char *loss_policy = NULL;
 485 
 486         any_sets = true;
 487         set = expand_idref(set, scheduler->input);
 488         loss_policy = crm_element_value(xml_obj, XML_TICKET_ATTR_LOSS_POLICY);
 489 
 490         if ((set == NULL) // Configuration error, message already logged
 491             || (unpack_rsc_ticket_set(set, ticket, loss_policy,
 492                                       scheduler) != 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, scheduler);
 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(pcmk_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 == pcmk_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] */