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. pcmk__colocation_has_influence
  17. mark_action_blocked
  18. pcmk__block_colocation_dependents
  19. get_resource_for_role
  20. pcmk__colocation_affects
  21. pcmk__apply_coloc_to_scores
  22. pcmk__apply_coloc_to_priority
  23. best_node_score_matching_attr
  24. allowed_on_one
  25. add_node_scores_matching_attr
  26. pcmk__add_colocated_node_scores
  27. pcmk__add_dependent_scores
  28. pcmk__colocation_intersect_nodes
  29. pcmk__with_this_colocations
  30. 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         pcmk__assert(colocation1->primary != NULL);
  69     } else {
  70         rsc1 = colocation1->primary;
  71         rsc2 = colocation2->primary;
  72         pcmk__assert(colocation1->dependent != NULL);
  73     }
  74     pcmk__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     pcmk__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     pcmk__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     pcmk__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     pcmk__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                 pcmk__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 Check whether colocation's dependent preferences should be considered
1030  *
1031  * \param[in] colocation  Colocation constraint
1032  * \param[in] rsc         Primary instance (normally this will be
1033  *                        colocation->primary, which NULL will be treated as,
1034  *                        but for clones or bundles with multiple instances
1035  *                        this can be a particular instance)
1036  *
1037  * \return true if colocation influence should be effective, otherwise false
1038  */
1039 bool
1040 pcmk__colocation_has_influence(const pcmk__colocation_t *colocation,
     /* [previous][next][first][last][top][bottom][index][help] */
1041                                const pcmk_resource_t *rsc)
1042 {
1043     if (rsc == NULL) {
1044         rsc = colocation->primary;
1045     }
1046 
1047     /* A bundle replica colocates its remote connection with its container,
1048      * using a finite score so that the container can run on Pacemaker Remote
1049      * nodes.
1050      *
1051      * Moving a connection is lightweight and does not interrupt the service,
1052      * while moving a container is heavyweight and does interrupt the service,
1053      * so don't move a clean, active container based solely on the preferences
1054      * of its connection.
1055      *
1056      * This also avoids problematic scenarios where two containers want to
1057      * perpetually swap places.
1058      */
1059     if (pcmk_is_set(colocation->dependent->flags,
1060                     pcmk_rsc_remote_nesting_allowed)
1061         && !pcmk_is_set(rsc->flags, pcmk_rsc_failed)
1062         && pcmk__list_of_1(rsc->running_on)) {
1063         return false;
1064     }
1065 
1066     /* The dependent in a colocation influences the primary's location
1067      * if the PCMK_XA_INFLUENCE option is true or the primary is not yet active.
1068      */
1069     return pcmk_is_set(colocation->flags, pcmk__coloc_influence)
1070            || (rsc->running_on == NULL);
1071 }
1072 
1073 /*!
1074  * \internal
1075  * \brief Make actions of a given type unrunnable for a given resource
1076  *
1077  * \param[in,out] rsc     Resource whose actions should be blocked
1078  * \param[in]     task    Name of action to block
1079  * \param[in]     reason  Unrunnable start action causing the block
1080  */
1081 static void
1082 mark_action_blocked(pcmk_resource_t *rsc, const char *task,
     /* [previous][next][first][last][top][bottom][index][help] */
1083                     const pcmk_resource_t *reason)
1084 {
1085     GList *iter = NULL;
1086     char *reason_text = crm_strdup_printf("colocation with %s", reason->id);
1087 
1088     for (iter = rsc->actions; iter != NULL; iter = iter->next) {
1089         pcmk_action_t *action = iter->data;
1090 
1091         if (pcmk_is_set(action->flags, pcmk_action_runnable)
1092             && pcmk__str_eq(action->task, task, pcmk__str_none)) {
1093 
1094             pcmk__clear_action_flags(action, pcmk_action_runnable);
1095             pe_action_set_reason(action, reason_text, false);
1096             pcmk__block_colocation_dependents(action);
1097             pcmk__update_action_for_orderings(action, rsc->cluster);
1098         }
1099     }
1100 
1101     // If parent resource can't perform an action, neither can any children
1102     for (iter = rsc->children; iter != NULL; iter = iter->next) {
1103         mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason);
1104     }
1105     free(reason_text);
1106 }
1107 
1108 /*!
1109  * \internal
1110  * \brief If an action is unrunnable, block any relevant dependent actions
1111  *
1112  * If a given action is an unrunnable start or promote, block the start or
1113  * promote actions of resources colocated with it, as appropriate to the
1114  * colocations' configured roles.
1115  *
1116  * \param[in,out] action  Action to check
1117  */
1118 void
1119 pcmk__block_colocation_dependents(pcmk_action_t *action)
     /* [previous][next][first][last][top][bottom][index][help] */
