root/lib/pacemaker/pcmk_sched_colocation.c

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

DEFINITIONS

This source file includes following definitions.
  1. cmp_colocation_priority
  2. cmp_dependent_priority
  3. cmp_primary_priority
  4. pcmk__add_this_with
  5. pcmk__add_this_with_list
  6. pcmk__add_with_this
  7. pcmk__add_with_this_list
  8. anti_colocation_order
  9. pcmk__new_colocation
  10. unpack_influence
  11. unpack_colocation_set
  12. colocate_rsc_sets
  13. unpack_simple_colocation
  14. unpack_colocation_tags
  15. pcmk__unpack_colocation
  16. mark_action_blocked
  17. pcmk__block_colocation_dependents
  18. get_resource_for_role
  19. pcmk__colocation_affects
  20. pcmk__apply_coloc_to_scores
  21. pcmk__apply_coloc_to_priority
  22. best_node_score_matching_attr
  23. allowed_on_one
  24. add_node_scores_matching_attr
  25. pcmk__add_colocated_node_scores
  26. pcmk__add_dependent_scores
  27. pcmk__colocation_intersect_nodes
  28. pcmk__with_this_colocations
  29. pcmk__this_with_colocations

   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 "crm/common/util.h"
  21 #include "crm/common/xml_internal.h"
  22 #include "crm/common/xml.h"
  23 #include "libpacemaker_private.h"
  24 
  25 // Used to temporarily mark a node as unusable
  26 #define INFINITY_HACK   (PCMK_SCORE_INFINITY * -100)
  27 
  28 /*!
  29  * \internal
  30  * \brief Compare two colocations according to priority
  31  *
  32  * Compare two colocations according to the order in which they should be
  33  * considered, based on either their dependent resources or their primary
  34  * resources -- preferring (in order):
  35  *  * Colocation that is not \c NULL
  36  *  * Colocation whose resource has higher priority
  37  *  * Colocation whose resource is of a higher-level variant
  38  *    (bundle > clone > group > primitive)
  39  *  * Colocation whose resource is promotable, if both are clones
  40  *  * Colocation whose resource has lower ID in lexicographic order
  41  *
  42  * \param[in] colocation1  First colocation to compare
  43  * \param[in] colocation2  Second colocation to compare
  44  * \param[in] dependent    If \c true, compare colocations by dependent
  45  *                         priority; otherwise compare them by primary priority
  46  *
  47  * \return A negative number if \p colocation1 should be considered first,
  48  *         a positive number if \p colocation2 should be considered first,
  49  *         or 0 if order doesn't matter
  50  */
  51 static gint
  52 cmp_colocation_priority(const pcmk__colocation_t *colocation1,
     /* [previous][next][first][last][top][bottom][index][help] */
  53                         const pcmk__colocation_t *colocation2, bool dependent)
  54 {
  55     const pcmk_resource_t *rsc1 = NULL;
  56     const pcmk_resource_t *rsc2 = NULL;
  57 
  58     if (colocation1 == NULL) {
  59         return 1;
  60     }
  61     if (colocation2 == NULL) {
  62         return -1;
  63     }
  64 
  65     if (dependent) {
  66         rsc1 = colocation1->dependent;
  67         rsc2 = colocation2->dependent;
  68         CRM_ASSERT(colocation1->primary != NULL);
  69     } else {
  70         rsc1 = colocation1->primary;
  71         rsc2 = colocation2->primary;
  72         CRM_ASSERT(colocation1->dependent != NULL);
  73     }
  74     CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL));
  75 
  76     if (rsc1->priority > rsc2->priority) {
  77         return -1;
  78     }
  79     if (rsc1->priority < rsc2->priority) {
  80         return 1;
  81     }
  82 
  83     // Process clones before primitives and groups
  84     if (rsc1->variant > rsc2->variant) {
  85         return -1;
  86     }
  87     if (rsc1->variant < rsc2->variant) {
  88         return 1;
  89     }
  90 
  91     /* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable
  92      * clones (probably unnecessary, but avoids having to update regression
  93      * tests)
  94      */
  95     if (pcmk__is_clone(rsc1)) {
  96         if (pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
  97             && !pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
  98             return -1;
  99         }
 100         if (!pcmk_is_set(rsc1->flags, pcmk_rsc_promotable)
 101             && pcmk_is_set(rsc2->flags, pcmk_rsc_promotable)) {
 102             return 1;
 103         }
 104     }
 105 
 106     return strcmp(rsc1->id, rsc2->id);
 107 }
 108 
 109 /*!
 110  * \internal
 111  * \brief Compare two colocations according to priority based on dependents
 112  *
 113  * Compare two colocations according to the order in which they should be
 114  * considered, based on their dependent resources -- preferring (in order):
 115  *  * Colocation that is not \c NULL
 116  *  * Colocation whose resource has higher priority
 117  *  * Colocation whose resource is of a higher-level variant
 118  *    (bundle > clone > group > primitive)
 119  *  * Colocation whose resource is promotable, if both are clones
 120  *  * Colocation whose resource has lower ID in lexicographic order
 121  *
 122  * \param[in] a  First colocation to compare
 123  * \param[in] b  Second colocation to compare
 124  *
 125  * \return A negative number if \p a should be considered first,
 126  *         a positive number if \p b should be considered first,
 127  *         or 0 if order doesn't matter
 128  */
 129 static gint
 130 cmp_dependent_priority(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 131 {
 132     return cmp_colocation_priority(a, b, true);
 133 }
 134 
 135 /*!
 136  * \internal
 137  * \brief Compare two colocations according to priority based on primaries
 138  *
 139  * Compare two colocations according to the order in which they should be
 140  * considered, based on their primary resources -- preferring (in order):
 141  *  * Colocation that is not \c NULL
 142  *  * Colocation whose primary has higher priority
 143  *  * Colocation whose primary is of a higher-level variant
 144  *    (bundle > clone > group > primitive)
 145  *  * Colocation whose primary is promotable, if both are clones
 146  *  * Colocation whose primary has lower ID in lexicographic order
 147  *
 148  * \param[in] a  First colocation to compare
 149  * \param[in] b  Second colocation to compare
 150  *
 151  * \return A negative number if \p a should be considered first,
 152  *         a positive number if \p b should be considered first,
 153  *         or 0 if order doesn't matter
 154  */
 155 static gint
 156 cmp_primary_priority(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
 157 {
 158     return cmp_colocation_priority(a, b, false);
 159 }
 160 
 161 /*!
 162  * \internal
 163  * \brief Add a "this with" colocation constraint to a sorted list
 164  *
 165  * \param[in,out] list        List of constraints to add \p colocation to
 166  * \param[in]     colocation  Colocation constraint to add to \p list
 167  * \param[in]     rsc         Resource whose colocations we're getting (for
 168  *                            logging only)
 169  *
 170  * \note The list will be sorted using cmp_primary_priority().
 171  */
 172 void
 173 pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation,
     /* [previous][next][first][last][top][bottom][index][help] */
 174                     const pcmk_resource_t *rsc)
 175 {
 176     CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
 177 
 178     pcmk__rsc_trace(rsc,
 179                     "Adding colocation %s (%s with %s using %s @%s) to "
 180                     "'this with' list for %s",
 181                     colocation->id, colocation->dependent->id,
 182                     colocation->primary->id, colocation->node_attribute,
 183                     pcmk_readable_score(colocation->score), rsc->id);
 184     *list = g_list_insert_sorted(*list, (gpointer) colocation,
 185                                  cmp_primary_priority);
 186 }
 187 
 188 /*!
 189  * \internal
 190  * \brief Add a list of "this with" colocation constraints to a list
 191  *
 192  * \param[in,out] list      List of constraints to add \p addition to
 193  * \param[in]     addition  List of colocation constraints to add to \p list
 194  * \param[in]     rsc       Resource whose colocations we're getting (for
 195  *                          logging only)
 196  *
 197  * \note The lists must be pre-sorted by cmp_primary_priority().
 198  */
 199 void
 200 pcmk__add_this_with_list(GList **list, GList *addition,
     /* [previous][next][first][last][top][bottom][index][help] */
 201                          const pcmk_resource_t *rsc)
 202 {
 203     CRM_ASSERT((list != NULL) && (rsc != NULL));
 204 
 205     pcmk__if_tracing(
 206         {}, // Always add each colocation individually if tracing
 207         {
 208             if (*list == NULL) {
 209                 // Trivial case for efficiency if not tracing
 210                 *list = g_list_copy(addition);
 211                 return;
 212             }
 213         }
 214     );
 215 
 216     for (const GList *iter = addition; iter != NULL; iter = iter->next) {
 217         pcmk__add_this_with(list, addition->data, rsc);
 218     }
 219 }
 220 
 221 /*!
 222  * \internal
 223  * \brief Add a "with this" colocation constraint to a sorted list
 224  *
 225  * \param[in,out] list        List of constraints to add \p colocation to
 226  * \param[in]     colocation  Colocation constraint to add to \p list
 227  * \param[in]     rsc         Resource whose colocations we're getting (for
 228  *                            logging only)
 229  *
 230  * \note The list will be sorted using cmp_dependent_priority().
 231  */
 232 void
 233 pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation,
     /* [previous][next][first][last][top][bottom][index][help] */
 234                     const pcmk_resource_t *rsc)
 235 {
 236     CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL));
 237 
 238     pcmk__rsc_trace(rsc,
 239                     "Adding colocation %s (%s with %s using %s @%s) to "
 240                     "'with this' list for %s",
 241                     colocation->id, colocation->dependent->id,
 242                     colocation->primary->id, colocation->node_attribute,
 243                     pcmk_readable_score(colocation->score), rsc->id);
 244     *list = g_list_insert_sorted(*list, (gpointer) colocation,
 245                                  cmp_dependent_priority);
 246 }
 247 
 248 /*!
 249  * \internal
 250  * \brief Add a list of "with this" colocation constraints to a list
 251  *
 252  * \param[in,out] list      List of constraints to add \p addition to
 253  * \param[in]     addition  List of colocation constraints to add to \p list
 254  * \param[in]     rsc       Resource whose colocations we're getting (for
 255  *                          logging only)
 256  *
 257  * \note The lists must be pre-sorted by cmp_dependent_priority().
 258  */
 259 void
 260 pcmk__add_with_this_list(GList **list, GList *addition,
     /* [previous][next][first][last][top][bottom][index][help] */
 261                          const pcmk_resource_t *rsc)
 262 {
 263     CRM_ASSERT((list != NULL) && (rsc != NULL));
 264 
 265     pcmk__if_tracing(
 266         {}, // Always add each colocation individually if tracing
 267         {
 268             if (*list == NULL) {
 269                 // Trivial case for efficiency if not tracing
 270                 *list = g_list_copy(addition);
 271                 return;
 272             }
 273         }
 274     );
 275 
 276     for (const GList *iter = addition; iter != NULL; iter = iter->next) {
 277         pcmk__add_with_this(list, addition->data, rsc);
 278     }
 279 }
 280 
 281 /*!
 282  * \internal
 283  * \brief Add orderings necessary for an anti-colocation constraint
 284  *
 285  * \param[in,out] first_rsc   One resource in an anti-colocation
 286  * \param[in]     first_role  Anti-colocation role of \p first_rsc
 287  * \param[in]     then_rsc    Other resource in the anti-colocation
 288  * \param[in]     then_role   Anti-colocation role of \p then_rsc
 289  */
 290 static void
 291 anti_colocation_order(pcmk_resource_t *first_rsc, int first_role,
     /* [previous][next][first][last][top][bottom][index][help] */
 292                       pcmk_resource_t *then_rsc, int then_role)
 293 {
 294     const char *first_tasks[] = { NULL, NULL };
 295     const char *then_tasks[] = { NULL, NULL };
 296 
 297     /* Actions to make first_rsc lose first_role */
 298     if (first_role == pcmk_role_promoted) {
 299         first_tasks[0] = PCMK_ACTION_DEMOTE;
 300 
 301     } else {
 302         first_tasks[0] = PCMK_ACTION_STOP;
 303 
 304         if (first_role == pcmk_role_unpromoted) {
 305             first_tasks[1] = PCMK_ACTION_PROMOTE;
 306         }
 307     }
 308 
 309     /* Actions to make then_rsc gain then_role */
 310     if (then_role == pcmk_role_promoted) {
 311         then_tasks[0] = PCMK_ACTION_PROMOTE;
 312 
 313     } else {
 314         then_tasks[0] = PCMK_ACTION_START;
 315 
 316         if (then_role == pcmk_role_unpromoted) {
 317             then_tasks[1] = PCMK_ACTION_DEMOTE;
 318         }
 319     }
 320 
 321     for (int first_lpc = 0;
 322          (first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) {
 323 
 324         for (int then_lpc = 0;
 325              (then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) {
 326 
 327             pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc],
 328                                          then_rsc, then_tasks[then_lpc],
 329                                          pcmk__ar_if_required_on_same_node);
 330         }
 331     }
 332 }
 333 
 334 /*!
 335  * \internal
 336  * \brief Add a new colocation constraint to scheduler data
 337  *
 338  * \param[in]     id              XML ID for this constraint
 339  * \param[in]     node_attr       Colocate by this attribute (NULL for #uname)
 340  * \param[in]     score           Constraint score
 341  * \param[in,out] dependent       Resource to be colocated
 342  * \param[in,out] primary         Resource to colocate \p dependent with
 343  * \param[in]     dependent_role  Current role of \p dependent
 344  * \param[in]     primary_role    Current role of \p primary
 345  * \param[in]     flags           Group of enum pcmk__coloc_flags
 346  */
 347 void
 348 pcmk__new_colocation(const char *id, const char *node_attr, int score,
     /* [previous][next][first][last][top][bottom][index][help] */
 349                      pcmk_resource_t *dependent, pcmk_resource_t *primary,
 350                      const char *dependent_role, const char *primary_role,
 351                      uint32_t flags)
 352 {
 353     pcmk__colocation_t *new_con = NULL;
 354 
 355     CRM_CHECK(id != NULL, return);
 356 
 357     if ((dependent == NULL) || (primary == NULL)) {
 358         pcmk__config_err("Ignoring colocation '%s' because resource "
 359                          "does not exist", id);
 360         return;
 361     }
 362 
 363     if (score == 0) {
 364         pcmk__rsc_trace(dependent,
 365                         "Ignoring colocation '%s' (%s with %s) because score is 0",
 366                         id, dependent->id, primary->id);
 367         return;
 368     }
 369 
 370     new_con = pcmk__assert_alloc(1, sizeof(pcmk__colocation_t));
 371 
 372     if (pcmk__str_eq(dependent_role, PCMK_ROLE_STARTED,
 373                      pcmk__str_null_matches|pcmk__str_casei)) {
 374         dependent_role = PCMK__ROLE_UNKNOWN;
 375     }
 376 
 377     if (pcmk__str_eq(primary_role, PCMK_ROLE_STARTED,
 378                      pcmk__str_null_matches|pcmk__str_casei)) {
 379         primary_role = PCMK__ROLE_UNKNOWN;
 380     }
 381 
 382     new_con->id = id;
 383     new_con->dependent = dependent;
 384     new_con->primary = primary;
 385     new_con->score = score;
 386     new_con->dependent_role = pcmk_parse_role(dependent_role);
 387     new_con->primary_role = pcmk_parse_role(primary_role);
 388     new_con->node_attribute = pcmk__s(node_attr, CRM_ATTR_UNAME);
 389     new_con->flags = flags;
 390 
 391     pcmk__add_this_with(&(dependent->rsc_cons), new_con, dependent);
 392     pcmk__add_with_this(&(primary->rsc_cons_lhs), new_con, primary);
 393 
 394     dependent->cluster->colocation_constraints = g_list_prepend(
 395         dependent->cluster->colocation_constraints, new_con);
 396 
 397     if (score <= -PCMK_SCORE_INFINITY) {
 398         anti_colocation_order(dependent, new_con->dependent_role, primary,
 399                               new_con->primary_role);
 400         anti_colocation_order(primary, new_con->primary_role, dependent,
 401                               new_con->dependent_role);
 402     }
 403 }
 404 
 405 /*!
 406  * \internal
 407  * \brief Return the boolean influence corresponding to configuration
 408  *
 409  * \param[in] coloc_id     Colocation XML ID (for error logging)
 410  * \param[in] rsc          Resource involved in constraint (for default)
 411  * \param[in] influence_s  String value of \c PCMK_XA_INFLUENCE option
 412  *
 413  * \return \c pcmk__coloc_influence if string evaluates true, or string is
 414  *         \c NULL or invalid and resource's \c PCMK_META_CRITICAL option
 415  *         evaluates true, otherwise \c pcmk__coloc_none
 416  */
 417 static uint32_t
 418 unpack_influence(const char *coloc_id, const pcmk_resource_t *rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
 419                  const char *influence_s)
 420 {
 421     if (influence_s != NULL) {
 422         int influence_i = 0;
 423 
 424         if (crm_str_to_boolean(influence_s, &influence_i) < 0) {
 425             pcmk__config_err("Constraint '%s' has invalid value for "
 426                              PCMK_XA_INFLUENCE " (using default)",
 427                              coloc_id);
 428         } else {
 429             return (influence_i == 0)? pcmk__coloc_none : pcmk__coloc_influence;
 430         }
 431     }
 432     if (pcmk_is_set(rsc->flags, pcmk_rsc_critical)) {
 433         return pcmk__coloc_influence;
 434     }
 435     return pcmk__coloc_none;
 436 }
 437 
 438 static void
 439 unpack_colocation_set(xmlNode *set, int score, const char *coloc_id,
     /* [previous][next][first][last][top][bottom][index][help] */
 440                       const char *influence_s, pcmk_scheduler_t *scheduler)
 441 {
 442     xmlNode *xml_rsc = NULL;
 443     pcmk_resource_t *other = NULL;
 444     pcmk_resource_t *resource = NULL;
 445     const char *set_id = pcmk__xe_id(set);
 446     const char *role = crm_element_value(set, PCMK_XA_ROLE);
 447     bool with_previous = false;
 448     int local_score = score;
 449     bool sequential = false;
 450     uint32_t flags = pcmk__coloc_none;
 451     const char *xml_rsc_id = NULL;
 452     const char *score_s = crm_element_value(set, PCMK_XA_SCORE);
 453 
 454     if (score_s) {
 455         local_score = char2score(score_s);
 456     }
 457     if (local_score == 0) {
 458         crm_trace("Ignoring colocation '%s' for set '%s' because score is 0",
 459                   coloc_id, set_id);
 460         return;
 461     }
 462 
 463     /* @COMPAT The deprecated PCMK__XA_ORDERING attribute specifies whether
 464      * resources in a positive-score set are colocated with the previous or next
 465      * resource.
 466      */
 467     if (pcmk__str_eq(crm_element_value(set, PCMK__XA_ORDERING),
 468                      PCMK__VALUE_GROUP,
 469                      pcmk__str_null_matches|pcmk__str_casei)) {
 470         with_previous = true;
 471     } else {
 472         pcmk__warn_once(pcmk__wo_set_ordering,
 473                         "Support for '" PCMK__XA_ORDERING "' other than"
 474                         " '" PCMK__VALUE_GROUP "' in " PCMK_XE_RESOURCE_SET
 475                         " (such as %s) is deprecated and will be removed in a"
 476                         " future release",
 477                         set_id);
 478     }
 479 
 480     if ((pcmk__xe_get_bool_attr(set, PCMK_XA_SEQUENTIAL,
 481                                 &sequential) == pcmk_rc_ok)
 482         && !sequential) {
 483         return;
 484     }
 485 
 486     if (local_score > 0) {
 487         for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL,
 488                                             NULL);
 489              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 490 
 491             xml_rsc_id = pcmk__xe_id(xml_rsc);
 492             resource = pcmk__find_constraint_resource(scheduler->resources,
 493                                                       xml_rsc_id);
 494             if (resource == NULL) {
 495                 // Should be possible only with validation disabled
 496                 pcmk__config_err("Ignoring %s and later resources in set %s: "
 497                                  "No such resource", xml_rsc_id, set_id);
 498                 return;
 499             }
 500             if (other != NULL) {
 501                 flags = pcmk__coloc_explicit
 502                         | unpack_influence(coloc_id, resource, influence_s);
 503                 if (with_previous) {
 504                     pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
 505                                     resource->id, other->id, set_id);
 506                     pcmk__new_colocation(set_id, NULL, local_score, resource,
 507                                          other, role, role, flags);
 508                 } else {
 509                     pcmk__rsc_trace(resource, "Colocating %s with %s in set %s",
 510                                     other->id, resource->id, set_id);
 511                     pcmk__new_colocation(set_id, NULL, local_score, other,
 512                                          resource, role, role, flags);
 513                 }
 514             }
 515             other = resource;
 516         }
 517 
 518     } else {
 519         /* Anti-colocating with every prior resource is
 520          * the only way to ensure the intuitive result
 521          * (i.e. that no one in the set can run with anyone else in the set)
 522          */
 523 
 524         for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL,
 525                                             NULL);
 526              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 527 
 528             xmlNode *xml_rsc_with = NULL;
 529 
 530             xml_rsc_id = pcmk__xe_id(xml_rsc);
 531             resource = pcmk__find_constraint_resource(scheduler->resources,
 532                                                       xml_rsc_id);
 533             if (resource == NULL) {
 534                 // Should be possible only with validation disabled
 535                 pcmk__config_err("Ignoring %s and later resources in set %s: "
 536                                  "No such resource", xml_rsc_id, set_id);
 537                 return;
 538             }
 539             flags = pcmk__coloc_explicit
 540                     | unpack_influence(coloc_id, resource, influence_s);
 541             for (xml_rsc_with = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF,
 542                                                      NULL, NULL);
 543                  xml_rsc_with != NULL;
 544                  xml_rsc_with = pcmk__xe_next_same(xml_rsc_with)) {
 545 
 546                 xml_rsc_id = pcmk__xe_id(xml_rsc_with);
 547                 if (pcmk__str_eq(resource->id, xml_rsc_id, pcmk__str_none)) {
 548                     break;
 549                 }
 550                 other = pcmk__find_constraint_resource(scheduler->resources,
 551                                                        xml_rsc_id);
 552                 CRM_ASSERT(other != NULL); // We already processed it
 553                 pcmk__new_colocation(set_id, NULL, local_score,
 554                                      resource, other, role, role, flags);
 555             }
 556         }
 557     }
 558 }
 559 
 560 /*!
 561  * \internal
 562  * \brief Colocate two resource sets relative to each other
 563  *
 564  * \param[in]     id           Colocation XML ID
 565  * \param[in]     set1         Dependent set
 566  * \param[in]     set2         Primary set
 567  * \param[in]     score        Colocation score
 568  * \param[in]     influence_s  Value of colocation's \c PCMK_XA_INFLUENCE
 569  *                             attribute
 570  * \param[in,out] scheduler    Scheduler data
 571  */
 572 static void
 573 colocate_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2,
     /* [previous][next][first][last][top][bottom][index][help] */
 574                   int score, const char *influence_s,
 575                   pcmk_scheduler_t *scheduler)
 576 {
 577     xmlNode *xml_rsc = NULL;
 578     pcmk_resource_t *rsc_1 = NULL;
 579     pcmk_resource_t *rsc_2 = NULL;
 580 
 581     const char *xml_rsc_id = NULL;
 582     const char *role_1 = crm_element_value(set1, PCMK_XA_ROLE);
 583     const char *role_2 = crm_element_value(set2, PCMK_XA_ROLE);
 584 
 585     int rc = pcmk_rc_ok;
 586     bool sequential = false;
 587     uint32_t flags = pcmk__coloc_none;
 588 
 589     if (score == 0) {
 590         crm_trace("Ignoring colocation '%s' between sets %s and %s "
 591                   "because score is 0",
 592                   id, pcmk__xe_id(set1), pcmk__xe_id(set2));
 593         return;
 594     }
 595 
 596     rc = pcmk__xe_get_bool_attr(set1, PCMK_XA_SEQUENTIAL, &sequential);
 597     if ((rc != pcmk_rc_ok) || sequential) {
 598         // Get the first one
 599         xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL);
 600         if (xml_rsc != NULL) {
 601             xml_rsc_id = pcmk__xe_id(xml_rsc);
 602             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
 603                                                    xml_rsc_id);
 604             if (rsc_1 == NULL) {
 605                 // Should be possible only with validation disabled
 606                 pcmk__config_err("Ignoring colocation of set %s with set %s "
 607                                  "because first resource %s not found",
 608                                  pcmk__xe_id(set1), pcmk__xe_id(set2),
 609                                  xml_rsc_id);
 610                 return;
 611             }
 612         }
 613     }
 614 
 615     rc = pcmk__xe_get_bool_attr(set2, PCMK_XA_SEQUENTIAL, &sequential);
 616     if ((rc != pcmk_rc_ok) || sequential) {
 617         // Get the last one
 618         for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
 619                                             NULL);
 620              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 621 
 622             xml_rsc_id = pcmk__xe_id(xml_rsc);
 623         }
 624         rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
 625                                                xml_rsc_id);
 626         if (rsc_2 == NULL) {
 627             // Should be possible only with validation disabled
 628             pcmk__config_err("Ignoring colocation of set %s with set %s "
 629                              "because last resource %s not found",
 630                              pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id);
 631             return;
 632         }
 633     }
 634 
 635     if ((rsc_1 != NULL) && (rsc_2 != NULL)) { // Both sets are sequential
 636         flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
 637         pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2,
 638                              flags);
 639 
 640     } else if (rsc_1 != NULL) { // Only set1 is sequential
 641         flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s);
 642         for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL,
 643                                             NULL);
 644              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 645 
 646             xml_rsc_id = pcmk__xe_id(xml_rsc);
 647             rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
 648                                                    xml_rsc_id);
 649             if (rsc_2 == NULL) {
 650                 // Should be possible only with validation disabled
 651                 pcmk__config_err("Ignoring set %s colocation with resource %s "
 652                                  "in set %s: No such resource",
 653                                  pcmk__xe_id(set1), xml_rsc_id,
 654                                  pcmk__xe_id(set2));
 655                 continue;
 656             }
 657             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
 658                                  role_2, flags);
 659         }
 660 
 661     } else if (rsc_2 != NULL) { // Only set2 is sequential
 662         for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
 663                                             NULL);
 664              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 665 
 666             xml_rsc_id = pcmk__xe_id(xml_rsc);
 667             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
 668                                                    xml_rsc_id);
 669             if (rsc_1 == NULL) {
 670                 // Should be possible only with validation disabled
 671                 pcmk__config_err("Ignoring colocation of set %s resource %s "
 672                                  "with set %s: No such resource",
 673                                  pcmk__xe_id(set1), xml_rsc_id,
 674                                  pcmk__xe_id(set2));
 675                 continue;
 676             }
 677             flags = pcmk__coloc_explicit
 678                     | unpack_influence(id, rsc_1, influence_s);
 679             pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1,
 680                                  role_2, flags);
 681         }
 682 
 683     } else { // Neither set is sequential
 684         for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL,
 685                                             NULL);
 686              xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) {
 687 
 688             xmlNode *xml_rsc_2 = NULL;
 689 
 690             xml_rsc_id = pcmk__xe_id(xml_rsc);
 691             rsc_1 = pcmk__find_constraint_resource(scheduler->resources,
 692                                                    xml_rsc_id);
 693             if (rsc_1 == NULL) {
 694                 // Should be possible only with validation disabled
 695                 pcmk__config_err("Ignoring colocation of set %s resource %s "
 696                                  "with set %s: No such resource",
 697                                  pcmk__xe_id(set1), xml_rsc_id,
 698                                  pcmk__xe_id(set2));
 699                 continue;
 700             }
 701 
 702             flags = pcmk__coloc_explicit
 703                     | unpack_influence(id, rsc_1, influence_s);
 704             for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF,
 705                                                   NULL, NULL);
 706                  xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) {
 707 
 708                 xml_rsc_id = pcmk__xe_id(xml_rsc_2);
 709                 rsc_2 = pcmk__find_constraint_resource(scheduler->resources,
 710                                                        xml_rsc_id);
 711                 if (rsc_2 == NULL) {
 712                     // Should be possible only with validation disabled
 713                     pcmk__config_err("Ignoring colocation of set %s resource "
 714                                      "%s with set %s resource %s: No such "
 715                                      "resource",
 716                                      pcmk__xe_id(set1), pcmk__xe_id(xml_rsc),
 717                                      pcmk__xe_id(set2), xml_rsc_id);
 718                     continue;
 719                 }
 720                 pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2,
 721                                      role_1, role_2, flags);
 722             }
 723         }
 724     }
 725 }
 726 
 727 static void
 728 unpack_simple_colocation(xmlNode *xml_obj, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 729                          const char *influence_s, pcmk_scheduler_t *scheduler)
 730 {
 731     int score_i = 0;
 732     uint32_t flags = pcmk__coloc_none;
 733 
 734     const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE);
 735     const char *dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
 736     const char *primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
 737     const char *dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 738     const char *primary_role = crm_element_value(xml_obj,
 739                                                  PCMK_XA_WITH_RSC_ROLE);
 740     const char *attr = crm_element_value(xml_obj, PCMK_XA_NODE_ATTRIBUTE);
 741 
 742     const char *primary_instance = NULL;
 743     const char *dependent_instance = NULL;
 744     pcmk_resource_t *primary = NULL;
 745     pcmk_resource_t *dependent = NULL;
 746 
 747     primary = pcmk__find_constraint_resource(scheduler->resources, primary_id);
 748     dependent = pcmk__find_constraint_resource(scheduler->resources,
 749                                                dependent_id);
 750 
 751     // @COMPAT: Deprecated since 2.1.5
 752     primary_instance = crm_element_value(xml_obj, PCMK__XA_WITH_RSC_INSTANCE);
 753     dependent_instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE);
 754     if (dependent_instance != NULL) {
 755         pcmk__warn_once(pcmk__wo_coloc_inst,
 756                         "Support for " PCMK__XA_RSC_INSTANCE " is deprecated "
 757                         "and will be removed in a future release");
 758     }
 759     if (primary_instance != NULL) {
 760         pcmk__warn_once(pcmk__wo_coloc_inst,
 761                         "Support for " PCMK__XA_WITH_RSC_INSTANCE " is "
 762                         "deprecated and will be removed in a future release");
 763     }
 764 
 765     if (dependent == NULL) {
 766         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 767                          "does not exist", id, dependent_id);
 768         return;
 769 
 770     } else if (primary == NULL) {
 771         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 772                          "does not exist", id, primary_id);
 773         return;
 774 
 775     } else if ((dependent_instance != NULL) && !pcmk__is_clone(dependent)) {
 776         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 777                          "is not a clone but instance '%s' was requested",
 778                          id, dependent_id, dependent_instance);
 779         return;
 780 
 781     } else if ((primary_instance != NULL) && !pcmk__is_clone(primary)) {
 782         pcmk__config_err("Ignoring constraint '%s' because resource '%s' "
 783                          "is not a clone but instance '%s' was requested",
 784                          id, primary_id, primary_instance);
 785         return;
 786     }
 787 
 788     if (dependent_instance != NULL) {
 789         dependent = find_clone_instance(dependent, dependent_instance);
 790         if (dependent == NULL) {
 791             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
 792                               "does not have an instance '%s'",
 793                               id, dependent_id, dependent_instance);
 794             return;
 795         }
 796     }
 797 
 798     if (primary_instance != NULL) {
 799         primary = find_clone_instance(primary, primary_instance);
 800         if (primary == NULL) {
 801             pcmk__config_warn("Ignoring constraint '%s' because resource '%s' "
 802                               "does not have an instance '%s'",
 803                               id, primary_id, primary_instance);
 804             return;
 805         }
 806     }
 807 
 808     if (pcmk__xe_attr_is_true(xml_obj, PCMK_XA_SYMMETRICAL)) {
 809         pcmk__config_warn("The colocation constraint "
 810                           "'" PCMK_XA_SYMMETRICAL "' attribute has been "
 811                           "removed");
 812     }
 813 
 814     if (score) {
 815         score_i = char2score(score);
 816     }
 817 
 818     flags = pcmk__coloc_explicit | unpack_influence(id, dependent, influence_s);
 819     pcmk__new_colocation(id, attr, score_i, dependent, primary,
 820                          dependent_role, primary_role, flags);
 821 }
 822 
 823 // \return Standard Pacemaker return code
 824 static int
 825 unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml,
     /* [previous][next][first][last][top][bottom][index][help] */
 826                        pcmk_scheduler_t *scheduler)
 827 {
 828     const char *id = NULL;
 829     const char *dependent_id = NULL;
 830     const char *primary_id = NULL;
 831     const char *dependent_role = NULL;
 832     const char *primary_role = NULL;
 833 
 834     pcmk_resource_t *dependent = NULL;
 835     pcmk_resource_t *primary = NULL;
 836 
 837     pcmk_tag_t *dependent_tag = NULL;
 838     pcmk_tag_t *primary_tag = NULL;
 839 
 840     xmlNode *dependent_set = NULL;
 841     xmlNode *primary_set = NULL;
 842     bool any_sets = false;
 843 
 844     *expanded_xml = NULL;
 845 
 846     CRM_CHECK(xml_obj != NULL, return EINVAL);
 847 
 848     id = pcmk__xe_id(xml_obj);
 849     if (id == NULL) {
 850         pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID,
 851                          xml_obj->name);
 852         return pcmk_rc_unpack_error;
 853     }
 854 
 855     // Check whether there are any resource sets with template or tag references
 856     *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler);
 857     if (*expanded_xml != NULL) {
 858         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
 859         return pcmk_rc_ok;
 860     }
 861 
 862     dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC);
 863     primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC);
 864     if ((dependent_id == NULL) || (primary_id == NULL)) {
 865         return pcmk_rc_ok;
 866     }
 867 
 868     if (!pcmk__valid_resource_or_tag(scheduler, dependent_id, &dependent,
 869                                      &dependent_tag)) {
 870         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
 871                          "valid resource or tag", id, dependent_id);
 872         return pcmk_rc_unpack_error;
 873     }
 874 
 875     if (!pcmk__valid_resource_or_tag(scheduler, primary_id, &primary,
 876                                      &primary_tag)) {
 877         pcmk__config_err("Ignoring constraint '%s' because '%s' is not a "
 878                          "valid resource or tag", id, primary_id);
 879         return pcmk_rc_unpack_error;
 880     }
 881 
 882     if ((dependent != NULL) && (primary != NULL)) {
 883         /* Neither side references any template/tag. */
 884         return pcmk_rc_ok;
 885     }
 886 
 887     if ((dependent_tag != NULL) && (primary_tag != NULL)) {
 888         // A colocation constraint between two templates/tags makes no sense
 889         pcmk__config_err("Ignoring constraint '%s' because two templates or "
 890                          "tags cannot be colocated", id);
 891         return pcmk_rc_unpack_error;
 892     }
 893 
 894     dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE);
 895     primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE);
 896 
 897     *expanded_xml = pcmk__xml_copy(NULL, xml_obj);
 898 
 899     /* Convert dependent's template/tag reference into constraint
 900      * PCMK_XE_RESOURCE_SET
 901      */
 902     if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, PCMK_XA_RSC, true,
 903                           scheduler)) {
 904         free_xml(*expanded_xml);
 905         *expanded_xml = NULL;
 906         return pcmk_rc_unpack_error;
 907     }
 908 
 909     if (dependent_set != NULL) {
 910         if (dependent_role != NULL) {
 911             /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
 912              * PCMK_XA_ROLE
 913              */
 914             crm_xml_add(dependent_set, PCMK_XA_ROLE, dependent_role);
 915             pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE);
 916         }
 917         any_sets = true;
 918     }
 919 
 920     /* Convert primary's template/tag reference into constraint
 921      * PCMK_XE_RESOURCE_SET
 922      */
 923     if (!pcmk__tag_to_set(*expanded_xml, &primary_set, PCMK_XA_WITH_RSC, true,
 924                           scheduler)) {
 925         free_xml(*expanded_xml);
 926         *expanded_xml = NULL;
 927         return pcmk_rc_unpack_error;
 928     }
 929 
 930     if (primary_set != NULL) {
 931         if (primary_role != NULL) {
 932             /* Move PCMK_XA_WITH_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as
 933              * PCMK_XA_ROLE
 934              */
 935             crm_xml_add(primary_set, PCMK_XA_ROLE, primary_role);
 936             pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_WITH_RSC_ROLE);
 937         }
 938         any_sets = true;
 939     }
 940 
 941     if (any_sets) {
 942         crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION);
 943     } else {
 944         free_xml(*expanded_xml);
 945         *expanded_xml = NULL;
 946     }
 947 
 948     return pcmk_rc_ok;
 949 }
 950 
 951 /*!
 952  * \internal
 953  * \brief Parse a colocation constraint from XML into scheduler data
 954  *
 955  * \param[in,out] xml_obj    Colocation constraint XML to unpack
 956  * \param[in,out] scheduler  Scheduler data to add constraint to
 957  */
 958 void
 959 pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler)
     /* [previous][next][first][last][top][bottom][index][help] */
 960 {
 961     int score_i = 0;
 962     xmlNode *set = NULL;
 963     xmlNode *last = NULL;
 964 
 965     xmlNode *orig_xml = NULL;
 966     xmlNode *expanded_xml = NULL;
 967 
 968     const char *id = crm_element_value(xml_obj, PCMK_XA_ID);
 969     const char *score = NULL;
 970     const char *influence_s = NULL;
 971 
 972     if (pcmk__str_empty(id)) {
 973         pcmk__config_err("Ignoring " PCMK_XE_RSC_COLOCATION
 974                          " without " CRM_ATTR_ID);
 975         return;
 976     }
 977 
 978     if (unpack_colocation_tags(xml_obj, &expanded_xml,
 979                                scheduler) != pcmk_rc_ok) {
 980         return;
 981     }
 982     if (expanded_xml != NULL) {
 983         orig_xml = xml_obj;
 984         xml_obj = expanded_xml;
 985     }
 986 
 987     score = crm_element_value(xml_obj, PCMK_XA_SCORE);
 988     if (score != NULL) {
 989         score_i = char2score(score);
 990     }
 991     influence_s = crm_element_value(xml_obj, PCMK_XA_INFLUENCE);
 992 
 993     for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL);
 994          set != NULL; set = pcmk__xe_next_same(set)) {
 995 
 996         set = expand_idref(set, scheduler->input);
 997         if (set == NULL) { // Configuration error, message already logged
 998             if (expanded_xml != NULL) {
 999                 free_xml(expanded_xml);
1000             }
1001             return;
1002         }
1003 
1004         if (pcmk__str_empty(pcmk__xe_id(set))) {
1005             pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET
1006                              " without " CRM_ATTR_ID);
1007             continue;
1008         }
1009         unpack_colocation_set(set, score_i, id, influence_s, scheduler);
1010 
1011         if (last != NULL) {
1012             colocate_rsc_sets(id, last, set, score_i, influence_s, scheduler);
1013         }
1014         last = set;
1015     }
1016 
1017     if (expanded_xml) {
1018         free_xml(expanded_xml);
1019         xml_obj = orig_xml;
1020     }
1021 
1022     if (last == NULL) {
1023         unpack_simple_colocation(xml_obj, id, influence_s, scheduler);
1024     }
1025 }
1026 
1027 /*!
1028  * \internal
1029  * \brief Make actions of a given type unrunnable for a given resource
1030  *
1031  * \param[in,out] rsc     Resource whose actions should be blocked
1032  * \param[in]     task    Name of action to block
1033  * \param[in]     reason  Unrunnable start action causing the block
1034  */
1035 static void
1036 mark_action_blocked(pcmk_resource_t *rsc, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
1037                     const pcmk_resource_t *reason)
1038 {
1039     GList *iter = NULL;
1040     char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
1041 
1042     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
1043         pcmk_action_t *action = iter->data;
1044 
1045         if (pcmk_is_set(action->flags, pcmk_action_runnable)
1046             && pcmk__str_eq(action->task, task, pcmk__str_none)) {
1047 
1048             pcmk__clear_action_flags(action, pcmk_action_runnable);
1049             pe_action_set_reason(action, reason_text, false);
1050             pcmk__block_colocation_dependents(action);
1051             pcmk__update_action_for_orderings(action, rsc->cluster);
1052         }
1053     }
1054 
1055     // If parent resource can't perform an action, neither can any children
1056     for (iter = rsc->children; iter != NULL; iter = iter->next) {
1057         mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason);
1058     }
1059     free(reason_text);
1060 }
1061 
1062 /*!
1063  * \internal
1064  * \brief If an action is unrunnable, block any relevant dependent actions
1065  *
1066  * If a given action is an unrunnable start or promote, block the start or
1067  * promote actions of resources colocated with it, as appropriate to the
1068  * colocations' configured roles.
1069  *
1070  * \param[in,out] action  Action to check
1071  */
1072 void
1073 pcmk__block_colocation_dependents(pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1074 {
1075     GList *iter = NULL;
1076     GList *colocations = NULL;
1077     pcmk_resource_t *rsc = NULL;
1078     bool is_start = false;
1079 
1080     if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
1081         return; // Only unrunnable actions block dependents
1082     }
1083 
1084     is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none);
1085     if (!is_start
1086         && !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
1087         return; // Only unrunnable starts and promotes block dependents
1088     }
1089 
1090     CRM_ASSERT(action->rsc != NULL); // Start and promote are resource actions
1091 
1092     /* If this resource is part of a collective resource, dependents are blocked
1093      * only if all instances of the collective are unrunnable, so check the
1094      * collective resource.
1095      */
1096     rsc = uber_parent(action->rsc);
1097     if (rsc->parent != NULL) {
1098         rsc = rsc->parent; // Bundle
1099     }
1100 
1101     // Colocation fails only if entire primary can't reach desired role
1102     for (iter = rsc->children; iter != NULL; iter = iter->next) {
1103         pcmk_resource_t *child = iter->data;
1104         pcmk_action_t *child_action = find_first_action(child->actions, NULL,
1105                                                         action->task, NULL);
1106 
1107         if ((child_action == NULL)
1108             || pcmk_is_set(child_action->flags, pcmk_action_runnable)) {
1109             crm_trace("Not blocking %s colocation dependents because "
1110                       "at least %s has runnable %s",
1111                       rsc->id, child->id, action->task);
1112             return; // At least one child can reach desired role
1113         }
1114     }
1115 
1116     crm_trace("Blocking %s colocation dependents due to unrunnable %s %s",
1117               rsc->id, action->rsc->id, action->task);
1118 
1119     // Check each colocation where this resource is primary
1120     colocations = pcmk__with_this_colocations(rsc);
1121     for (iter = colocations; iter != NULL; iter = iter->next) {
1122         pcmk__colocation_t *colocation = iter->data;
1123 
1124         if (colocation->score < PCMK_SCORE_INFINITY) {
1125             continue; // Only mandatory colocations block dependent
1126         }
1127 
1128         /* If the primary can't start, the dependent can't reach its colocated
1129          * role, regardless of what the primary or dependent colocation role is.
1130          *
1131          * If the primary can't be promoted, the dependent can't reach its
1132          * colocated role if the primary's colocation role is promoted.
1133          */
1134         if (!is_start && (colocation->primary_role != pcmk_role_promoted)) {
1135             continue;
1136         }
1137 
1138         // Block the dependent from reaching its colocated role
1139         if (colocation->dependent_role == pcmk_role_promoted) {
1140             mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE,
1141                                 action->rsc);
1142         } else {
1143             mark_action_blocked(colocation->dependent, PCMK_ACTION_START,
1144                                 action->rsc);
1145         }
1146     }
1147     g_list_free(colocations);
1148 }
1149 
1150 /*!
1151  * \internal
1152  * \brief Get the resource to use for role comparisons
1153  *
1154  * A bundle replica includes a container and possibly an instance of the bundled
1155  * resource. The dependent in a "with bundle" colocation is colocated with a
1156  * particular bundle container. However, if the colocation includes a role, then
1157  * the role must be checked on the bundled resource instance inside the
1158  * container. The container itself will never be promoted; the bundled resource
1159  * may be.
1160  *
1161  * If the given resource is a bundle replica container, return the resource
1162  * inside it, if any. Otherwise, return the resource itself.
1163  *
1164  * \param[in] rsc  Resource to check
1165  *
1166  * \return Resource to use for role comparisons
1167  */
1168 static const pcmk_resource_t *
1169 get_resource_for_role(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1170 {
1171     if (pcmk_is_set(rsc->flags, pcmk_rsc_replica_container)) {
1172         const pcmk_resource_t *child = pe__get_rsc_in_container(rsc);
1173 
1174         if (child != NULL) {
1175             return child;
1176         }
1177     }
1178     return rsc;
1179 }
1180 
1181 /*!
1182  * \internal
1183  * \brief Determine how a colocation constraint should affect a resource
1184  *
1185  * Colocation constraints have different effects at different points in the
1186  * scheduler sequence. Initially, they affect a resource's location; once that
1187  * is determined, then for promotable clones they can affect a resource
1188  * instance's role; after both are determined, the constraints no longer matter.
1189  * Given a specific colocation constraint, check what has been done so far to
1190  * determine what should be affected at the current point in the scheduler.
1191  *
1192  * \param[in] dependent   Dependent resource in colocation
1193  * \param[in] primary     Primary resource in colocation
1194  * \param[in] colocation  Colocation constraint
1195  * \param[in] preview     If true, pretend resources have already been assigned
1196  *
1197  * \return How colocation constraint should be applied at this point
1198  */
1199 enum pcmk__coloc_affects
1200 pcmk__colocation_affects(const pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1201                          const pcmk_resource_t *primary,
1202                          const pcmk__colocation_t *colocation, bool preview)
1203 {
1204     const pcmk_resource_t *dependent_role_rsc = NULL;
1205     const pcmk_resource_t *primary_role_rsc = NULL;
1206 
1207     CRM_ASSERT((dependent != NULL) && (primary != NULL)
1208                && (colocation != NULL));
1209 
1210     if (!preview && pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
1211         // Primary resource has not been assigned yet, so we can't do anything
1212         return pcmk__coloc_affects_nothing;
1213     }
1214 
1215     dependent_role_rsc = get_resource_for_role(dependent);
1216     primary_role_rsc = get_resource_for_role(primary);
1217 
1218     if ((colocation->dependent_role >= pcmk_role_unpromoted)
1219         && (dependent_role_rsc->parent != NULL)
1220         && pcmk_is_set(dependent_role_rsc->parent->flags, pcmk_rsc_promotable)
1221         && !pcmk_is_set(dependent_role_rsc->flags, pcmk_rsc_unassigned)) {
1222 
1223         /* This is a colocation by role, and the dependent is a promotable clone
1224          * that has already been assigned, so the colocation should now affect
1225          * the role.
1226          */
1227         return pcmk__coloc_affects_role;
1228     }
1229 
1230     if (!preview && !pcmk_is_set(dependent->flags, pcmk_rsc_unassigned)) {
1231         /* The dependent resource has already been through assignment, so the
1232          * constraint no longer has any effect. Log an error if a mandatory
1233          * colocation constraint has been violated.
1234          */
1235 
1236         const pcmk_node_t *primary_node = primary->allocated_to;
1237 
1238         if (dependent->allocated_to == NULL) {
1239             crm_trace("Skipping colocation '%s': %s will not run anywhere",
1240                       colocation->id, dependent->id);
1241 
1242         } else if (colocation->score >= PCMK_SCORE_INFINITY) {
1243             // Dependent resource must colocate with primary resource
1244 
1245             if (!pcmk__same_node(primary_node, dependent->allocated_to)) {
1246                 pcmk__sched_err("%s must be colocated with %s but is not "
1247                                 "(%s vs. %s)",
1248                                 dependent->id, primary->id,
1249                                 pcmk__node_name(dependent->allocated_to),
1250                                 pcmk__node_name(primary_node));
1251             }
1252 
1253         } else if (colocation->score <= -PCMK_SCORE_INFINITY) {
1254             // Dependent resource must anti-colocate with primary resource
1255 
1256             if (pcmk__same_node(dependent->allocated_to, primary_node)) {
1257                 pcmk__sched_err("%s and %s must be anti-colocated but are "
1258                                 "assigned to the same node (%s)",
1259                                 dependent->id, primary->id,
1260                                 pcmk__node_name(primary_node));
1261             }
1262         }
1263         return pcmk__coloc_affects_nothing;
1264     }
1265 
1266     if ((colocation->dependent_role != pcmk_role_unknown)
1267         && (colocation->dependent_role != dependent_role_rsc->next_role)) {
1268         crm_trace("Skipping %scolocation '%s': dependent limited to %s role "
1269 
1270                   "but %s next role is %s",
1271                   ((colocation->score < 0)? "anti-" : ""),
1272                   colocation->id, pcmk_role_text(colocation->dependent_role),
1273                   dependent_role_rsc->id,
1274                   pcmk_role_text(dependent_role_rsc->next_role));
1275         return pcmk__coloc_affects_nothing;
1276     }
1277 
1278     if ((colocation->primary_role != pcmk_role_unknown)
1279         && (colocation->primary_role != primary_role_rsc->next_role)) {
1280         crm_trace("Skipping %scolocation '%s': primary limited to %s role "
1281                   "but %s next role is %s",
1282                   ((colocation->score < 0)? "anti-" : ""),
1283                   colocation->id, pcmk_role_text(colocation->primary_role),
1284                   primary_role_rsc->id,
1285                   pcmk_role_text(primary_role_rsc->next_role));
1286         return pcmk__coloc_affects_nothing;
1287     }
1288 
1289     return pcmk__coloc_affects_location;
1290 }
1291 
1292 /*!
1293  * \internal
1294  * \brief Apply colocation to dependent for assignment purposes
1295  *
1296  * Update the allowed node scores of the dependent resource in a colocation,
1297  * for the purposes of assigning it to a node.
1298  *
1299  * \param[in,out] dependent   Dependent resource in colocation
1300  * \param[in]     primary     Primary resource in colocation
1301  * \param[in]     colocation  Colocation constraint
1302  */
1303 void
1304 pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1305                             const pcmk_resource_t *primary,
1306                             const pcmk__colocation_t *colocation)
1307 {
1308     const char *attr = colocation->node_attribute;
1309     const char *value = NULL;
1310     GHashTable *work = NULL;
1311     GHashTableIter iter;
1312     pcmk_node_t *node = NULL;
1313 
1314     if (primary->allocated_to != NULL) {
1315         value = pcmk__colocation_node_attr(primary->allocated_to, attr,
1316                                            primary);
1317 
1318     } else if (colocation->score < 0) {
1319         // Nothing to do (anti-colocation with something that is not running)
1320         return;
1321     }
1322 
1323     work = pcmk__copy_node_table(dependent->allowed_nodes);
1324 
1325     g_hash_table_iter_init(&iter, work);
1326     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1327         if (primary->allocated_to == NULL) {
1328             node->weight = pcmk__add_scores(-colocation->score, node->weight);
1329             pcmk__rsc_trace(dependent,
1330                             "Applied %s to %s score on %s (now %s after "
1331                             "subtracting %s because primary %s inactive)",
1332                             colocation->id, dependent->id,
1333                             pcmk__node_name(node),
1334                             pcmk_readable_score(node->weight),
1335                             pcmk_readable_score(colocation->score), primary->id);
1336             continue;
1337         }
1338 
1339         if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent),
1340                          value, pcmk__str_casei)) {
1341 
1342             /* Add colocation score only if optional (or minus infinity). A
1343              * mandatory colocation is a requirement rather than a preference,
1344              * so we don't need to consider it for relative assignment purposes.
1345              * The resource will simply be forbidden from running on the node if
1346              * the primary isn't active there (via the condition above).
1347              */
1348             if (colocation->score < PCMK_SCORE_INFINITY) {
1349                 node->weight = pcmk__add_scores(colocation->score,
1350                                                 node->weight);
1351                 pcmk__rsc_trace(dependent,
1352                                 "Applied %s to %s score on %s (now %s after "
1353                                 "adding %s)",
1354                                 colocation->id, dependent->id,
1355                                 pcmk__node_name(node),
1356                                 pcmk_readable_score(node->weight),
1357                                 pcmk_readable_score(colocation->score));
1358             }
1359             continue;
1360         }
1361 
1362         if (colocation->score >= PCMK_SCORE_INFINITY) {
1363             /* Only mandatory colocations are relevant when the colocation
1364              * attribute doesn't match, because an attribute not matching is not
1365              * a negative preference -- the colocation is simply relevant only
1366              * where it matches.
1367              */
1368             node->weight = -PCMK_SCORE_INFINITY;
1369             pcmk__rsc_trace(dependent,
1370                             "Banned %s from %s because colocation %s attribute %s "
1371                             "does not match",
1372                             dependent->id, pcmk__node_name(node),
1373                             colocation->id, attr);
1374         }
1375     }
1376 
1377     if ((colocation->score <= -PCMK_SCORE_INFINITY)
1378         || (colocation->score >= PCMK_SCORE_INFINITY)
1379         || pcmk__any_node_available(work)) {
1380 
1381         g_hash_table_destroy(dependent->allowed_nodes);
1382         dependent->allowed_nodes = work;
1383         work = NULL;
1384 
1385     } else {
1386         pcmk__rsc_info(dependent,
1387                        "%s: Rolling back scores from %s (no available nodes)",
1388                        dependent->id, primary->id);
1389     }
1390 
1391     if (work != NULL) {
1392         g_hash_table_destroy(work);
1393     }
1394 }
1395 
1396 /*!
1397  * \internal
1398  * \brief Apply colocation to dependent for role purposes
1399  *
1400  * Update the priority of the dependent resource in a colocation, for the
1401  * purposes of selecting its role
1402  *
1403  * \param[in,out] dependent   Dependent resource in colocation
1404  * \param[in]     primary     Primary resource in colocation
1405  * \param[in]     colocation  Colocation constraint
1406  *
1407  * \return The score added to the dependent's priority
1408  */
1409 int
1410 pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1411                               const pcmk_resource_t *primary,
1412                               const pcmk__colocation_t *colocation)
1413 {
1414     const char *dependent_value = NULL;
1415     const char *primary_value = NULL;
1416     const char *attr = colocation->node_attribute;
1417     int score_multiplier = 1;
1418     int priority_delta = 0;
1419 
1420     CRM_ASSERT((dependent != NULL) && (primary != NULL)
1421                && (colocation != NULL));
1422 
1423     if (dependent->allocated_to == NULL) {
1424         return 0;
1425     }
1426 
1427     if ((primary->allocated_to != NULL)
1428         && (colocation->primary_role != pcmk_role_unknown)) {
1429         /* Colocation applies only if the primary's next role matches.
1430          *
1431          * If primary->allocated_to == NULL, we want to proceed past this block,
1432          * so that dependent->allocated_to is marked ineligible for promotion.
1433          *
1434          * @TODO Why ignore a mandatory colocation in this case when we apply
1435          * its negation in the mismatched value case?
1436          */
1437         const pcmk_resource_t *role_rsc = get_resource_for_role(primary);
1438 
1439         if (colocation->primary_role != role_rsc->next_role) {
1440             return 0;
1441         }
1442     }
1443 
1444     dependent_value = pcmk__colocation_node_attr(dependent->allocated_to, attr,
1445                                                  dependent);
1446     primary_value = pcmk__colocation_node_attr(primary->allocated_to, attr,
1447                                                primary);
1448 
1449     if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
1450         if ((colocation->score == PCMK_SCORE_INFINITY)
1451             && (colocation->dependent_role == pcmk_role_promoted)) {
1452 
1453             /* For a mandatory promoted-role colocation, mark the dependent node
1454              * ineligible to promote the dependent if its attribute value
1455              * doesn't match the primary node's
1456              */
1457             score_multiplier = -1;
1458 
1459         } else {
1460             // Otherwise, ignore the colocation if attribute values don't match
1461             return 0;
1462         }
1463 
1464     } else if (colocation->dependent_role == pcmk_role_unpromoted) {
1465         /* Node attribute values matched, so we want to avoid promoting the
1466          * dependent on this node
1467          */
1468         score_multiplier = -1;
1469     }
1470 
1471     priority_delta = score_multiplier * colocation->score;
1472     dependent->priority = pcmk__add_scores(priority_delta, dependent->priority);
1473     pcmk__rsc_trace(dependent,
1474                     "Applied %s to %s promotion priority (now %s after %s %d)",
1475                     colocation->id, dependent->id,
1476                     pcmk_readable_score(dependent->priority),
1477                     ((score_multiplier == 1)? "adding" : "subtracting"),
1478                     colocation->score);
1479 
1480     return priority_delta;
1481 }
1482 
1483 /*!
1484  * \internal
1485  * \brief Find score of highest-scored node that matches colocation attribute
1486  *
1487  * \param[in]     colocation  Colocation constraint being applied
1488  * \param[in,out] rsc         Resource whose allowed nodes should be searched
1489  * \param[in]     attr        Colocation attribute name (must not be NULL)
1490  * \param[in]     value       Colocation attribute value to require
1491  */
1492 static int
1493 best_node_score_matching_attr(const pcmk__colocation_t *colocation,
     /* [previous][next][first][last][top][bottom][index][help] */
1494                               pcmk_resource_t *rsc, const char *attr,
1495                               const char *value)
1496 {
1497     GHashTable *allowed_nodes_orig = NULL;
1498     GHashTableIter iter;
1499     pcmk_node_t *node = NULL;
1500     int best_score = -PCMK_SCORE_INFINITY;
1501     const char *best_node = NULL;
1502 
1503     if ((colocation != NULL) && (rsc == colocation->dependent)
1504         && pcmk_is_set(colocation->flags, pcmk__coloc_explicit)
1505         && pcmk__is_group(rsc->parent)
1506         && (rsc != rsc->parent->children->data)) {
1507         /* The resource is a user-configured colocation's explicit dependent,
1508          * and a group member other than the first, which means the group's
1509          * location constraint scores were not applied to it (see
1510          * pcmk__group_apply_location()). Explicitly consider those scores now.
1511          *
1512          * @TODO This does leave one suboptimal case: if the group itself or
1513          * another member other than the first is explicitly colocated with
1514          * the same primary, the primary will count the group's location scores
1515          * multiple times. This is much less likely than a single member being
1516          * explicitly colocated, so it's an acceptable tradeoff for now.
1517          */
1518         allowed_nodes_orig = rsc->allowed_nodes;
1519         rsc->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig);
1520         for (GList *loc_iter = rsc->cluster->placement_constraints;
1521              loc_iter != NULL; loc_iter = loc_iter->next) {
1522 
1523             pcmk__location_t *location = loc_iter->data;
1524 
1525             if (location->rsc == rsc->parent) {
1526                 rsc->cmds->apply_location(rsc, location);
1527             }
1528         }
1529     }
1530 
1531     // Find best allowed node with matching attribute
1532     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
1533     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
1534 
1535         if ((node->weight > best_score)
1536             && pcmk__node_available(node, false, false)
1537             && pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc),
1538                             pcmk__str_casei)) {
1539 
1540             best_score = node->weight;
1541             best_node = node->details->uname;
1542         }
1543     }
1544 
1545     if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) {
1546         if (best_node == NULL) {
1547             crm_info("No allowed node for %s matches node attribute %s=%s",
1548                      rsc->id, attr, value);
1549         } else {
1550             crm_info("Allowed node %s for %s had best score (%d) "
1551                      "of those matching node attribute %s=%s",
1552                      best_node, rsc->id, best_score, attr, value);
1553         }
1554     }
1555 
1556     if (allowed_nodes_orig != NULL) {
1557         g_hash_table_destroy(rsc->allowed_nodes);
1558         rsc->allowed_nodes = allowed_nodes_orig;
1559     }
1560     return best_score;
1561 }
1562 
1563 /*!
1564  * \internal
1565  * \brief Check whether a resource is allowed only on a single node
1566  *
1567  * \param[in] rsc   Resource to check
1568  *
1569  * \return \c true if \p rsc is allowed only on one node, otherwise \c false
1570  */
1571 static bool
1572 allowed_on_one(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1573 {
1574     GHashTableIter iter;
1575     pcmk_node_t *allowed_node = NULL;
1576     int allowed_nodes = 0;
1577 
1578     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
1579     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) {
1580         if ((allowed_node->weight >= 0) && (++allowed_nodes > 1)) {
1581             pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id);
1582             return false;
1583         }
1584     }
1585     pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id,
1586                     ((allowed_nodes == 1)? "on a single node" : "nowhere"));
1587     return (allowed_nodes == 1);
1588 }
1589 
1590 /*!
1591  * \internal
1592  * \brief Add resource's colocation matches to current node assignment scores
1593  *
1594  * For each node in a given table, if any of a given resource's allowed nodes
1595  * have a matching value for the colocation attribute, add the highest of those
1596  * nodes' scores to the node's score.
1597  *
1598  * \param[in,out] nodes          Table of nodes with assignment scores so far
1599  * \param[in,out] source_rsc     Resource whose node scores to add
1600  * \param[in]     target_rsc     Resource on whose behalf to update \p nodes
1601  * \param[in]     colocation     Original colocation constraint (used to get
1602  *                               configured primary resource's stickiness, and
1603  *                               to get colocation node attribute; pass NULL to
1604  *                               ignore stickiness and use default attribute)
1605  * \param[in]     factor         Factor by which to multiply scores being added
1606  * \param[in]     only_positive  Whether to add only positive scores
1607  */
1608 static void
1609 add_node_scores_matching_attr(GHashTable *nodes,
     /* [previous][next][first][last][top][bottom][index][help] */
1610                               pcmk_resource_t *source_rsc,
1611                               const pcmk_resource_t *target_rsc,
1612                               const pcmk__colocation_t *colocation,
1613                               float factor, bool only_positive)
1614 {
1615     GHashTableIter iter;
1616     pcmk_node_t *node = NULL;
1617     const char *attr = colocation->node_attribute;
1618 
1619     // Iterate through each node
1620     g_hash_table_iter_init(&iter, nodes);
1621     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1622         float delta_f = 0;
1623         int delta = 0;
1624         int score = 0;
1625         int new_score = 0;
1626         const char *value = pcmk__colocation_node_attr(node, attr, target_rsc);
1627 
1628         score = best_node_score_matching_attr(colocation, source_rsc, attr, value);
1629 
1630         if ((factor < 0) && (score < 0)) {
1631             /* If the dependent is anti-colocated, we generally don't want the
1632              * primary to prefer nodes that the dependent avoids. That could
1633              * lead to unnecessary shuffling of the primary when the dependent
1634              * hits its migration threshold somewhere, for example.
1635              *
1636              * However, there are cases when it is desirable. If the dependent
1637              * can't run anywhere but where the primary is, it would be
1638              * worthwhile to move the primary for the sake of keeping the
1639              * dependent active.
1640              *
1641              * We can't know that exactly at this point since we don't know
1642              * where the primary will be assigned, but we can limit considering
1643              * the preference to when the dependent is allowed only on one node.
1644              * This is less than ideal for multiple reasons:
1645              *
1646              * - the dependent could be allowed on more than one node but have
1647              *   anti-colocation primaries on each;
1648              * - the dependent could be a clone or bundle with multiple
1649              *   instances, and the dependent as a whole is allowed on multiple
1650              *   nodes but some instance still can't run
1651              * - the dependent has considered node-specific criteria such as
1652              *   location constraints and stickiness by this point, but might
1653              *   have other factors that end up disallowing a node
1654              *
1655              * but the alternative is making the primary move when it doesn't
1656              * need to.
1657              *
1658              * We also consider the primary's stickiness and influence, so the
1659              * user has some say in the matter. (This is the configured primary,
1660              * not a particular instance of the primary, but that doesn't matter
1661              * unless stickiness uses a rule to vary by node, and that seems
1662              * acceptable to ignore.)
1663              */
1664             if ((colocation->primary->stickiness >= -score)
1665                 || !pcmk__colocation_has_influence(colocation, NULL)
1666                 || !allowed_on_one(colocation->dependent)) {
1667                 crm_trace("%s: Filtering %d + %f * %d "
1668                           "(double negative disallowed)",
1669                           pcmk__node_name(node), node->weight, factor, score);
1670                 continue;
1671             }
1672         }
1673 
1674         if (node->weight == INFINITY_HACK) {
1675             crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
1676                       pcmk__node_name(node), node->weight, factor, score);
1677             continue;
1678         }
1679 
1680         delta_f = factor * score;
1681 
1682         // Round the number; see http://c-faq.com/fp/round.html
1683         delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5));
1684 
1685         /* Small factors can obliterate the small scores that are often actually
1686          * used in configurations. If the score and factor are nonzero, ensure
1687          * that the result is nonzero as well.
1688          */
1689         if ((delta == 0) && (score != 0)) {
1690             if (factor > 0.0) {
1691                 delta = 1;
1692             } else if (factor < 0.0) {
1693                 delta = -1;
1694             }
1695         }
1696 
1697         new_score = pcmk__add_scores(delta, node->weight);
1698 
1699         if (only_positive && (new_score < 0) && (node->weight > 0)) {
1700             crm_trace("%s: Filtering %d + %f * %d = %d "
1701                       "(negative disallowed, marking node unusable)",
1702                       pcmk__node_name(node), node->weight, factor, score,
1703                       new_score);
1704             node->weight = INFINITY_HACK;
1705             continue;
1706         }
1707 
1708         if (only_positive && (new_score < 0) && (node->weight == 0)) {
1709             crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
1710                       pcmk__node_name(node), node->weight, factor, score,
1711                       new_score);
1712             continue;
1713         }
1714 
1715         crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node),
1716                   node->weight, factor, score, new_score);
1717         node->weight = new_score;
1718     }
1719 }
1720 
1721 /*!
1722  * \internal
1723  * \brief Update nodes with scores of colocated resources' nodes
1724  *
1725  * Given a table of nodes and a resource, update the nodes' scores with the
1726  * scores of the best nodes matching the attribute used for each of the
1727  * resource's relevant colocations.
1728  *
1729  * \param[in,out] source_rsc  Resource whose node scores to add
1730  * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
1731  * \param[in]     log_id      Resource ID for logs (if \c NULL, use
1732  *                            \p source_rsc ID)
1733  * \param[in,out] nodes       Nodes to update (set initial contents to \c NULL
1734  *                            to copy allowed nodes from \p source_rsc)
1735  * \param[in]     colocation  Original colocation constraint (used to get
1736  *                            configured primary resource's stickiness, and
1737  *                            to get colocation node attribute; if \c NULL,
1738  *                            <tt>source_rsc</tt>'s own matching node scores
1739  *                            will not be added, and \p *nodes must be \c NULL
1740  *                            as well)
1741  * \param[in]     factor      Incorporate scores multiplied by this factor
1742  * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
1743  *
1744  * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
1745  *       the \c pcmk__coloc_select_this_with flag are used together (and only by
1746  *       \c cmp_resources()).
1747  * \note The caller remains responsible for freeing \p *nodes.
1748  * \note This is the shared implementation of
1749  *       \c pcmk_assignment_methods_t:add_colocated_node_scores().
1750  */
1751 void
1752 pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
1753                                 const pcmk_resource_t *target_rsc,
1754                                 const char *log_id,
1755                                 GHashTable **nodes,
1756                                 const pcmk__colocation_t *colocation,
1757                                 float factor, uint32_t flags)
1758 {
1759     GHashTable *work = NULL;
1760 
1761     CRM_ASSERT((source_rsc != NULL) && (nodes != NULL)
1762                && ((colocation != NULL)
1763                    || ((target_rsc == NULL) && (*nodes == NULL))));
1764 
1765     if (log_id == NULL) {
1766         log_id = source_rsc->id;
1767     }
1768 
1769     // Avoid infinite recursion
1770     if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
1771         pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
1772                        log_id, source_rsc->id);
1773         return;
1774     }
1775     pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1776 
1777     if (*nodes == NULL) {
1778         work = pcmk__copy_node_table(source_rsc->allowed_nodes);
1779         target_rsc = source_rsc;
1780     } else {
1781         const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative);
1782 
1783         pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)",
1784                         log_id, (pos? "positive" : "all"), source_rsc->id, factor);
1785         work = pcmk__copy_node_table(*nodes);
1786         add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation,
1787                                       factor, pos);
1788     }
1789 
1790     if (work == NULL) {
1791         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1792         return;
1793     }
1794 
1795     if (pcmk__any_node_available(work)) {
1796         GList *colocations = NULL;
1797 
1798         if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
1799             colocations = pcmk__this_with_colocations(source_rsc);
1800             pcmk__rsc_trace(source_rsc,
1801                             "Checking additional %d optional '%s with' "
1802                             "constraints",
1803                             g_list_length(colocations), source_rsc->id);
1804         } else {
1805             colocations = pcmk__with_this_colocations(source_rsc);
1806             pcmk__rsc_trace(source_rsc,
1807                             "Checking additional %d optional 'with %s' "
1808                             "constraints",
1809                             g_list_length(colocations), source_rsc->id);
1810         }
1811         flags |= pcmk__coloc_select_active;
1812 
1813         for (GList *iter = colocations; iter != NULL; iter = iter->next) {
1814             pcmk__colocation_t *constraint = iter->data;
1815 
1816             pcmk_resource_t *other = NULL;
1817             float other_factor = factor * constraint->score
1818                                  / (float) PCMK_SCORE_INFINITY;
1819 
1820             if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
1821                 other = constraint->primary;
1822             } else if (!pcmk__colocation_has_influence(constraint, NULL)) {
1823                 continue;
1824             } else {
1825                 other = constraint->dependent;
1826             }
1827 
1828             pcmk__rsc_trace(source_rsc,
1829                             "Optionally merging score of '%s' constraint "
1830                             "(%s with %s)",
1831                             constraint->id, constraint->dependent->id,
1832                             constraint->primary->id);
1833             other->cmds->add_colocated_node_scores(other, target_rsc, log_id,
1834                                                    &work, constraint,
1835                                                    other_factor, flags);
1836             pe__show_node_scores(true, NULL, log_id, work, source_rsc->cluster);
1837         }
1838         g_list_free(colocations);
1839 
1840     } else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
1841         pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s",
1842                        log_id, source_rsc->id);
1843         g_hash_table_destroy(work);
1844         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1845         return;
1846     }
1847 
1848 
1849     if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
1850         pcmk_node_t *node = NULL;
1851         GHashTableIter iter;
1852 
1853         g_hash_table_iter_init(&iter, work);
1854         while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1855             if (node->weight == INFINITY_HACK) {
1856                 node->weight = 1;
1857             }
1858         }
1859     }
1860 
1861     if (*nodes != NULL) {
1862        g_hash_table_destroy(*nodes);
1863     }
1864     *nodes = work;
1865 
1866     pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1867 }
1868 
1869 /*!
1870  * \internal
1871  * \brief Apply a "with this" colocation to a resource's allowed node scores
1872  *
1873  * \param[in,out] data       Colocation to apply
1874  * \param[in,out] user_data  Resource being assigned
1875  */
1876 void
1877 pcmk__add_dependent_scores(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1878 {
1879     pcmk__colocation_t *colocation = data;
1880     pcmk_resource_t *target_rsc = user_data;
1881 
1882     pcmk_resource_t *source_rsc = colocation->dependent;
1883     const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
1884     uint32_t flags = pcmk__coloc_select_active;
1885 
1886     if (!pcmk__colocation_has_influence(colocation, NULL)) {
1887         return;
1888     }
1889     if (pcmk__is_clone(target_rsc)) {
1890         flags |= pcmk__coloc_select_nonnegative;
1891     }
1892     pcmk__rsc_trace(target_rsc,
1893                     "%s: Incorporating attenuated %s assignment scores due "
1894                     "to colocation %s",
1895                     target_rsc->id, source_rsc->id, colocation->id);
1896     source_rsc->cmds->add_colocated_node_scores(source_rsc, target_rsc,
1897                                                 source_rsc->id,
1898                                                 &target_rsc->allowed_nodes,
1899                                                 colocation, factor, flags);
1900 }
1901 
1902 /*!
1903  * \internal
1904  * \brief Exclude nodes from a dependent's node table if not in a given list
1905  *
1906  * Given a dependent resource in a colocation and a list of nodes where the
1907  * primary resource will run, set a node's score to \c -INFINITY in the
1908  * dependent's node table if not found in the primary nodes list.
1909  *
1910  * \param[in,out] dependent      Dependent resource
1911  * \param[in]     primary        Primary resource (for logging only)
1912  * \param[in]     colocation     Colocation constraint (for logging only)
1913  * \param[in]     primary_nodes  List of nodes where the primary will have
1914  *                               unblocked instances in a suitable role
1915  * \param[in]     merge_scores   If \c true and a node is found in both \p table
1916  *                               and \p list, add the node's score in \p list to
1917  *                               the node's score in \p table
1918  */
1919 void
1920 pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1921                                  const pcmk_resource_t *primary,
1922                                  const pcmk__colocation_t *colocation,
1923                                  const GList *primary_nodes, bool merge_scores)
1924 {
1925     GHashTableIter iter;
1926     pcmk_node_t *dependent_node = NULL;
1927 
1928     CRM_ASSERT((dependent != NULL) && (primary != NULL)
1929                && (colocation != NULL));
1930 
1931     g_hash_table_iter_init(&iter, dependent->allowed_nodes);
1932     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) {
1933         const pcmk_node_t *primary_node = NULL;
1934 
1935         primary_node = pe_find_node_id(primary_nodes,
1936                                        dependent_node->details->id);
1937         if (primary_node == NULL) {
1938             dependent_node->weight = -PCMK_SCORE_INFINITY;
1939             pcmk__rsc_trace(dependent,
1940                             "Banning %s from %s (no primary instance) for %s",
1941                             dependent->id, pcmk__node_name(dependent_node),
1942                             colocation->id);
1943 
1944         } else if (merge_scores) {
1945             dependent_node->weight = pcmk__add_scores(dependent_node->weight,
1946                                                       primary_node->weight);
1947             pcmk__rsc_trace(dependent,
1948                             "Added %s's score %s to %s's score for %s (now %s) "
1949                             "for colocation %s",
1950                             primary->id, pcmk_readable_score(primary_node->weight),
1951                             dependent->id, pcmk__node_name(dependent_node),
1952                             pcmk_readable_score(dependent_node->weight),
1953                             colocation->id);
1954         }
1955     }
1956 }
1957 
1958 /*!
1959  * \internal
1960  * \brief Get all colocations affecting a resource as the primary
1961  *
1962  * \param[in] rsc  Resource to get colocations for
1963  *
1964  * \return Newly allocated list of colocations affecting \p rsc as primary
1965  *
1966  * \note This is a convenience wrapper for the with_this_colocations() method.
1967  */
1968 GList *
1969 pcmk__with_this_colocations(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1970 {
1971     GList *list = NULL;
1972 
1973     rsc->cmds->with_this_colocations(rsc, rsc, &list);
1974     return list;
1975 }
1976 
1977 /*!
1978  * \internal
1979  * \brief Get all colocations affecting a resource as the dependent
1980  *
1981  * \param[in] rsc  Resource to get colocations for
1982  *
1983  * \return Newly allocated list of colocations affecting \p rsc as dependent
1984  *
1985  * \note This is a convenience wrapper for the this_with_colocations() method.
1986  */
1987 GList *
1988 pcmk__this_with_colocations(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1989 {
1990     GList *list = NULL;
1991 
1992     rsc->cmds->this_with_colocations(rsc, rsc, &list);
1993     return list;
1994 }

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