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