1120 {
1121     GList *iter = NULL;
1122     GList *colocations = NULL;
1123     pcmk_resource_t *rsc = NULL;
1124     bool is_start = false;
1125 
1126     if (pcmk_is_set(action->flags, pcmk_action_runnable)) {
1127         return; // Only unrunnable actions block dependents
1128     }
1129 
1130     is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none);
1131     if (!is_start
1132         && !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) {
1133         return; // Only unrunnable starts and promotes block dependents
1134     }
1135 
1136     pcmk__assert(action->rsc != NULL); // Start and promote are resource actions
1137 
1138     /* If this resource is part of a collective resource, dependents are blocked
1139      * only if all instances of the collective are unrunnable, so check the
1140      * collective resource.
1141      */
1142     rsc = uber_parent(action->rsc);
1143     if (rsc->parent != NULL) {
1144         rsc = rsc->parent; // Bundle
1145     }
1146 
1147     // Colocation fails only if entire primary can't reach desired role
1148     for (iter = rsc->children; iter != NULL; iter = iter->next) {
1149         pcmk_resource_t *child = iter->data;
1150         pcmk_action_t *child_action = find_first_action(child->actions, NULL,
1151                                                         action->task, NULL);
1152 
1153         if ((child_action == NULL)
1154             || pcmk_is_set(child_action->flags, pcmk_action_runnable)) {
1155             crm_trace("Not blocking %s colocation dependents because "
1156                       "at least %s has runnable %s",
1157                       rsc->id, child->id, action->task);
1158             return; // At least one child can reach desired role
1159         }
1160     }
1161 
1162     crm_trace("Blocking %s colocation dependents due to unrunnable %s %s",
1163               rsc->id, action->rsc->id, action->task);
1164 
1165     // Check each colocation where this resource is primary
1166     colocations = pcmk__with_this_colocations(rsc);
1167     for (iter = colocations; iter != NULL; iter = iter->next) {
1168         pcmk__colocation_t *colocation = iter->data;
1169 
1170         if (colocation->score < PCMK_SCORE_INFINITY) {
1171             continue; // Only mandatory colocations block dependent
1172         }
1173 
1174         /* If the primary can't start, the dependent can't reach its colocated
1175          * role, regardless of what the primary or dependent colocation role is.
1176          *
1177          * If the primary can't be promoted, the dependent can't reach its
1178          * colocated role if the primary's colocation role is promoted.
1179          */
1180         if (!is_start && (colocation->primary_role != pcmk_role_promoted)) {
1181             continue;
1182         }
1183 
1184         // Block the dependent from reaching its colocated role
1185         if (colocation->dependent_role == pcmk_role_promoted) {
1186             mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE,
1187                                 action->rsc);
1188         } else {
1189             mark_action_blocked(colocation->dependent, PCMK_ACTION_START,
1190                                 action->rsc);
1191         }
1192     }
1193     g_list_free(colocations);
1194 }
1195 
1196 /*!
1197  * \internal
1198  * \brief Get the resource to use for role comparisons
1199  *
1200  * A bundle replica includes a container and possibly an instance of the bundled
1201  * resource. The dependent in a "with bundle" colocation is colocated with a
1202  * particular bundle container. However, if the colocation includes a role, then
1203  * the role must be checked on the bundled resource instance inside the
1204  * container. The container itself will never be promoted; the bundled resource
1205  * may be.
1206  *
1207  * If the given resource is a bundle replica container, return the resource
1208  * inside it, if any. Otherwise, return the resource itself.
1209  *
1210  * \param[in] rsc  Resource to check
1211  *
1212  * \return Resource to use for role comparisons
1213  */
1214 static const pcmk_resource_t *
1215 get_resource_for_role(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1216 {
1217     if (pcmk_is_set(rsc->flags, pcmk_rsc_replica_container)) {
1218         const pcmk_resource_t *child = pe__get_rsc_in_container(rsc);
1219 
1220         if (child != NULL) {
1221             return child;
1222         }
1223     }
1224     return rsc;
1225 }
1226 
1227 /*!
1228  * \internal
1229  * \brief Determine how a colocation constraint should affect a resource
1230  *
1231  * Colocation constraints have different effects at different points in the
1232  * scheduler sequence. Initially, they affect a resource's location; once that
1233  * is determined, then for promotable clones they can affect a resource
1234  * instance's role; after both are determined, the constraints no longer matter.
1235  * Given a specific colocation constraint, check what has been done so far to
1236  * determine what should be affected at the current point in the scheduler.
1237  *
1238  * \param[in] dependent   Dependent resource in colocation
1239  * \param[in] primary     Primary resource in colocation
1240  * \param[in] colocation  Colocation constraint
1241  * \param[in] preview     If true, pretend resources have already been assigned
1242  *
1243  * \return How colocation constraint should be applied at this point
1244  */
1245 enum pcmk__coloc_affects
1246 pcmk__colocation_affects(const pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1247                          const pcmk_resource_t *primary,
1248                          const pcmk__colocation_t *colocation, bool preview)
1249 {
1250     const pcmk_resource_t *dependent_role_rsc = NULL;
1251     const pcmk_resource_t *primary_role_rsc = NULL;
1252 
1253     pcmk__assert((dependent != NULL) && (primary != NULL)
1254                  && (colocation != NULL));
1255 
1256     if (!preview && pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
1257         // Primary resource has not been assigned yet, so we can't do anything
1258         return pcmk__coloc_affects_nothing;
1259     }
1260 
1261     dependent_role_rsc = get_resource_for_role(dependent);
1262     primary_role_rsc = get_resource_for_role(primary);
1263 
1264     if ((colocation->dependent_role >= pcmk_role_unpromoted)
1265         && (dependent_role_rsc->parent != NULL)
1266         && pcmk_is_set(dependent_role_rsc->parent->flags, pcmk_rsc_promotable)
1267         && !pcmk_is_set(dependent_role_rsc->flags, pcmk_rsc_unassigned)) {
1268 
1269         /* This is a colocation by role, and the dependent is a promotable clone
1270          * that has already been assigned, so the colocation should now affect
1271          * the role.
1272          */
1273         return pcmk__coloc_affects_role;
1274     }
1275 
1276     if (!preview && !pcmk_is_set(dependent->flags, pcmk_rsc_unassigned)) {
1277         /* The dependent resource has already been through assignment, so the
1278          * constraint no longer has any effect. Log an error if a mandatory
1279          * colocation constraint has been violated.
1280          */
1281 
1282         const pcmk_node_t *primary_node = primary->allocated_to;
1283 
1284         if (dependent->allocated_to == NULL) {
1285             crm_trace("Skipping colocation '%s': %s will not run anywhere",
1286                       colocation->id, dependent->id);
1287 
1288         } else if (colocation->score >= PCMK_SCORE_INFINITY) {
1289             // Dependent resource must colocate with primary resource
1290 
1291             if (!pcmk__same_node(primary_node, dependent->allocated_to)) {
1292                 pcmk__sched_err("%s must be colocated with %s but is not "
1293                                 "(%s vs. %s)",
1294                                 dependent->id, primary->id,
1295                                 pcmk__node_name(dependent->allocated_to),
1296                                 pcmk__node_name(primary_node));
1297             }
1298 
1299         } else if (colocation->score <= -PCMK_SCORE_INFINITY) {
1300             // Dependent resource must anti-colocate with primary resource
1301 
1302             if (pcmk__same_node(dependent->allocated_to, primary_node)) {
1303                 pcmk__sched_err("%s and %s must be anti-colocated but are "
1304                                 "assigned to the same node (%s)",
1305                                 dependent->id, primary->id,
1306                                 pcmk__node_name(primary_node));
1307             }
1308         }
1309         return pcmk__coloc_affects_nothing;
1310     }
1311 
1312     if ((colocation->dependent_role != pcmk_role_unknown)
1313         && (colocation->dependent_role != dependent_role_rsc->next_role)) {
1314         crm_trace("Skipping %scolocation '%s': dependent limited to %s role "
1315 
1316                   "but %s next role is %s",
1317                   ((colocation->score < 0)? "anti-" : ""),
1318                   colocation->id, pcmk_role_text(colocation->dependent_role),
1319                   dependent_role_rsc->id,
1320                   pcmk_role_text(dependent_role_rsc->next_role));
1321         return pcmk__coloc_affects_nothing;
1322     }
1323 
1324     if ((colocation->primary_role != pcmk_role_unknown)
1325         && (colocation->primary_role != primary_role_rsc->next_role)) {
1326         crm_trace("Skipping %scolocation '%s': primary limited to %s role "
1327                   "but %s next role is %s",
1328                   ((colocation->score < 0)? "anti-" : ""),
1329                   colocation->id, pcmk_role_text(colocation->primary_role),
1330                   primary_role_rsc->id,
1331                   pcmk_role_text(primary_role_rsc->next_role));
1332         return pcmk__coloc_affects_nothing;
1333     }
1334 
1335     return pcmk__coloc_affects_location;
1336 }
1337 
1338 /*!
1339  * \internal
1340  * \brief Apply colocation to dependent for assignment purposes
1341  *
1342  * Update the allowed node scores of the dependent resource in a colocation,
1343  * for the purposes of assigning it to a node.
1344  *
1345  * \param[in,out] dependent   Dependent resource in colocation
1346  * \param[in]     primary     Primary resource in colocation
1347  * \param[in]     colocation  Colocation constraint
1348  */
1349 void
1350 pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1351                             const pcmk_resource_t *primary,
1352                             const pcmk__colocation_t *colocation)
1353 {
1354     const char *attr = colocation->node_attribute;
1355     const char *value = NULL;
1356     GHashTable *work = NULL;
1357     GHashTableIter iter;
1358     pcmk_node_t *node = NULL;
1359 
1360     if (primary->allocated_to != NULL) {
1361         value = pcmk__colocation_node_attr(primary->allocated_to, attr,
1362                                            primary);
1363 
1364     } else if (colocation->score < 0) {
1365         // Nothing to do (anti-colocation with something that is not running)
1366         return;
1367     }
1368 
1369     work = pcmk__copy_node_table(dependent->allowed_nodes);
1370 
1371     g_hash_table_iter_init(&iter, work);
1372     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1373         if (primary->allocated_to == NULL) {
1374             node->weight = pcmk__add_scores(-colocation->score, node->weight);
1375             pcmk__rsc_trace(dependent,
1376                             "Applied %s to %s score on %s (now %s after "
1377                             "subtracting %s because primary %s inactive)",
1378                             colocation->id, dependent->id,
1379                             pcmk__node_name(node),
1380                             pcmk_readable_score(node->weight),
1381                             pcmk_readable_score(colocation->score), primary->id);
1382             continue;
1383         }
1384 
1385         if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent),
1386                          value, pcmk__str_casei)) {
1387 
1388             /* Add colocation score only if optional (or minus infinity). A
1389              * mandatory colocation is a requirement rather than a preference,
1390              * so we don't need to consider it for relative assignment purposes.
1391              * The resource will simply be forbidden from running on the node if
1392              * the primary isn't active there (via the condition above).
1393              */
1394             if (colocation->score < PCMK_SCORE_INFINITY) {
1395                 node->weight = pcmk__add_scores(colocation->score,
1396                                                 node->weight);
1397                 pcmk__rsc_trace(dependent,
1398                                 "Applied %s to %s score on %s (now %s after "
1399                                 "adding %s)",
1400                                 colocation->id, dependent->id,
1401                                 pcmk__node_name(node),
1402                                 pcmk_readable_score(node->weight),
1403                                 pcmk_readable_score(colocation->score));
1404             }
1405             continue;
1406         }
1407 
1408         if (colocation->score >= PCMK_SCORE_INFINITY) {
1409             /* Only mandatory colocations are relevant when the colocation
1410              * attribute doesn't match, because an attribute not matching is not
1411              * a negative preference -- the colocation is simply relevant only
1412              * where it matches.
1413              */
1414             node->weight = -PCMK_SCORE_INFINITY;
1415             pcmk__rsc_trace(dependent,
1416                             "Banned %s from %s because colocation %s attribute %s "
1417                             "does not match",
1418                             dependent->id, pcmk__node_name(node),
1419                             colocation->id, attr);
1420         }
1421     }
1422 
1423     if ((colocation->score <= -PCMK_SCORE_INFINITY)
1424         || (colocation->score >= PCMK_SCORE_INFINITY)
1425         || pcmk__any_node_available(work)) {
1426 
1427         g_hash_table_destroy(dependent->allowed_nodes);
1428         dependent->allowed_nodes = work;
1429         work = NULL;
1430 
1431     } else {
1432         pcmk__rsc_info(dependent,
1433                        "%s: Rolling back scores from %s (no available nodes)",
1434                        dependent->id, primary->id);
1435     }
1436 
1437     if (work != NULL) {
1438         g_hash_table_destroy(work);
1439     }
1440 }
1441 
1442 /*!
1443  * \internal
1444  * \brief Apply colocation to dependent for role purposes
1445  *
1446  * Update the priority of the dependent resource in a colocation, for the
1447  * purposes of selecting its role
1448  *
1449  * \param[in,out] dependent   Dependent resource in colocation
1450  * \param[in]     primary     Primary resource in colocation
1451  * \param[in]     colocation  Colocation constraint
1452  *
1453  * \return The score added to the dependent's priority
1454  */
1455 int
1456 pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1457                               const pcmk_resource_t *primary,
1458                               const pcmk__colocation_t *colocation)
1459 {
1460     const char *dependent_value = NULL;
1461     const char *primary_value = NULL;
1462     const char *attr = colocation->node_attribute;
1463     int score_multiplier = 1;
1464     int priority_delta = 0;
1465 
1466     pcmk__assert((dependent != NULL) && (primary != NULL)
1467                  && (colocation != NULL));
1468 
1469     if (dependent->allocated_to == NULL) {
1470         return 0;
1471     }
1472 
1473     if ((primary->allocated_to != NULL)
1474         && (colocation->primary_role != pcmk_role_unknown)) {
1475         /* Colocation applies only if the primary's next role matches.
1476          *
1477          * If primary->allocated_to == NULL, we want to proceed past this block,
1478          * so that dependent->allocated_to is marked ineligible for promotion.
1479          *
1480          * @TODO Why ignore a mandatory colocation in this case when we apply
1481          * its negation in the mismatched value case?
1482          */
1483         const pcmk_resource_t *role_rsc = get_resource_for_role(primary);
1484 
1485         if (colocation->primary_role != role_rsc->next_role) {
1486             return 0;
1487         }
1488     }
1489 
1490     dependent_value = pcmk__colocation_node_attr(dependent->allocated_to, attr,
1491                                                  dependent);
1492     primary_value = pcmk__colocation_node_attr(primary->allocated_to, attr,
1493                                                primary);
1494 
1495     if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) {
1496         if ((colocation->score == PCMK_SCORE_INFINITY)
1497             && (colocation->dependent_role == pcmk_role_promoted)) {
1498 
1499             /* For a mandatory promoted-role colocation, mark the dependent node
1500              * ineligible to promote the dependent if its attribute value
1501              * doesn't match the primary node's
1502              */
1503             score_multiplier = -1;
1504 
1505         } else {
1506             // Otherwise, ignore the colocation if attribute values don't match
1507             return 0;
1508         }
1509 
1510     } else if (colocation->dependent_role == pcmk_role_unpromoted) {
1511         /* Node attribute values matched, so we want to avoid promoting the
1512          * dependent on this node
1513          */
1514         score_multiplier = -1;
1515     }
1516 
1517     priority_delta = score_multiplier * colocation->score;
1518     dependent->priority = pcmk__add_scores(priority_delta, dependent->priority);
1519     pcmk__rsc_trace(dependent,
1520                     "Applied %s to %s promotion priority (now %s after %s %d)",
1521                     colocation->id, dependent->id,
1522                     pcmk_readable_score(dependent->priority),
1523                     ((score_multiplier == 1)? "adding" : "subtracting"),
1524                     colocation->score);
1525 
1526     return priority_delta;
1527 }
1528 
1529 /*!
1530  * \internal
1531  * \brief Find score of highest-scored node that matches colocation attribute
1532  *
1533  * \param[in]     colocation  Colocation constraint being applied
1534  * \param[in,out] rsc         Resource whose allowed nodes should be searched
1535  * \param[in]     attr        Colocation attribute name (must not be NULL)
1536  * \param[in]     value       Colocation attribute value to require
1537  */
1538 static int
1539 best_node_score_matching_attr(const pcmk__colocation_t *colocation,
     /* [previous][next][first][last][top][bottom][index][help] */
1540                               pcmk_resource_t *rsc, const char *attr,
1541                               const char *value)
1542 {
1543     GHashTable *allowed_nodes_orig = NULL;
1544     GHashTableIter iter;
1545     pcmk_node_t *node = NULL;
1546     int best_score = -PCMK_SCORE_INFINITY;
1547     const char *best_node = NULL;
1548 
1549     if ((colocation != NULL) && (rsc == colocation->dependent)
1550         && pcmk_is_set(colocation->flags, pcmk__coloc_explicit)
1551         && pcmk__is_group(rsc->parent)
1552         && (rsc != rsc->parent->children->data)) {
1553         /* The resource is a user-configured colocation's explicit dependent,
1554          * and a group member other than the first, which means the group's
1555          * location constraint scores were not applied to it (see
1556          * pcmk__group_apply_location()). Explicitly consider those scores now.
1557          *
1558          * @TODO This does leave one suboptimal case: if the group itself or
1559          * another member other than the first is explicitly colocated with
1560          * the same primary, the primary will count the group's location scores
1561          * multiple times. This is much less likely than a single member being
1562          * explicitly colocated, so it's an acceptable tradeoff for now.
1563          */
1564         allowed_nodes_orig = rsc->allowed_nodes;
1565         rsc->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig);
1566         for (GList *loc_iter = rsc->cluster->placement_constraints;
1567              loc_iter != NULL; loc_iter = loc_iter->next) {
1568 
1569             pcmk__location_t *location = loc_iter->data;
1570 
1571             if (location->rsc == rsc->parent) {
1572                 rsc->cmds->apply_location(rsc, location);
1573             }
1574         }
1575     }
1576 
1577     // Find best allowed node with matching attribute
1578     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
1579     while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) {
1580 
1581         if ((node->weight > best_score)
1582             && pcmk__node_available(node, false, false)
1583             && pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc),
1584                             pcmk__str_casei)) {
1585 
1586             best_score = node->weight;
1587             best_node = node->details->uname;
1588         }
1589     }
1590 
1591     if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) {
1592         if (best_node == NULL) {
1593             crm_info("No allowed node for %s matches node attribute %s=%s",
1594                      rsc->id, attr, value);
1595         } else {
1596             crm_info("Allowed node %s for %s had best score (%d) "
1597                      "of those matching node attribute %s=%s",
1598                      best_node, rsc->id, best_score, attr, value);
1599         }
1600     }
1601 
1602     if (allowed_nodes_orig != NULL) {
1603         g_hash_table_destroy(rsc->allowed_nodes);
1604         rsc->allowed_nodes = allowed_nodes_orig;
1605     }
1606     return best_score;
1607 }
1608 
1609 /*!
1610  * \internal
1611  * \brief Check whether a resource is allowed only on a single node
1612  *
1613  * \param[in] rsc   Resource to check
1614  *
1615  * \return \c true if \p rsc is allowed only on one node, otherwise \c false
1616  */
1617 static bool
1618 allowed_on_one(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
1619 {
1620     GHashTableIter iter;
1621     pcmk_node_t *allowed_node = NULL;
1622     int allowed_nodes = 0;
1623 
1624     g_hash_table_iter_init(&iter, rsc->allowed_nodes);
1625     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) {
1626         if ((allowed_node->weight >= 0) && (++allowed_nodes > 1)) {
1627             pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id);
1628             return false;
1629         }
1630     }
1631     pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id,
1632                     ((allowed_nodes == 1)? "on a single node" : "nowhere"));
1633     return (allowed_nodes == 1);
1634 }
1635 
1636 /*!
1637  * \internal
1638  * \brief Add resource's colocation matches to current node assignment scores
1639  *
1640  * For each node in a given table, if any of a given resource's allowed nodes
1641  * have a matching value for the colocation attribute, add the highest of those
1642  * nodes' scores to the node's score.
1643  *
1644  * \param[in,out] nodes          Table of nodes with assignment scores so far
1645  * \param[in,out] source_rsc     Resource whose node scores to add
1646  * \param[in]     target_rsc     Resource on whose behalf to update \p nodes
1647  * \param[in]     colocation     Original colocation constraint (used to get
1648  *                               configured primary resource's stickiness, and
1649  *                               to get colocation node attribute; pass NULL to
1650  *                               ignore stickiness and use default attribute)
1651  * \param[in]     factor         Factor by which to multiply scores being added
1652  * \param[in]     only_positive  Whether to add only positive scores
1653  */
1654 static void
1655 add_node_scores_matching_attr(GHashTable *nodes,
     /* [previous][next][first][last][top][bottom][index][help] */
1656                               pcmk_resource_t *source_rsc,
1657                               const pcmk_resource_t *target_rsc,
1658                               const pcmk__colocation_t *colocation,
1659                               float factor, bool only_positive)
1660 {
1661     GHashTableIter iter;
1662     pcmk_node_t *node = NULL;
1663     const char *attr = colocation->node_attribute;
1664 
1665     // Iterate through each node
1666     g_hash_table_iter_init(&iter, nodes);
1667     while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1668         float delta_f = 0;
1669         int delta = 0;
1670         int score = 0;
1671         int new_score = 0;
1672         const char *value = pcmk__colocation_node_attr(node, attr, target_rsc);
1673 
1674         score = best_node_score_matching_attr(colocation, source_rsc, attr, value);
1675 
1676         if ((factor < 0) && (score < 0)) {
1677             /* If the dependent is anti-colocated, we generally don't want the
1678              * primary to prefer nodes that the dependent avoids. That could
1679              * lead to unnecessary shuffling of the primary when the dependent
1680              * hits its migration threshold somewhere, for example.
1681              *
1682              * However, there are cases when it is desirable. If the dependent
1683              * can't run anywhere but where the primary is, it would be
1684              * worthwhile to move the primary for the sake of keeping the
1685              * dependent active.
1686              *
1687              * We can't know that exactly at this point since we don't know
1688              * where the primary will be assigned, but we can limit considering
1689              * the preference to when the dependent is allowed only on one node.
1690              * This is less than ideal for multiple reasons:
1691              *
1692              * - the dependent could be allowed on more than one node but have
1693              *   anti-colocation primaries on each;
1694              * - the dependent could be a clone or bundle with multiple
1695              *   instances, and the dependent as a whole is allowed on multiple
1696              *   nodes but some instance still can't run
1697              * - the dependent has considered node-specific criteria such as
1698              *   location constraints and stickiness by this point, but might
1699              *   have other factors that end up disallowing a node
1700              *
1701              * but the alternative is making the primary move when it doesn't
1702              * need to.
1703              *
1704              * We also consider the primary's stickiness and influence, so the
1705              * user has some say in the matter. (This is the configured primary,
1706              * not a particular instance of the primary, but that doesn't matter
1707              * unless stickiness uses a rule to vary by node, and that seems
1708              * acceptable to ignore.)
1709              */
1710             if ((colocation->primary->stickiness >= -score)
1711                 || !pcmk__colocation_has_influence(colocation, NULL)
1712                 || !allowed_on_one(colocation->dependent)) {
1713                 crm_trace("%s: Filtering %d + %f * %d "
1714                           "(double negative disallowed)",
1715                           pcmk__node_name(node), node->weight, factor, score);
1716                 continue;
1717             }
1718         }
1719 
1720         if (node->weight == INFINITY_HACK) {
1721             crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)",
1722                       pcmk__node_name(node), node->weight, factor, score);
1723             continue;
1724         }
1725 
1726         delta_f = factor * score;
1727 
1728         // Round the number; see http://c-faq.com/fp/round.html
1729         delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5));
1730 
1731         /* Small factors can obliterate the small scores that are often actually
1732          * used in configurations. If the score and factor are nonzero, ensure
1733          * that the result is nonzero as well.
1734          */
1735         if ((delta == 0) && (score != 0)) {
1736             if (factor > 0.0) {
1737                 delta = 1;
1738             } else if (factor < 0.0) {
1739                 delta = -1;
1740             }
1741         }
1742 
1743         new_score = pcmk__add_scores(delta, node->weight);
1744 
1745         if (only_positive && (new_score < 0) && (node->weight > 0)) {
1746             crm_trace("%s: Filtering %d + %f * %d = %d "
1747                       "(negative disallowed, marking node unusable)",
1748                       pcmk__node_name(node), node->weight, factor, score,
1749                       new_score);
1750             node->weight = INFINITY_HACK;
1751             continue;
1752         }
1753 
1754         if (only_positive && (new_score < 0) && (node->weight == 0)) {
1755             crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)",
1756                       pcmk__node_name(node), node->weight, factor, score,
1757                       new_score);
1758             continue;
1759         }
1760 
1761         crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node),
1762                   node->weight, factor, score, new_score);
1763         node->weight = new_score;
1764     }
1765 }
1766 
1767 /*!
1768  * \internal
1769  * \brief Update nodes with scores of colocated resources' nodes
1770  *
1771  * Given a table of nodes and a resource, update the nodes' scores with the
1772  * scores of the best nodes matching the attribute used for each of the
1773  * resource's relevant colocations.
1774  *
1775  * \param[in,out] source_rsc  Resource whose node scores to add
1776  * \param[in]     target_rsc  Resource on whose behalf to update \p *nodes
1777  * \param[in]     log_id      Resource ID for logs (if \c NULL, use
1778  *                            \p source_rsc ID)
1779  * \param[in,out] nodes       Nodes to update (set initial contents to \c NULL
1780  *                            to copy allowed nodes from \p source_rsc)
1781  * \param[in]     colocation  Original colocation constraint (used to get
1782  *                            configured primary resource's stickiness, and
1783  *                            to get colocation node attribute; if \c NULL,
1784  *                            <tt>source_rsc</tt>'s own matching node scores
1785  *                            will not be added, and \p *nodes must be \c NULL
1786  *                            as well)
1787  * \param[in]     factor      Incorporate scores multiplied by this factor
1788  * \param[in]     flags       Bitmask of enum pcmk__coloc_select values
1789  *
1790  * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
1791  *       the \c pcmk__coloc_select_this_with flag are used together (and only by
1792  *       \c cmp_resources()).
1793  * \note The caller remains responsible for freeing \p *nodes.
1794  * \note This is the shared implementation of
1795  *       \c pcmk_assignment_methods_t:add_colocated_node_scores().
1796  */
1797 void
1798 pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc,
     /* [previous][next][first][last][top][bottom][index][help] */
