root/lib/pacemaker/pcmk_sched_colocation.c

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

DEFINITIONS

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

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