root/lib/pacemaker/pcmk_sched_constraints.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__unpack_constraints
  2. pcmk__find_constraint_resource
  3. find_constraint_tag
  4. pcmk__parse_constraint_role
  5. pcmk__valid_resource_or_tag
  6. pcmk__expand_tags_in_sets
  7. pcmk__tag_to_set
  8. pcmk__create_internal_constraints

   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 <sys/param.h>
  13 #include <sys/types.h>
  14 #include <stdbool.h>
  15 #include <regex.h>
  16 #include <glib.h>
  17 
  18 #include <crm/crm.h>
  19 #include <crm/cib.h>
  20 #include <crm/common/scheduler.h>
  21 #include <crm/common/xml.h>
  22 #include <crm/common/xml_internal.h>
  23 #include <crm/common/iso8601.h>
  24 #include <crm/pengine/status.h>
  25 #include <crm/pengine/internal.h>
  26 #include <pacemaker-internal.h>
  27 #include "libpacemaker_private.h"
  28 
  29 /*!
  30  * \internal
  31  * \brief Unpack constraints from XML
  32  *
  33  * Given scheduler data, unpack all constraints from its input XML into
  34  * data structures.
  35  *
  36  * \param[in,out] scheduler  Scheduler data
  37  */
  38 void
  39 pcmk__unpack_constraints(pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
  40 {
  41     xmlNode *xml_constraints = pcmk_find_cib_element(scheduler->input,
  42                                                      PCMK_XE_CONSTRAINTS);
  43 
  44     for (xmlNode *xml_obj = pcmk__xe_first_child(xml_constraints, NULL, NULL,
  45                                                  NULL);
  46          xml_obj != NULL; xml_obj = pcmk__xe_next(xml_obj, NULL)) {
  47 
  48         const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
  49         const char *tag = (const char *) xml_obj->name;
  50 
  51         if (id == NULL) {
  52             pcmk__config_err("Ignoring <%s> constraint without "
  53                              PCMK_XA_ID, tag);
  54             continue;
  55         }
  56 
  57         crm_trace("Unpacking %s constraint '%s'", tag, id);
  58 
  59         if (pcmk__str_eq(PCMK_XE_RSC_ORDER, tag, pcmk__str_none)) {
  60             pcmk__unpack_ordering(xml_obj, scheduler);
  61 
  62         } else if (pcmk__str_eq(PCMK_XE_RSC_COLOCATION, tag, pcmk__str_none)) {
  63             pcmk__unpack_colocation(xml_obj, scheduler);
  64 
  65         } else if (pcmk__str_eq(PCMK_XE_RSC_LOCATION, tag, pcmk__str_none)) {
  66             pcmk__unpack_location(xml_obj, scheduler);
  67 
  68         } else if (pcmk__str_eq(PCMK_XE_RSC_TICKET, tag, pcmk__str_none)) {
  69             pcmk__unpack_rsc_ticket(xml_obj, scheduler);
  70 
  71         } else {
  72             pcmk__config_err("Unsupported constraint type: %s", tag);
  73         }
  74     }
  75 }
  76 
  77 pcmk_resource_t *
  78 pcmk__find_constraint_resource(GList *rsc_list, const char *id)
     /* [previous][next][first][last][top][bottom][index][help] */
  79 {
  80     if (id == NULL) {
  81         return NULL;
  82     }
  83     for (GList *iter = rsc_list; iter != NULL; iter = iter->next) {
  84         pcmk_resource_t *parent = iter->data;
  85         pcmk_resource_t *match = NULL;
  86 
  87         match = parent->priv->fns->find_rsc(parent, id, NULL,
  88                                             pcmk_rsc_match_history);
  89         if (match != NULL) {
  90             if (!pcmk__str_eq(match->id, id, pcmk__str_none)) {
  91                 /* We found an instance of a clone instead */
  92                 match = uber_parent(match);
  93                 crm_debug("Found %s for %s", match->id, id);
  94             }
  95             return match;
  96         }
  97     }
  98     crm_trace("No match for %s", id);
  99     return NULL;
 100 }
 101 
 102 /*!
 103  * \internal
 104  * \brief Check whether an ID references a resource tag
 105  *
 106  * \param[in]  scheduler  Scheduler data
 107  * \param[in]  id         Tag ID to search for
 108  * \param[out] tag        Where to store tag, if found
 109  *
 110  * \return true if ID refers to a tagged resource or resource set template,
 111  *         otherwise false
 112  */
 113 static bool
 114 find_constraint_tag(const pcmk_scheduler_t *scheduler, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 115                     pcmk__idref_t **tag)
 116 {
 117     *tag = NULL;
 118 
 119     // Check whether id refers to a resource set template
 120     if (g_hash_table_lookup_extended(scheduler->priv->templates, id,
 121                                      NULL, (gpointer *) tag)) {
 122         if (*tag == NULL) {
 123             crm_notice("No resource is derived from template '%s'", id);
 124             return false;
 125         }
 126         return true;
 127     }
 128 
 129     // If not, check whether id refers to a tag
 130     if (g_hash_table_lookup_extended(scheduler->priv->tags, id,
 131                                      NULL, (gpointer *) tag)) {
 132         if (*tag == NULL) {
 133             crm_notice("No resource is tagged with '%s'", id);
 134             return false;
 135         }
 136         return true;
 137     }
 138 
 139     pcmk__config_warn("No resource, template, or tag named '%s'", id);
 140     return false;
 141 }
 142 
 143 /*!
 144  * \internal
 145  * \brief Parse a role attribute from a constraint
 146  *
 147  * This is like pcmk_parse_role() except that started is treated as
 148  * pcmk_role_unknown (indicating any role), and the return value is
 149  * pcmk_rc_unpack_error for invalid specifications.
 150  *
 151  * \param[in] id         ID of constraint being parsed (for logging only)
 152  * \param[in] role_spec  Role specification
 153  * \param[in] role       Where to store parsed role
 154  *
 155  * \return Standard Pacemaker return code
 156  */
 157 int
 158 pcmk__parse_constraint_role(const char *id, const char *role_spec,
     /* [previous][next][first][last][top][bottom][index][help] */
 159                             enum rsc_role_e *role)
 160 {
 161     *role = pcmk_parse_role(role_spec);
 162     switch (*role) {
 163         case pcmk_role_unknown:
 164             if (role_spec != NULL) {
 165                 pcmk__config_err("Ignoring constraint %s: Invalid role '%s'",
 166                                  id, role_spec);
 167                 return pcmk_rc_unpack_error;
 168             }
 169             break;
 170 
 171         case pcmk_role_started:
 172             *role = pcmk_role_unknown;
 173             break;
 174 
 175         default:
 176             break;
 177     }
 178     return pcmk_rc_ok;
 179 }
 180 
 181 /*!
 182  * \brief
 183  * \internal Check whether an ID refers to a valid resource or tag
 184  *
 185  * \param[in]  scheduler  Scheduler data
 186  * \param[in]  id         ID to search for
 187  * \param[out] rsc        Where to store resource, if found
 188  *                        (or NULL to skip searching resources)
 189  * \param[out] tag        Where to store tag, if found
 190  *                        (or NULL to skip searching tags)
 191  *
 192  * \return true if id refers to a resource (possibly indirectly via a tag)
 193  */
 194 bool
 195 pcmk__valid_resource_or_tag(const pcmk_scheduler_t *scheduler, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 196                             pcmk_resource_t **rsc, pcmk__idref_t **tag)
 197 {
 198     if (rsc != NULL) {
 199         *rsc = pcmk__find_constraint_resource(scheduler->priv->resources, id);
 200         if (*rsc != NULL) {
 201             return true;
 202         }
 203     }
 204 
 205     if ((tag != NULL) && find_constraint_tag(scheduler, id, tag)) {
 206         return true;
 207     }
 208 
 209     return false;
 210 }
 211 
 212 /*!
 213  * \internal
 214  * \brief Replace any resource tags with equivalent \C PCMK_XE_RESOURCE_REF
 215  *        entries
 216  *
 217  * If a given constraint has resource sets, check each set for
 218  * \c PCMK_XE_RESOURCE_REF entries that list tags rather than resource IDs, and
 219  * replace any found with \c PCMK_XE_RESOURCE_REF entries for the corresponding
 220  * resource IDs.
 221  *
 222  * \param[in,out] xml_obj    Constraint XML
 223  * \param[in]     scheduler  Scheduler data
 224  *
 225  * \return Equivalent XML with resource tags replaced (or NULL if none)
 226  * \note It is the caller's responsibility to free the return value with
 227  *       \c pcmk__xml_free().
 228  */
 229 xmlNode *
 230 pcmk__expand_tags_in_sets(xmlNode *xml_obj, const pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 231 {
 232     xmlNode *new_xml = NULL;
 233     bool any_refs = false;
 234 
 235     // Short-circuit if there are no sets
 236     if (pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL,
 237                              NULL) == NULL) {
 238         return NULL;
 239     }
 240 
 241     new_xml = pcmk__xml_copy(NULL, xml_obj);
 242 
 243     for (xmlNode *set = pcmk__xe_first_child(new_xml, PCMK_XE_RESOURCE_SET,
 244                                              NULL, NULL);
 245          set != NULL; set = pcmk__xe_next(set, PCMK_XE_RESOURCE_SET)) {
 246 
 247         GList *tag_refs = NULL;
 248         GList *iter = NULL;
 249 
 250         for (xmlNode *xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
 251                                                      NULL, NULL);
 252              xml_rsc != NULL;
 253              xml_rsc = pcmk__xe_next(xml_rsc, PCMK_XE_RESOURCE_REF)) {
 254 
 255             pcmk_resource_t *rsc = NULL;
 256             pcmk__idref_t *tag = NULL;
 257 
 258             if (!pcmk__valid_resource_or_tag(scheduler, pcmk__xe_id(xml_rsc),
 259                                              &rsc, &tag)) {
 260                 pcmk__config_err("Ignoring resource sets for constraint '%s' "
 261                                  "because '%s' is not a valid resource or tag",
 262                                  pcmk__xe_id(xml_obj), pcmk__xe_id(xml_rsc));
 263                 pcmk__xml_free(new_xml);
 264                 return NULL;
 265 
 266             } else if (rsc) {
 267                 continue;
 268 
 269             } else if (tag) {
 270                 /* PCMK_XE_RESOURCE_REF under PCMK_XE_RESOURCE_SET references
 271                  * template or tag
 272                  */
 273                 xmlNode *last_ref = xml_rsc;
 274 
 275                 /* For example, given the original XML:
 276                  *
 277                  *   <resource_set id="tag1-colocation-0" sequential="true">
 278                  *     <resource_ref id="rsc1"/>
 279                  *     <resource_ref id="tag1"/>
 280                  *     <resource_ref id="rsc4"/>
 281                  *   </resource_set>
 282                  *
 283                  * If rsc2 and rsc3 are tagged with tag1, we add them after it:
 284                  *
 285                  *   <resource_set id="tag1-colocation-0" sequential="true">
 286                  *     <resource_ref id="rsc1"/>
 287                  *     <resource_ref id="tag1"/>
 288                  *     <resource_ref id="rsc2"/>
 289                  *     <resource_ref id="rsc3"/>
 290                  *     <resource_ref id="rsc4"/>
 291                  *   </resource_set>
 292                  */
 293 
 294                 for (iter = tag->refs; iter != NULL; iter = iter->next) {
 295                     const char *ref_id = iter->data;
 296                     xmlNode *new_ref = pcmk__xe_create(set,
 297                                                        PCMK_XE_RESOURCE_REF);
 298 
 299                     crm_xml_add(new_ref, PCMK_XA_ID, ref_id);
 300                     xmlAddNextSibling(last_ref, new_ref);
 301 
 302                     last_ref = new_ref;
 303                 }
 304 
 305                 any_refs = true;
 306 
 307                 /* Freeing the resource_ref now would break the XML child
 308                  * iteration, so just remember it for freeing later.
 309                  */
 310                 tag_refs = g_list_append(tag_refs, xml_rsc);
 311             }
 312         }
 313 
 314         /* Now free '<resource_ref id="tag1"/>', and finally get:
 315 
 316            <resource_set id="tag1-colocation-0" sequential="true">
 317              <resource_ref id="rsc1"/>
 318              <resource_ref id="rsc2"/>
 319              <resource_ref id="rsc3"/>
 320              <resource_ref id="rsc4"/>
 321            </resource_set>
 322 
 323          */
 324         for (iter = tag_refs; iter != NULL; iter = iter->next) {
 325             xmlNode *tag_ref = iter->data;
 326 
 327             pcmk__xml_free(tag_ref);
 328         }
 329         g_list_free(tag_refs);
 330     }
 331 
 332     if (!any_refs) {
 333         pcmk__xml_free(new_xml);
 334         new_xml = NULL;
 335     }
 336     return new_xml;
 337 }
 338 
 339 /*!
 340  * \internal
 341  * \brief Convert a tag into a resource set of tagged resources
 342  *
 343  * \param[in,out] xml_obj      Constraint XML
 344  * \param[out]    rsc_set      Where to store resource set XML
 345  * \param[in]     attr         Name of XML attribute with resource or tag ID
 346  * \param[in]     convert_rsc  If true, convert to set even if \p attr
 347  *                             references a resource
 348  * \param[in]     scheduler    Scheduler data
 349  */
 350 bool
 351 pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr,
     /* [previous][next][first][last][top][bottom][index][help] */
 352                  bool convert_rsc, const pcmk_scheduler_t *scheduler)
 353 {
 354     const char *cons_id = NULL;
 355     const char *id = NULL;
 356 
 357     pcmk_resource_t *rsc = NULL;
 358     pcmk__idref_t *tag = NULL;
 359 
 360     *rsc_set = NULL;
 361 
 362     CRM_CHECK((xml_obj != NULL) && (attr != NULL), return false);
 363 
 364     cons_id = pcmk__xe_id(xml_obj);
 365     if (cons_id == NULL) {
 366         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
 367                          xml_obj->name);
 368         return false;
 369     }
 370 
 371     id = crm_element_value(xml_obj, attr);
 372     if (id == NULL) {
 373         return true;
 374     }
 375 
 376     if (!pcmk__valid_resource_or_tag(scheduler, id, &rsc, &tag)) {
 377         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
 378                          "valid resource or tag", cons_id, id);
 379         return false;
 380 
 381     } else if (tag) {
 382         /* The "attr" attribute (for a resource in a constraint) specifies a
 383          * template or tag. Add the corresponding PCMK_XE_RESOURCE_SET
 384          * containing the resources derived from or tagged with it.
 385          */
 386         *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
 387         crm_xml_add(*rsc_set, PCMK_XA_ID, id);
 388 
 389         for (GList *iter = tag->refs; iter != NULL; iter = iter->next) {
 390             const char *obj_ref = iter->data;
 391             xmlNode *rsc_ref = NULL;
 392 
 393             rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
 394             crm_xml_add(rsc_ref, PCMK_XA_ID, obj_ref);
 395         }
 396 
 397         // Set PCMK_XA_SEQUENTIAL=PCMK_VALUE_FALSE for the PCMK_XE_RESOURCE_SET
 398         pcmk__xe_set_bool_attr(*rsc_set, PCMK_XA_SEQUENTIAL, false);
 399 
 400     } else if ((rsc != NULL) && convert_rsc) {
 401         /* Even if a regular resource is referenced by "attr", convert it into a
 402          * PCMK_XE_RESOURCE_SET, because the other resource reference in the
 403          * constraint could be a template or tag.
 404          */
 405         xmlNode *rsc_ref = NULL;
 406 
 407         *rsc_set = pcmk__xe_create(xml_obj, PCMK_XE_RESOURCE_SET);
 408         crm_xml_add(*rsc_set, PCMK_XA_ID, id);
 409 
 410         rsc_ref = pcmk__xe_create(*rsc_set, PCMK_XE_RESOURCE_REF);
 411         crm_xml_add(rsc_ref, PCMK_XA_ID, id);
 412 
 413     } else {
 414         return true;
 415     }
 416 
 417     /* Remove the "attr" attribute referencing the template/tag */
 418     if (*rsc_set != NULL) {
 419         pcmk__xe_remove_attr(xml_obj, attr);
 420     }
 421 
 422     return true;
 423 }
 424 
 425 /*!
 426  * \internal
 427  * \brief Create constraints inherent to resource types
 428  *
 429  * \param[in,out] scheduler  Scheduler data
 430  */
 431 void
 432 pcmk__create_internal_constraints(pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 433 {
 434     crm_trace("Create internal constraints");
 435     for (GList *iter = scheduler->priv->resources;
 436          iter != NULL; iter = iter->next) {
 437 
 438         pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data;
 439 
 440         rsc->priv->cmds->internal_constraints(rsc);
 441     }
 442 }

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