1799                                 const pcmk_resource_t *target_rsc,
1800                                 const char *log_id,
1801                                 GHashTable **nodes,
1802                                 const pcmk__colocation_t *colocation,
1803                                 float factor, uint32_t flags)
1804 {
1805     GHashTable *work = NULL;
1806 
1807     pcmk__assert((source_rsc != NULL) && (nodes != NULL)
1808                  && ((colocation != NULL)
1809                      || ((target_rsc == NULL) && (*nodes == NULL))));
1810 
1811     if (log_id == NULL) {
1812         log_id = source_rsc->id;
1813     }
1814 
1815     // Avoid infinite recursion
1816     if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
1817         pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
1818                        log_id, source_rsc->id);
1819         return;
1820     }
1821     pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1822 
1823     if (*nodes == NULL) {
1824         work = pcmk__copy_node_table(source_rsc->allowed_nodes);
1825         target_rsc = source_rsc;
1826     } else {
1827         const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative);
1828 
1829         pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)",
1830                         log_id, (pos? "positive" : "all"), source_rsc->id, factor);
1831         work = pcmk__copy_node_table(*nodes);
1832         add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation,
1833                                       factor, pos);
1834     }
1835 
1836     if (work == NULL) {
1837         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1838         return;
1839     }
1840 
1841     if (pcmk__any_node_available(work)) {
1842         GList *colocations = NULL;
1843 
1844         if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
1845             colocations = pcmk__this_with_colocations(source_rsc);
1846             pcmk__rsc_trace(source_rsc,
1847                             "Checking additional %d optional '%s with' "
1848                             "constraints",
1849                             g_list_length(colocations), source_rsc->id);
1850         } else {
1851             colocations = pcmk__with_this_colocations(source_rsc);
1852             pcmk__rsc_trace(source_rsc,
1853                             "Checking additional %d optional 'with %s' "
1854                             "constraints",
1855                             g_list_length(colocations), source_rsc->id);
1856         }
1857         flags |= pcmk__coloc_select_active;
1858 
1859         for (GList *iter = colocations; iter != NULL; iter = iter->next) {
1860             pcmk__colocation_t *constraint = iter->data;
1861 
1862             pcmk_resource_t *other = NULL;
1863             float other_factor = factor * constraint->score
1864                                  / (float) PCMK_SCORE_INFINITY;
1865 
1866             if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) {
1867                 other = constraint->primary;
1868             } else if (!pcmk__colocation_has_influence(constraint, NULL)) {
1869                 continue;
1870             } else {
1871                 other = constraint->dependent;
1872             }
1873 
1874             pcmk__rsc_trace(source_rsc,
1875                             "Optionally merging score of '%s' constraint "
1876                             "(%s with %s)",
1877                             constraint->id, constraint->dependent->id,
1878                             constraint->primary->id);
1879             other->cmds->add_colocated_node_scores(other, target_rsc, log_id,
1880                                                    &work, constraint,
1881                                                    other_factor, flags);
1882             pe__show_node_scores(true, NULL, log_id, work, source_rsc->cluster);
1883         }
1884         g_list_free(colocations);
1885 
1886     } else if (pcmk_is_set(flags, pcmk__coloc_select_active)) {
1887         pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s",
1888                        log_id, source_rsc->id);
1889         g_hash_table_destroy(work);
1890         pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1891         return;
1892     }
1893 
1894 
1895     if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) {
1896         pcmk_node_t *node = NULL;
1897         GHashTableIter iter;
1898 
1899         g_hash_table_iter_init(&iter, work);
1900         while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
1901             if (node->weight == INFINITY_HACK) {
1902                 node->weight = 1;
1903             }
1904         }
1905     }
1906 
1907     if (*nodes != NULL) {
1908        g_hash_table_destroy(*nodes);
1909     }
1910     *nodes = work;
1911 
1912     pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
1913 }
1914 
1915 /*!
1916  * \internal
1917  * \brief Apply a "with this" colocation to a resource's allowed node scores
1918  *
1919  * \param[in,out] data       Colocation to apply
1920  * \param[in,out] user_data  Resource being assigned
1921  */
1922 void
1923 pcmk__add_dependent_scores(gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
1924 {
1925     pcmk__colocation_t *colocation = data;
1926     pcmk_resource_t *target_rsc = user_data;
1927 
1928     pcmk_resource_t *source_rsc = colocation->dependent;
1929     const float factor = colocation->score / (float) PCMK_SCORE_INFINITY;
1930     uint32_t flags = pcmk__coloc_select_active;
1931 
1932     if (!pcmk__colocation_has_influence(colocation, NULL)) {
1933         return;
1934     }
1935     if (pcmk__is_clone(target_rsc)) {
1936         flags |= pcmk__coloc_select_nonnegative;
1937     }
1938     pcmk__rsc_trace(target_rsc,
1939                     "%s: Incorporating attenuated %s assignment scores due "
1940                     "to colocation %s",
1941                     target_rsc->id, source_rsc->id, colocation->id);
1942     source_rsc->cmds->add_colocated_node_scores(source_rsc, target_rsc,
1943                                                 source_rsc->id,
1944                                                 &target_rsc->allowed_nodes,
1945                                                 colocation, factor, flags);
1946 }
1947 
1948 /*!
1949  * \internal
1950  * \brief Exclude nodes from a dependent's node table if not in a given list
1951  *
1952  * Given a dependent resource in a colocation and a list of nodes where the
1953  * primary resource will run, set a node's score to \c -INFINITY in the
1954  * dependent's node table if not found in the primary nodes list.
1955  *
1956  * \param[in,out] dependent      Dependent resource
1957  * \param[in]     primary        Primary resource (for logging only)
1958  * \param[in]     colocation     Colocation constraint (for logging only)
1959  * \param[in]     primary_nodes  List of nodes where the primary will have
1960  *                               unblocked instances in a suitable role
1961  * \param[in]     merge_scores   If \c true and a node is found in both \p table
1962  *                               and \p list, add the node's score in \p list to
1963  *                               the node's score in \p table
1964  */
1965 void
1966 pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent,
     /* [previous][next][first][last][top][bottom][index][help] */
1967                                  const pcmk_resource_t *primary,
1968                                  const pcmk__colocation_t *colocation,
1969                                  const GList *primary_nodes, bool merge_scores)
1970 {
1971     GHashTableIter iter;
1972     pcmk_node_t *dependent_node = NULL;
1973 
1974     pcmk__assert((dependent != NULL) && (primary != NULL)
1975                  && (colocation != NULL));
1976 
1977     g_hash_table_iter_init(&iter, dependent->allowed_nodes);
1978     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) {
1979         const pcmk_node_t *primary_node = NULL;
1980 
1981         primary_node = pe_find_node_id(primary_nodes,
1982                                        dependent_node->details->id);
1983         if (primary_node == NULL) {
1984             dependent_node->weight = -PCMK_SCORE_INFINITY;
1985             pcmk__rsc_trace(dependent,
1986                             "Banning %s from %s (no primary instance) for %s",
1987                             dependent->id, pcmk__node_name(dependent_node),
1988                             colocation->id);
1989 
1990         } else if (merge_scores) {
1991             dependent_node->weight = pcmk__add_scores(dependent_node->weight,
1992                                                       primary_node->weight);
1993             pcmk__rsc_trace(dependent,
1994                             "Added %s's score %s to %s's score for %s (now %s) "
1995                             "for colocation %s",
1996                             primary->id, pcmk_readable_score(primary_node->weight),
1997                             dependent->id, pcmk__node_name(dependent_node),
1998                             pcmk_readable_score(dependent_node->weight),
1999                             colocation->id);
2000         }
2001     }
2002 }
2003 
2004 /*!
2005  * \internal
2006  * \brief Get all colocations affecting a resource as the primary
2007  *
2008  * \param[in] rsc  Resource to get colocations for
2009  *
2010  * \return Newly allocated list of colocations affecting \p rsc as primary
2011  *
2012  * \note This is a convenience wrapper for the with_this_colocations() method.
2013  */
2014 GList *
2015 pcmk__with_this_colocations(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
2016 {
2017     GList *list = NULL;
2018 
2019     rsc->cmds->with_this_colocations(rsc, rsc, &list);
2020     return list;
2021 }
2022 
2023 /*!
2024  * \internal
2025  * \brief Get all colocations affecting a resource as the dependent
2026  *
2027  * \param[in] rsc  Resource to get colocations for
2028  *
2029  * \return Newly allocated list of colocations affecting \p rsc as dependent
2030  *
2031  * \note This is a convenience wrapper for the this_with_colocations() method.
2032  */
2033 GList *
2034 pcmk__this_with_colocations(const pcmk_resource_t *rsc)
     /* [previous][next][first][last][top][bottom][index][help] */
2035 {
2036     GList *list = NULL;
2037 
2038     rsc->cmds->this_with_colocations(rsc, rsc, &list);
2039     return list;
2040 }

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