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