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 :
14 : #include <crm/common/xml.h>
15 :
16 : #include <pacemaker-internal.h>
17 : #include "libpacemaker_private.h"
18 :
19 : /*!
20 : * \internal
21 : * \brief Assign a group resource to a node
22 : *
23 : * \param[in,out] rsc Group resource to assign to a node
24 : * \param[in] prefer Node to prefer, if all else is equal
25 : * \param[in] stop_if_fail If \c true and a child of \p rsc can't be
26 : * assigned to a node, set the child's next role to
27 : * stopped and update existing actions
28 : *
29 : * \return Node that \p rsc is assigned to, if assigned entirely to one node
30 : *
31 : * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can
32 : * completely undo the assignment. A successful assignment can be either
33 : * undone or left alone as final. A failed assignment has the same effect
34 : * as calling pcmk__unassign_resource(); there are no side effects on
35 : * roles or actions.
36 : */
37 : pcmk_node_t *
38 0 : pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer,
39 : bool stop_if_fail)
40 : {
41 0 : pcmk_node_t *first_assigned_node = NULL;
42 0 : pcmk_resource_t *first_member = NULL;
43 :
44 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
45 :
46 0 : if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
47 0 : return rsc->allocated_to; // Assignment already done
48 : }
49 0 : if (pcmk_is_set(rsc->flags, pcmk_rsc_assigning)) {
50 0 : pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s",
51 : rsc->id);
52 0 : return NULL;
53 : }
54 :
55 0 : if (rsc->children == NULL) {
56 : // No members to assign
57 0 : pcmk__clear_rsc_flags(rsc, pcmk_rsc_unassigned);
58 0 : return NULL;
59 : }
60 :
61 0 : pcmk__set_rsc_flags(rsc, pcmk_rsc_assigning);
62 0 : first_member = (pcmk_resource_t *) rsc->children->data;
63 0 : rsc->role = first_member->role;
64 :
65 0 : pe__show_node_scores(!pcmk_is_set(rsc->cluster->flags,
66 : pcmk_sched_output_scores),
67 : rsc, __func__, rsc->allowed_nodes, rsc->cluster);
68 :
69 0 : for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
70 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
71 0 : pcmk_node_t *node = NULL;
72 :
73 0 : pcmk__rsc_trace(rsc, "Assigning group %s member %s",
74 : rsc->id, member->id);
75 0 : node = member->cmds->assign(member, prefer, stop_if_fail);
76 0 : if (first_assigned_node == NULL) {
77 0 : first_assigned_node = node;
78 : }
79 : }
80 :
81 0 : pe__set_next_role(rsc, first_member->next_role, "first group member");
82 0 : pcmk__clear_rsc_flags(rsc, pcmk_rsc_assigning|pcmk_rsc_unassigned);
83 :
84 0 : if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
85 0 : return NULL;
86 : }
87 0 : return first_assigned_node;
88 : }
89 :
90 : /*!
91 : * \internal
92 : * \brief Create a pseudo-operation for a group as an ordering point
93 : *
94 : * \param[in,out] group Group resource to create action for
95 : * \param[in] action Action name
96 : *
97 : * \return Newly created pseudo-operation
98 : */
99 : static pcmk_action_t *
100 0 : create_group_pseudo_op(pcmk_resource_t *group, const char *action)
101 : {
102 0 : pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0),
103 : action, NULL, TRUE, group->cluster);
104 :
105 0 : pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable);
106 0 : return op;
107 : }
108 :
109 : /*!
110 : * \internal
111 : * \brief Create all actions needed for a given group resource
112 : *
113 : * \param[in,out] rsc Group resource to create actions for
114 : */
115 : void
116 0 : pcmk__group_create_actions(pcmk_resource_t *rsc)
117 : {
118 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
119 :
120 0 : pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id);
121 :
122 : // Create actions for individual group members
123 0 : for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
124 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
125 :
126 0 : member->cmds->create_actions(member);
127 : }
128 :
129 : // Create pseudo-actions for group itself to serve as ordering points
130 0 : create_group_pseudo_op(rsc, PCMK_ACTION_START);
131 0 : create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING);
132 0 : create_group_pseudo_op(rsc, PCMK_ACTION_STOP);
133 0 : create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED);
134 0 : if (crm_is_true(g_hash_table_lookup(rsc->meta, PCMK_META_PROMOTABLE))) {
135 0 : create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE);
136 0 : create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED);
137 0 : create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE);
138 0 : create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED);
139 : }
140 0 : }
141 :
142 : // User data for member_internal_constraints()
143 : struct member_data {
144 : // These could be derived from member but this avoids some function calls
145 : bool ordered;
146 : bool colocated;
147 : bool promotable;
148 :
149 : pcmk_resource_t *last_active;
150 : pcmk_resource_t *previous_member;
151 : };
152 :
153 : /*!
154 : * \internal
155 : * \brief Create implicit constraints needed for a group member
156 : *
157 : * \param[in,out] data Group member to create implicit constraints for
158 : * \param[in,out] user_data Member data (struct member_data *)
159 : */
160 : static void
161 0 : member_internal_constraints(gpointer data, gpointer user_data)
162 : {
163 0 : pcmk_resource_t *member = (pcmk_resource_t *) data;
164 0 : struct member_data *member_data = (struct member_data *) user_data;
165 :
166 : // For ordering demote vs demote or stop vs stop
167 0 : uint32_t down_flags = pcmk__ar_then_implies_first_graphed;
168 :
169 : // For ordering demote vs demoted or stop vs stopped
170 0 : uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed;
171 :
172 : // Create the individual member's implicit constraints
173 0 : member->cmds->internal_constraints(member);
174 :
175 0 : if (member_data->previous_member == NULL) {
176 : // This is first member
177 0 : if (member_data->ordered) {
178 0 : pcmk__set_relation_flags(down_flags, pcmk__ar_ordered);
179 0 : post_down_flags = pcmk__ar_first_implies_then;
180 : }
181 :
182 0 : } else if (member_data->colocated) {
183 0 : uint32_t flags = pcmk__coloc_none;
184 :
185 0 : if (pcmk_is_set(member->flags, pcmk_rsc_critical)) {
186 0 : flags |= pcmk__coloc_influence;
187 : }
188 :
189 : // Colocate this member with the previous one
190 0 : pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY,
191 : member, member_data->previous_member, NULL, NULL,
192 : flags);
193 : }
194 :
195 0 : if (member_data->promotable) {
196 : // Demote group -> demote member -> group is demoted
197 0 : pcmk__order_resource_actions(member->parent, PCMK_ACTION_DEMOTE,
198 : member, PCMK_ACTION_DEMOTE, down_flags);
199 0 : pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
200 : member->parent, PCMK_ACTION_DEMOTED,
201 : post_down_flags);
202 :
203 : // Promote group -> promote member -> group is promoted
204 0 : pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE,
205 : member->parent, PCMK_ACTION_PROMOTED,
206 : pcmk__ar_unrunnable_first_blocks
207 : |pcmk__ar_first_implies_then
208 : |pcmk__ar_first_implies_then_graphed);
209 0 : pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
210 : member, PCMK_ACTION_PROMOTE,
211 : pcmk__ar_then_implies_first_graphed);
212 : }
213 :
214 : // Stop group -> stop member -> group is stopped
215 0 : pcmk__order_stops(member->parent, member, down_flags);
216 0 : pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
217 : member->parent, PCMK_ACTION_STOPPED,
218 : post_down_flags);
219 :
220 : // Start group -> start member -> group is started
221 0 : pcmk__order_starts(member->parent, member,
222 : pcmk__ar_then_implies_first_graphed);
223 0 : pcmk__order_resource_actions(member, PCMK_ACTION_START,
224 : member->parent, PCMK_ACTION_RUNNING,
225 : pcmk__ar_unrunnable_first_blocks
226 : |pcmk__ar_first_implies_then
227 : |pcmk__ar_first_implies_then_graphed);
228 :
229 0 : if (!member_data->ordered) {
230 0 : pcmk__order_starts(member->parent, member,
231 : pcmk__ar_first_implies_then
232 : |pcmk__ar_unrunnable_first_blocks
233 : |pcmk__ar_then_implies_first_graphed);
234 0 : if (member_data->promotable) {
235 0 : pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
236 : member, PCMK_ACTION_PROMOTE,
237 : pcmk__ar_first_implies_then
238 : |pcmk__ar_unrunnable_first_blocks
239 : |pcmk__ar_then_implies_first_graphed);
240 : }
241 :
242 0 : } else if (member_data->previous_member == NULL) {
243 0 : pcmk__order_starts(member->parent, member, pcmk__ar_none);
244 0 : if (member_data->promotable) {
245 0 : pcmk__order_resource_actions(member->parent, PCMK_ACTION_PROMOTE,
246 : member, PCMK_ACTION_PROMOTE,
247 : pcmk__ar_none);
248 : }
249 :
250 : } else {
251 : // Order this member relative to the previous one
252 :
253 0 : pcmk__order_starts(member_data->previous_member, member,
254 : pcmk__ar_first_implies_then
255 : |pcmk__ar_unrunnable_first_blocks);
256 0 : pcmk__order_stops(member, member_data->previous_member,
257 : pcmk__ar_ordered|pcmk__ar_intermediate_stop);
258 :
259 : /* In unusual circumstances (such as adding a new member to the middle
260 : * of a group with unmanaged later members), this member may be active
261 : * while the previous (new) member is inactive. In this situation, the
262 : * usual restart orderings will be irrelevant, so we need to order this
263 : * member's stop before the previous member's start.
264 : */
265 0 : if ((member->running_on != NULL)
266 0 : && (member_data->previous_member->running_on == NULL)) {
267 0 : pcmk__order_resource_actions(member, PCMK_ACTION_STOP,
268 : member_data->previous_member,
269 : PCMK_ACTION_START,
270 : pcmk__ar_then_implies_first
271 : |pcmk__ar_unrunnable_first_blocks);
272 : }
273 :
274 0 : if (member_data->promotable) {
275 0 : pcmk__order_resource_actions(member_data->previous_member,
276 : PCMK_ACTION_PROMOTE, member,
277 : PCMK_ACTION_PROMOTE,
278 : pcmk__ar_first_implies_then
279 : |pcmk__ar_unrunnable_first_blocks);
280 0 : pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE,
281 : member_data->previous_member,
282 : PCMK_ACTION_DEMOTE, pcmk__ar_ordered);
283 : }
284 : }
285 :
286 : // Make sure partially active groups shut down in sequence
287 0 : if (member->running_on != NULL) {
288 0 : if (member_data->ordered && (member_data->previous_member != NULL)
289 0 : && (member_data->previous_member->running_on == NULL)
290 0 : && (member_data->last_active != NULL)
291 0 : && (member_data->last_active->running_on != NULL)) {
292 0 : pcmk__order_stops(member, member_data->last_active,
293 : pcmk__ar_ordered);
294 : }
295 0 : member_data->last_active = member;
296 : }
297 :
298 0 : member_data->previous_member = member;
299 0 : }
300 :
301 : /*!
302 : * \internal
303 : * \brief Create implicit constraints needed for a group resource
304 : *
305 : * \param[in,out] rsc Group resource to create implicit constraints for
306 : */
307 : void
308 0 : pcmk__group_internal_constraints(pcmk_resource_t *rsc)
309 : {
310 0 : struct member_data member_data = { false, };
311 0 : const pcmk_resource_t *top = NULL;
312 :
313 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
314 :
315 : /* Order group pseudo-actions relative to each other for restarting:
316 : * stop group -> group is stopped -> start group -> group is started
317 : */
318 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP,
319 : rsc, PCMK_ACTION_STOPPED,
320 : pcmk__ar_unrunnable_first_blocks);
321 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED,
322 : rsc, PCMK_ACTION_START,
323 : pcmk__ar_ordered);
324 0 : pcmk__order_resource_actions(rsc, PCMK_ACTION_START,
325 : rsc, PCMK_ACTION_RUNNING,
326 : pcmk__ar_unrunnable_first_blocks);
327 :
328 0 : top = pe__const_top_resource(rsc, false);
329 :
330 0 : member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered);
331 0 : member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated);
332 0 : member_data.promotable = pcmk_is_set(top->flags, pcmk_rsc_promotable);
333 0 : g_list_foreach(rsc->children, member_internal_constraints, &member_data);
334 0 : }
335 :
336 : /*!
337 : * \internal
338 : * \brief Apply a colocation's score to node scores or resource priority
339 : *
340 : * Given a colocation constraint for a group with some other resource, apply the
341 : * score to the dependent's allowed node scores (if we are still placing
342 : * resources) or priority (if we are choosing promotable clone instance roles).
343 : *
344 : * \param[in,out] dependent Dependent group resource in colocation
345 : * \param[in] primary Primary resource in colocation
346 : * \param[in] colocation Colocation constraint to apply
347 : */
348 : static void
349 0 : colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
350 : const pcmk__colocation_t *colocation)
351 : {
352 0 : pcmk_resource_t *member = NULL;
353 :
354 0 : if (dependent->children == NULL) {
355 0 : return;
356 : }
357 :
358 0 : pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent",
359 : colocation->id, dependent->id, primary->id);
360 :
361 0 : if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) {
362 : // Colocate first member (internal colocations will handle the rest)
363 0 : member = (pcmk_resource_t *) dependent->children->data;
364 0 : member->cmds->apply_coloc_score(member, primary, colocation, true);
365 0 : return;
366 : }
367 :
368 0 : if (colocation->score >= PCMK_SCORE_INFINITY) {
369 0 : pcmk__config_err("%s: Cannot perform mandatory colocation between "
370 : "non-colocated group and %s",
371 : dependent->id, primary->id);
372 0 : return;
373 : }
374 :
375 : // Colocate each member individually
376 0 : for (GList *iter = dependent->children; iter != NULL; iter = iter->next) {
377 0 : member = (pcmk_resource_t *) iter->data;
378 0 : member->cmds->apply_coloc_score(member, primary, colocation, true);
379 : }
380 : }
381 :
382 : /*!
383 : * \internal
384 : * \brief Apply a colocation's score to node scores or resource priority
385 : *
386 : * Given a colocation constraint for some other resource with a group, apply the
387 : * score to the dependent's allowed node scores (if we are still placing
388 : * resources) or priority (if we are choosing promotable clone instance roles).
389 : *
390 : * \param[in,out] dependent Dependent resource in colocation
391 : * \param[in] primary Primary group resource in colocation
392 : * \param[in] colocation Colocation constraint to apply
393 : */
394 : static void
395 0 : colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary,
396 : const pcmk__colocation_t *colocation)
397 : {
398 0 : const pcmk_resource_t *member = NULL;
399 :
400 0 : pcmk__rsc_trace(primary,
401 : "Processing colocation %s (%s with group %s) for primary",
402 : colocation->id, dependent->id, primary->id);
403 :
404 0 : if (pcmk_is_set(primary->flags, pcmk_rsc_unassigned)) {
405 0 : return;
406 : }
407 :
408 0 : if (pe__group_flag_is_set(primary, pcmk__group_colocated)) {
409 :
410 0 : if (colocation->score >= PCMK_SCORE_INFINITY) {
411 : /* For mandatory colocations, the entire group must be assignable
412 : * (and in the specified role if any), so apply the colocation based
413 : * on the last member.
414 : */
415 0 : member = pe__last_group_member(primary);
416 0 : } else if (primary->children != NULL) {
417 : /* For optional colocations, whether the group is partially or fully
418 : * up doesn't matter, so apply the colocation based on the first
419 : * member.
420 : */
421 0 : member = (pcmk_resource_t *) primary->children->data;
422 : }
423 0 : if (member == NULL) {
424 0 : return; // Nothing to colocate with
425 : }
426 :
427 0 : member->cmds->apply_coloc_score(dependent, member, colocation, false);
428 0 : return;
429 : }
430 :
431 0 : if (colocation->score >= PCMK_SCORE_INFINITY) {
432 0 : pcmk__config_err("%s: Cannot perform mandatory colocation with"
433 : " non-colocated group %s",
434 : dependent->id, primary->id);
435 0 : return;
436 : }
437 :
438 : // Colocate dependent with each member individually
439 0 : for (const GList *iter = primary->children; iter != NULL;
440 0 : iter = iter->next) {
441 0 : member = iter->data;
442 0 : member->cmds->apply_coloc_score(dependent, member, colocation, false);
443 : }
444 : }
445 :
446 : /*!
447 : * \internal
448 : * \brief Apply a colocation's score to node scores or resource priority
449 : *
450 : * Given a colocation constraint, apply its score to the dependent's
451 : * allowed node scores (if we are still placing resources) or priority (if
452 : * we are choosing promotable clone instance roles).
453 : *
454 : * \param[in,out] dependent Dependent resource in colocation
455 : * \param[in] primary Primary resource in colocation
456 : * \param[in] colocation Colocation constraint to apply
457 : * \param[in] for_dependent true if called on behalf of dependent
458 : */
459 : void
460 0 : pcmk__group_apply_coloc_score(pcmk_resource_t *dependent,
461 : const pcmk_resource_t *primary,
462 : const pcmk__colocation_t *colocation,
463 : bool for_dependent)
464 : {
465 0 : CRM_ASSERT((dependent != NULL) && (primary != NULL)
466 : && (colocation != NULL));
467 :
468 0 : if (for_dependent) {
469 0 : colocate_group_with(dependent, primary, colocation);
470 :
471 : } else {
472 : // Method should only be called for primitive dependents
473 0 : CRM_ASSERT(dependent->variant == pcmk_rsc_variant_primitive);
474 :
475 0 : colocate_with_group(dependent, primary, colocation);
476 : }
477 0 : }
478 :
479 : /*!
480 : * \internal
481 : * \brief Return action flags for a given group resource action
482 : *
483 : * \param[in,out] action Group action to get flags for
484 : * \param[in] node If not NULL, limit effects to this node
485 : *
486 : * \return Flags appropriate to \p action on \p node
487 : */
488 : uint32_t
489 0 : pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node)
490 : {
491 : // Default flags for a group action
492 0 : uint32_t flags = pcmk_action_optional
493 : |pcmk_action_runnable
494 : |pcmk_action_pseudo;
495 :
496 0 : CRM_ASSERT(action != NULL);
497 :
498 : // Update flags considering each member's own flags for same action
499 0 : for (GList *iter = action->rsc->children; iter != NULL; iter = iter->next) {
500 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
501 :
502 : // Check whether member has the same action
503 0 : enum action_tasks task = get_complex_task(member, action->task);
504 0 : const char *task_s = pcmk_action_text(task);
505 0 : pcmk_action_t *member_action = find_first_action(member->actions, NULL,
506 : task_s, node);
507 :
508 0 : if (member_action != NULL) {
509 0 : uint32_t member_flags = member->cmds->action_flags(member_action,
510 : node);
511 :
512 : // Group action is mandatory if any member action is
513 0 : if (pcmk_is_set(flags, pcmk_action_optional)
514 0 : && !pcmk_is_set(member_flags, pcmk_action_optional)) {
515 0 : pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is",
516 : action->uuid, member_action->uuid);
517 0 : pcmk__clear_raw_action_flags(flags, "group action",
518 : pcmk_action_optional);
519 0 : pcmk__clear_action_flags(action, pcmk_action_optional);
520 : }
521 :
522 : // Group action is unrunnable if any member action is
523 0 : if (!pcmk__str_eq(task_s, action->task, pcmk__str_none)
524 0 : && pcmk_is_set(flags, pcmk_action_runnable)
525 0 : && !pcmk_is_set(member_flags, pcmk_action_runnable)) {
526 :
527 0 : pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is",
528 : action->uuid, member_action->uuid);
529 0 : pcmk__clear_raw_action_flags(flags, "group action",
530 : pcmk_action_runnable);
531 0 : pcmk__clear_action_flags(action, pcmk_action_runnable);
532 : }
533 :
534 : /* Group (pseudo-)actions other than stop or demote are unrunnable
535 : * unless every member will do it.
536 : */
537 0 : } else if ((task != pcmk_action_stop) && (task != pcmk_action_demote)) {
538 0 : pcmk__rsc_trace(action->rsc,
539 : "%s is not runnable because %s will not %s",
540 : action->uuid, member->id, task_s);
541 0 : pcmk__clear_raw_action_flags(flags, "group action",
542 : pcmk_action_runnable);
543 : }
544 : }
545 :
546 0 : return flags;
547 : }
548 :
549 : /*!
550 : * \internal
551 : * \brief Update two actions according to an ordering between them
552 : *
553 : * Given information about an ordering of two actions, update the actions' flags
554 : * (and runnable_before members if appropriate) as appropriate for the ordering.
555 : * Effects may cascade to other orderings involving the actions as well.
556 : *
557 : * \param[in,out] first 'First' action in an ordering
558 : * \param[in,out] then 'Then' action in an ordering
559 : * \param[in] node If not NULL, limit scope of ordering to this node
560 : * (only used when interleaving instances)
561 : * \param[in] flags Action flags for \p first for ordering purposes
562 : * \param[in] filter Action flags to limit scope of certain updates (may
563 : * include pcmk_action_optional to affect only
564 : * mandatory actions, and pcmk_action_runnable to
565 : * affect only runnable actions)
566 : * \param[in] type Group of enum pcmk__action_relation_flags to apply
567 : * \param[in,out] scheduler Scheduler data
568 : *
569 : * \return Group of enum pcmk__updated flags indicating what was updated
570 : */
571 : uint32_t
572 0 : pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then,
573 : const pcmk_node_t *node, uint32_t flags,
574 : uint32_t filter, uint32_t type,
575 : pcmk_scheduler_t *scheduler)
576 : {
577 0 : uint32_t changed = pcmk__updated_none;
578 :
579 : // Group method can be called only on behalf of "then" action
580 0 : CRM_ASSERT((first != NULL) && (then != NULL) && (then->rsc != NULL)
581 : && (scheduler != NULL));
582 :
583 : // Update the actions for the group itself
584 0 : changed |= pcmk__update_ordered_actions(first, then, node, flags, filter,
585 : type, scheduler);
586 :
587 : // Update the actions for each group member
588 0 : for (GList *iter = then->rsc->children; iter != NULL; iter = iter->next) {
589 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
590 :
591 0 : pcmk_action_t *member_action = find_first_action(member->actions, NULL,
592 0 : then->task, node);
593 :
594 0 : if (member_action != NULL) {
595 0 : changed |= member->cmds->update_ordered_actions(first,
596 : member_action, node,
597 : flags, filter, type,
598 : scheduler);
599 : }
600 : }
601 0 : return changed;
602 : }
603 :
604 : /*!
605 : * \internal
606 : * \brief Apply a location constraint to a group's allowed node scores
607 : *
608 : * \param[in,out] rsc Group resource to apply constraint to
609 : * \param[in,out] location Location constraint to apply
610 : */
611 : void
612 0 : pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location)
613 : {
614 0 : GList *node_list_orig = NULL;
615 0 : GList *node_list_copy = NULL;
616 0 : bool reset_scores = true;
617 :
618 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
619 : && (location != NULL));
620 :
621 0 : node_list_orig = location->nodes;
622 0 : node_list_copy = pcmk__copy_node_list(node_list_orig, true);
623 0 : reset_scores = pe__group_flag_is_set(rsc, pcmk__group_colocated);
624 :
625 : // Apply the constraint for the group itself (updates node scores)
626 0 : pcmk__apply_location(rsc, location);
627 :
628 : // Apply the constraint for each member
629 0 : for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
630 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
631 :
632 0 : member->cmds->apply_location(member, location);
633 :
634 0 : if (reset_scores) {
635 : /* The first member of colocated groups needs to use the original
636 : * node scores, but subsequent members should work on a copy, since
637 : * the first member's scores already incorporate theirs.
638 : */
639 0 : reset_scores = false;
640 0 : location->nodes = node_list_copy;
641 : }
642 : }
643 :
644 0 : location->nodes = node_list_orig;
645 0 : g_list_free_full(node_list_copy, free);
646 0 : }
647 :
648 : // Group implementation of pcmk_assignment_methods_t:colocated_resources()
649 : GList *
650 0 : pcmk__group_colocated_resources(const pcmk_resource_t *rsc,
651 : const pcmk_resource_t *orig_rsc,
652 : GList *colocated_rscs)
653 : {
654 0 : const pcmk_resource_t *member = NULL;
655 :
656 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
657 :
658 0 : if (orig_rsc == NULL) {
659 0 : orig_rsc = rsc;
660 : }
661 :
662 0 : if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
663 0 : || pcmk__is_clone(rsc->parent)) {
664 : /* This group has colocated members and/or is cloned -- either way,
665 : * add every child's colocated resources to the list. The first and last
666 : * members will include the group's own colocations.
667 : */
668 0 : colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc);
669 0 : for (const GList *iter = rsc->children;
670 0 : iter != NULL; iter = iter->next) {
671 :
672 0 : member = (const pcmk_resource_t *) iter->data;
673 0 : colocated_rscs = member->cmds->colocated_resources(member, orig_rsc,
674 : colocated_rscs);
675 : }
676 :
677 0 : } else if (rsc->children != NULL) {
678 : /* This group's members are not colocated, and the group is not cloned,
679 : * so just add the group's own colocations to the list.
680 : */
681 0 : colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc,
682 : colocated_rscs);
683 : }
684 :
685 0 : return colocated_rscs;
686 : }
687 :
688 : // Group implementation of pcmk_assignment_methods_t:with_this_colocations()
689 : void
690 0 : pcmk__with_group_colocations(const pcmk_resource_t *rsc,
691 : const pcmk_resource_t *orig_rsc, GList **list)
692 :
693 : {
694 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
695 : && (orig_rsc != NULL) && (list != NULL));
696 :
697 : // Ignore empty groups
698 0 : if (rsc->children == NULL) {
699 0 : return;
700 : }
701 :
702 : /* "With this" colocations are needed only for the group itself and for its
703 : * last member. (Previous members will chain via the group internal
704 : * colocations.)
705 : */
706 0 : if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) {
707 0 : return;
708 : }
709 :
710 0 : pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s",
711 : rsc->id, orig_rsc->id);
712 :
713 : // Add the group's own colocations
714 0 : pcmk__add_with_this_list(list, rsc->rsc_cons_lhs, orig_rsc);
715 :
716 : // If cloned, add any relevant colocations with the clone
717 0 : if (rsc->parent != NULL) {
718 0 : rsc->parent->cmds->with_this_colocations(rsc->parent, orig_rsc,
719 : list);
720 : }
721 :
722 0 : if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
723 : // @COMPAT Non-colocated groups are deprecated
724 0 : return;
725 : }
726 :
727 : // Add explicit colocations with the group's (other) children
728 0 : for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
729 0 : const pcmk_resource_t *member = iter->data;
730 :
731 0 : if (member != orig_rsc) {
732 0 : member->cmds->with_this_colocations(member, orig_rsc, list);
733 : }
734 : }
735 : }
736 :
737 : // Group implementation of pcmk_assignment_methods_t:this_with_colocations()
738 : void
739 0 : pcmk__group_with_colocations(const pcmk_resource_t *rsc,
740 : const pcmk_resource_t *orig_rsc, GList **list)
741 : {
742 0 : const pcmk_resource_t *member = NULL;
743 :
744 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
745 : && (orig_rsc != NULL) && (list != NULL));
746 :
747 : // Ignore empty groups
748 0 : if (rsc->children == NULL) {
749 0 : return;
750 : }
751 :
752 : /* "This with" colocations are normally needed only for the group itself and
753 : * for its first member.
754 : */
755 0 : if ((rsc == orig_rsc)
756 0 : || (orig_rsc == (const pcmk_resource_t *) rsc->children->data)) {
757 0 : pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s",
758 : rsc->id, orig_rsc->id);
759 :
760 : // Add the group's own colocations
761 0 : pcmk__add_this_with_list(list, rsc->rsc_cons, orig_rsc);
762 :
763 : // If cloned, add any relevant colocations involving the clone
764 0 : if (rsc->parent != NULL) {
765 0 : rsc->parent->cmds->this_with_colocations(rsc->parent, orig_rsc,
766 : list);
767 : }
768 :
769 0 : if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) {
770 : // @COMPAT Non-colocated groups are deprecated
771 0 : return;
772 : }
773 :
774 : // Add explicit colocations involving the group's (other) children
775 0 : for (const GList *iter = rsc->children;
776 0 : iter != NULL; iter = iter->next) {
777 0 : member = iter->data;
778 0 : if (member != orig_rsc) {
779 0 : member->cmds->this_with_colocations(member, orig_rsc, list);
780 : }
781 : }
782 0 : return;
783 : }
784 :
785 : /* Later group members honor the group's colocations indirectly, due to the
786 : * internal group colocations that chain everything from the first member.
787 : * However, if an earlier group member is unmanaged, this chaining will not
788 : * happen, so the group's mandatory colocations must be explicitly added.
789 : */
790 0 : for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) {
791 0 : member = iter->data;
792 0 : if (orig_rsc == member) {
793 0 : break; // We've seen all earlier members, and none are unmanaged
794 : }
795 :
796 0 : if (!pcmk_is_set(member->flags, pcmk_rsc_managed)) {
797 0 : crm_trace("Adding mandatory '%s with' colocations to list for "
798 : "member %s because earlier member %s is unmanaged",
799 : rsc->id, orig_rsc->id, member->id);
800 0 : for (const GList *cons_iter = rsc->rsc_cons; cons_iter != NULL;
801 0 : cons_iter = cons_iter->next) {
802 0 : const pcmk__colocation_t *colocation = NULL;
803 :
804 0 : colocation = (const pcmk__colocation_t *) cons_iter->data;
805 0 : if (colocation->score == PCMK_SCORE_INFINITY) {
806 0 : pcmk__add_this_with(list, colocation, orig_rsc);
807 : }
808 : }
809 : // @TODO Add mandatory (or all?) clone constraints if cloned
810 0 : break;
811 : }
812 : }
813 : }
814 :
815 : /*!
816 : * \internal
817 : * \brief Update nodes with scores of colocated resources' nodes
818 : *
819 : * Given a table of nodes and a resource, update the nodes' scores with the
820 : * scores of the best nodes matching the attribute used for each of the
821 : * resource's relevant colocations.
822 : *
823 : * \param[in,out] source_rsc Group resource whose node scores to add
824 : * \param[in] target_rsc Resource on whose behalf to update \p *nodes
825 : * \param[in] log_id Resource ID for logs (if \c NULL, use
826 : * \p source_rsc ID)
827 : * \param[in,out] nodes Nodes to update (set initial contents to \c NULL
828 : * to copy allowed nodes from \p source_rsc)
829 : * \param[in] colocation Original colocation constraint (used to get
830 : * configured primary resource's stickiness, and
831 : * to get colocation node attribute; if \c NULL,
832 : * <tt>source_rsc</tt>'s own matching node scores will
833 : * not be added, and \p *nodes must be \c NULL as
834 : * well)
835 : * \param[in] factor Incorporate scores multiplied by this factor
836 : * \param[in] flags Bitmask of enum pcmk__coloc_select values
837 : *
838 : * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and
839 : * the \c pcmk__coloc_select_this_with flag are used together (and only by
840 : * \c cmp_resources()).
841 : * \note The caller remains responsible for freeing \p *nodes.
842 : * \note This is the group implementation of
843 : * \c pcmk_assignment_methods_t:add_colocated_node_scores().
844 : */
845 : void
846 0 : pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc,
847 : const pcmk_resource_t *target_rsc,
848 : const char *log_id, GHashTable **nodes,
849 : const pcmk__colocation_t *colocation,
850 : float factor, uint32_t flags)
851 : {
852 0 : pcmk_resource_t *member = NULL;
853 :
854 0 : CRM_ASSERT((source_rsc != NULL)
855 : && (source_rsc->variant == pcmk_rsc_variant_group)
856 : && (nodes != NULL)
857 : && ((colocation != NULL)
858 : || ((target_rsc == NULL) && (*nodes == NULL))));
859 :
860 0 : if (log_id == NULL) {
861 0 : log_id = source_rsc->id;
862 : }
863 :
864 : // Avoid infinite recursion
865 0 : if (pcmk_is_set(source_rsc->flags, pcmk_rsc_updating_nodes)) {
866 0 : pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s",
867 : log_id, source_rsc->id);
868 0 : return;
869 : }
870 0 : pcmk__set_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
871 :
872 : // Ignore empty groups (only possible with schema validation disabled)
873 0 : if (source_rsc->children == NULL) {
874 0 : return;
875 : }
876 :
877 : /* Refer the operation to the first or last member as appropriate.
878 : *
879 : * cmp_resources() is the only caller that passes a NULL nodes table,
880 : * and is also the only caller using pcmk__coloc_select_this_with.
881 : * For "this with" colocations, the last member will recursively incorporate
882 : * all the other members' "this with" colocations via the internal group
883 : * colocations (and via the first member, the group's own colocations).
884 : *
885 : * For "with this" colocations, the first member works similarly.
886 : */
887 0 : if (*nodes == NULL) {
888 0 : member = pe__last_group_member(source_rsc);
889 : } else {
890 0 : member = source_rsc->children->data;
891 : }
892 0 : pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s "
893 : "(at %.6f)", log_id, source_rsc->id, member->id, factor);
894 0 : member->cmds->add_colocated_node_scores(member, target_rsc, log_id, nodes,
895 : colocation, factor, flags);
896 0 : pcmk__clear_rsc_flags(source_rsc, pcmk_rsc_updating_nodes);
897 : }
898 :
899 : // Group implementation of pcmk_assignment_methods_t:add_utilization()
900 : void
901 0 : pcmk__group_add_utilization(const pcmk_resource_t *rsc,
902 : const pcmk_resource_t *orig_rsc, GList *all_rscs,
903 : GHashTable *utilization)
904 : {
905 0 : pcmk_resource_t *member = NULL;
906 :
907 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group)
908 : && (orig_rsc != NULL) && (utilization != NULL));
909 :
910 0 : if (!pcmk_is_set(rsc->flags, pcmk_rsc_unassigned)) {
911 0 : return;
912 : }
913 :
914 0 : pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization",
915 : orig_rsc->id, rsc->id);
916 0 : if (pe__group_flag_is_set(rsc, pcmk__group_colocated)
917 0 : || pcmk__is_clone(rsc->parent)) {
918 : // Every group member will be on same node, so sum all members
919 0 : for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
920 0 : member = (pcmk_resource_t *) iter->data;
921 :
922 0 : if (pcmk_is_set(member->flags, pcmk_rsc_unassigned)
923 0 : && (g_list_find(all_rscs, member) == NULL)) {
924 0 : member->cmds->add_utilization(member, orig_rsc, all_rscs,
925 : utilization);
926 : }
927 : }
928 :
929 0 : } else if (rsc->children != NULL) {
930 : // Just add first member's utilization
931 0 : member = (pcmk_resource_t *) rsc->children->data;
932 0 : if ((member != NULL)
933 0 : && pcmk_is_set(member->flags, pcmk_rsc_unassigned)
934 0 : && (g_list_find(all_rscs, member) == NULL)) {
935 :
936 0 : member->cmds->add_utilization(member, orig_rsc, all_rscs,
937 : utilization);
938 : }
939 : }
940 : }
941 :
942 : void
943 0 : pcmk__group_shutdown_lock(pcmk_resource_t *rsc)
944 : {
945 0 : CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_group));
946 :
947 0 : for (GList *iter = rsc->children; iter != NULL; iter = iter->next) {
948 0 : pcmk_resource_t *member = (pcmk_resource_t *) iter->data;
949 :
950 0 : member->cmds->shutdown_lock(member);
951 : }
952 0 : }
|