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.h>
  17 #include <crm/common/scheduler_internal.h>
  18 #include <crm/pengine/status.h>
  19 #include <pacemaker-internal.h>
  20 
  21 #include "libpacemaker_private.h"
  22 
  23 enum loss_ticket_policy {
  24     loss_ticket_stop,
  25     loss_ticket_demote,
  26     loss_ticket_fence,
  27     loss_ticket_freeze
  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 
  38 /*!
  39  * \brief Check whether a ticket constraint matches a resource by role
  40  *
  41  * \param[in] rsc_ticket  Ticket constraint
  42  * \param[in] rsc         Resource to compare with ticket
  43  *
  44  * \param[in] true if constraint has no role or resource's role matches
  45  *            constraint's, otherwise false
  46  */
  47 static bool
  48 ticket_role_matches(const pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
  59 /*!
  60  * \brief Create location constraints and fencing as needed for a ticket
  61  *
  62  * \param[in,out] rsc         Resource affected by ticket
  63  * \param[in]     rsc_ticket  Ticket
  64  */
  65 static void
  66 constraints_for_ticket(pcmk_resource_t *rsc, const rsc_ticket_t *rsc_ticket)
     /* [previous][next][first][last][top][bottom][index][help] */
  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:
  94                 resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
  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) {
 102                     resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 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 
 113                 resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 114                                   "__loss_of_ticket__",
 115                                   rsc->priv->scheduler);
 116 
 117                 for (iter = rsc->priv->active_nodes;
 118                      iter != NULL; iter = iter->next) {
 119 
 120                     pe_fence_node(rsc->priv->scheduler,
 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) {
 131                     pcmk__clear_rsc_flags(rsc, pcmk__rsc_managed);
 132                     pcmk__set_rsc_flags(rsc, pcmk__rsc_blocked);
 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)) {
 141             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 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)) {
 149             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
 180                         pcmk__sched_fencing_enabled)) {
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 267         resource = pcmk__find_constraint_resource(scheduler->priv->resources,
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 {
 324         rsc = pcmk__find_constraint_resource(scheduler->priv->resources,
 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,
     /* [previous][next][first][last][top][bottom][index][help] */
 340                        pcmk_scheduler_t *scheduler)
 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
 416 pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 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) {
 439         scheduler->priv->ticket_constraints =
 440             pcmk__strkey_table(free, destroy_ticket);
 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;
 474         set = pcmk__xe_resolve_idref(set, scheduler->input);
 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 
 497 /*!
 498  * \internal
 499  * \brief Ban resource from a node if it doesn't have a promotion ticket
 500  *
 501  * If a resource has tickets for the promoted role, and the ticket is either not
 502  * granted or set to standby, then ban the resource from all nodes.
 503  *
 504  * \param[in,out] rsc  Resource to check
 505  */
 506 void
 507 pcmk__require_promotion_tickets(pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
 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,
 517                                pcmk__ticket_standby))) {
 518             resource_location(rsc, NULL, -PCMK_SCORE_INFINITY,
 519                               "__stateful_without_ticket__",
 520                               rsc->priv->scheduler);
 521         }
 522     }
 523 }

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