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

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