LCOV - code coverage report
Current view: top level - pacemaker - pcmk_sched_colocation.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 702 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 29 0.0 %

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

Generated by: LCOV version 1.14