diff --git a/lib/pacemaker/pcmk_sched_group.c b/lib/pacemaker/pcmk_sched_group.c index fffb4fbeb5..ab97511b29 100644 --- a/lib/pacemaker/pcmk_sched_group.c +++ b/lib/pacemaker/pcmk_sched_group.c @@ -1,1001 +1,1018 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include "libpacemaker_private.h" /*! * \internal * \brief Assign a group resource to a node * * \param[in,out] rsc Group resource to assign to a node * \param[in] prefer Node to prefer, if all else is equal * \param[in] stop_if_fail If \c true and a child of \p rsc can't be * assigned to a node, set the child's next role to * stopped and update existing actions * * \return Node that \p rsc is assigned to, if assigned entirely to one node * * \note If \p stop_if_fail is \c false, then \c pcmk__unassign_resource() can * completely undo the assignment. A successful assignment can be either * undone or left alone as final. A failed assignment has the same effect * as calling pcmk__unassign_resource(); there are no side effects on * roles or actions. */ pcmk_node_t * pcmk__group_assign(pcmk_resource_t *rsc, const pcmk_node_t *prefer, bool stop_if_fail) { pcmk_node_t *first_assigned_node = NULL; pcmk_resource_t *first_member = NULL; CRM_ASSERT(pcmk__is_group(rsc)); if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) { return rsc->private->assigned_node; // Assignment already done } if (pcmk_is_set(rsc->flags, pcmk__rsc_assigning)) { pcmk__rsc_debug(rsc, "Assignment dependency loop detected involving %s", rsc->id); return NULL; } if (rsc->private->children == NULL) { // No members to assign pcmk__clear_rsc_flags(rsc, pcmk__rsc_unassigned); return NULL; } pcmk__set_rsc_flags(rsc, pcmk__rsc_assigning); first_member = (pcmk_resource_t *) rsc->private->children->data; rsc->private->orig_role = first_member->private->orig_role; pe__show_node_scores(!pcmk_is_set(rsc->private->scheduler->flags, pcmk_sched_output_scores), rsc, __func__, rsc->private->allowed_nodes, rsc->private->scheduler); for (GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; pcmk_node_t *node = NULL; pcmk__rsc_trace(rsc, "Assigning group %s member %s", rsc->id, member->id); node = member->private->cmds->assign(member, prefer, stop_if_fail); if (first_assigned_node == NULL) { first_assigned_node = node; } } pe__set_next_role(rsc, first_member->private->next_role, "first group member"); pcmk__clear_rsc_flags(rsc, pcmk__rsc_assigning|pcmk__rsc_unassigned); if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) { return NULL; } return first_assigned_node; } /*! * \internal * \brief Create a pseudo-operation for a group as an ordering point * * \param[in,out] group Group resource to create action for * \param[in] action Action name * * \return Newly created pseudo-operation */ static pcmk_action_t * create_group_pseudo_op(pcmk_resource_t *group, const char *action) { pcmk_action_t *op = custom_action(group, pcmk__op_key(group->id, action, 0), action, NULL, TRUE, group->private->scheduler); pcmk__set_action_flags(op, pcmk__action_pseudo|pcmk__action_runnable); return op; } /*! * \internal * \brief Create all actions needed for a given group resource * * \param[in,out] rsc Group resource to create actions for */ void pcmk__group_create_actions(pcmk_resource_t *rsc) { CRM_ASSERT(pcmk__is_group(rsc)); pcmk__rsc_trace(rsc, "Creating actions for group %s", rsc->id); // Create actions for individual group members for (GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; member->private->cmds->create_actions(member); } // Create pseudo-actions for group itself to serve as ordering points create_group_pseudo_op(rsc, PCMK_ACTION_START); create_group_pseudo_op(rsc, PCMK_ACTION_RUNNING); create_group_pseudo_op(rsc, PCMK_ACTION_STOP); create_group_pseudo_op(rsc, PCMK_ACTION_STOPPED); if (crm_is_true(g_hash_table_lookup(rsc->private->meta, PCMK_META_PROMOTABLE))) { create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTE); create_group_pseudo_op(rsc, PCMK_ACTION_DEMOTED); create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTE); create_group_pseudo_op(rsc, PCMK_ACTION_PROMOTED); } } // User data for member_internal_constraints() struct member_data { // These could be derived from member but this avoids some function calls bool ordered; bool colocated; bool promotable; pcmk_resource_t *last_active; pcmk_resource_t *previous_member; }; /*! * \internal * \brief Create implicit constraints needed for a group member * * \param[in,out] data Group member to create implicit constraints for * \param[in,out] user_data Member data (struct member_data *) */ static void member_internal_constraints(gpointer data, gpointer user_data) { pcmk_resource_t *member = (pcmk_resource_t *) data; struct member_data *member_data = (struct member_data *) user_data; // For ordering demote vs demote or stop vs stop uint32_t down_flags = pcmk__ar_then_implies_first_graphed; // For ordering demote vs demoted or stop vs stopped uint32_t post_down_flags = pcmk__ar_first_implies_then_graphed; // Create the individual member's implicit constraints member->private->cmds->internal_constraints(member); if (member_data->previous_member == NULL) { // This is first member if (member_data->ordered) { pcmk__set_relation_flags(down_flags, pcmk__ar_ordered); post_down_flags = pcmk__ar_first_implies_then; } } else if (member_data->colocated) { uint32_t flags = pcmk__coloc_none; if (pcmk_is_set(member->flags, pcmk__rsc_critical)) { flags |= pcmk__coloc_influence; } // Colocate this member with the previous one pcmk__new_colocation("#group-members", NULL, PCMK_SCORE_INFINITY, member, member_data->previous_member, NULL, NULL, flags); } if (member_data->promotable) { // Demote group -> demote member -> group is demoted pcmk__order_resource_actions(member->private->parent, PCMK_ACTION_DEMOTE, member, PCMK_ACTION_DEMOTE, down_flags); pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE, member->private->parent, PCMK_ACTION_DEMOTED, post_down_flags); // Promote group -> promote member -> group is promoted pcmk__order_resource_actions(member, PCMK_ACTION_PROMOTE, member->private->parent, PCMK_ACTION_PROMOTED, pcmk__ar_unrunnable_first_blocks |pcmk__ar_first_implies_then |pcmk__ar_first_implies_then_graphed); pcmk__order_resource_actions(member->private->parent, PCMK_ACTION_PROMOTE, member, PCMK_ACTION_PROMOTE, pcmk__ar_then_implies_first_graphed); } // Stop group -> stop member -> group is stopped pcmk__order_stops(member->private->parent, member, down_flags); pcmk__order_resource_actions(member, PCMK_ACTION_STOP, member->private->parent, PCMK_ACTION_STOPPED, post_down_flags); // Start group -> start member -> group is started pcmk__order_starts(member->private->parent, member, pcmk__ar_then_implies_first_graphed); pcmk__order_resource_actions(member, PCMK_ACTION_START, member->private->parent, PCMK_ACTION_RUNNING, pcmk__ar_unrunnable_first_blocks |pcmk__ar_first_implies_then |pcmk__ar_first_implies_then_graphed); if (!member_data->ordered) { pcmk__order_starts(member->private->parent, member, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks |pcmk__ar_then_implies_first_graphed); if (member_data->promotable) { pcmk__order_resource_actions(member->private->parent, PCMK_ACTION_PROMOTE, member, PCMK_ACTION_PROMOTE, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks |pcmk__ar_then_implies_first_graphed); } } else if (member_data->previous_member == NULL) { pcmk__order_starts(member->private->parent, member, pcmk__ar_none); if (member_data->promotable) { pcmk__order_resource_actions(member->private->parent, PCMK_ACTION_PROMOTE, member, PCMK_ACTION_PROMOTE, pcmk__ar_none); } } else { // Order this member relative to the previous one pcmk__order_starts(member_data->previous_member, member, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks); pcmk__order_stops(member, member_data->previous_member, pcmk__ar_ordered|pcmk__ar_intermediate_stop); /* In unusual circumstances (such as adding a new member to the middle * of a group with unmanaged later members), this member may be active * while the previous (new) member is inactive. In this situation, the * usual restart orderings will be irrelevant, so we need to order this * member's stop before the previous member's start. */ if ((member->private->active_nodes != NULL) && (member_data->previous_member->private->active_nodes == NULL)) { pcmk__order_resource_actions(member, PCMK_ACTION_STOP, member_data->previous_member, PCMK_ACTION_START, pcmk__ar_then_implies_first |pcmk__ar_unrunnable_first_blocks); } if (member_data->promotable) { pcmk__order_resource_actions(member_data->previous_member, PCMK_ACTION_PROMOTE, member, PCMK_ACTION_PROMOTE, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks); pcmk__order_resource_actions(member, PCMK_ACTION_DEMOTE, member_data->previous_member, PCMK_ACTION_DEMOTE, pcmk__ar_ordered); } } // Make sure partially active groups shut down in sequence if (member->private->active_nodes != NULL) { if (member_data->ordered && (member_data->previous_member != NULL) && (member_data->previous_member->private->active_nodes == NULL) && (member_data->last_active != NULL) && (member_data->last_active->private->active_nodes != NULL)) { pcmk__order_stops(member, member_data->last_active, pcmk__ar_ordered); } member_data->last_active = member; } member_data->previous_member = member; } /*! * \internal * \brief Create implicit constraints needed for a group resource * * \param[in,out] rsc Group resource to create implicit constraints for */ void pcmk__group_internal_constraints(pcmk_resource_t *rsc) { struct member_data member_data = { false, }; const pcmk_resource_t *top = NULL; CRM_ASSERT(pcmk__is_group(rsc)); /* Order group pseudo-actions relative to each other for restarting: * stop group -> group is stopped -> start group -> group is started */ pcmk__order_resource_actions(rsc, PCMK_ACTION_STOP, rsc, PCMK_ACTION_STOPPED, pcmk__ar_unrunnable_first_blocks); pcmk__order_resource_actions(rsc, PCMK_ACTION_STOPPED, rsc, PCMK_ACTION_START, pcmk__ar_ordered); pcmk__order_resource_actions(rsc, PCMK_ACTION_START, rsc, PCMK_ACTION_RUNNING, pcmk__ar_unrunnable_first_blocks); top = pe__const_top_resource(rsc, false); member_data.ordered = pe__group_flag_is_set(rsc, pcmk__group_ordered); member_data.colocated = pe__group_flag_is_set(rsc, pcmk__group_colocated); member_data.promotable = pcmk_is_set(top->flags, pcmk__rsc_promotable); g_list_foreach(rsc->private->children, member_internal_constraints, &member_data); } /*! * \internal * \brief Apply a colocation's score to node scores or resource priority * * Given a colocation constraint for a group with some other resource, apply the * score to the dependent's allowed node scores (if we are still placing * resources) or priority (if we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent group resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply */ static void colocate_group_with(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { pcmk_resource_t *member = NULL; if (dependent->private->children == NULL) { return; } pcmk__rsc_trace(primary, "Processing %s (group %s with %s) for dependent", colocation->id, dependent->id, primary->id); if (pe__group_flag_is_set(dependent, pcmk__group_colocated)) { // Colocate first member (internal colocations will handle the rest) member = (pcmk_resource_t *) dependent->private->children->data; member->private->cmds->apply_coloc_score(member, primary, colocation, true); return; } if (colocation->score >= PCMK_SCORE_INFINITY) { pcmk__config_err("%s: Cannot perform mandatory colocation between " "non-colocated group and %s", dependent->id, primary->id); return; } // Colocate each member individually for (GList *iter = dependent->private->children; iter != NULL; iter = iter->next) { member = (pcmk_resource_t *) iter->data; member->private->cmds->apply_coloc_score(member, primary, colocation, true); } } /*! * \internal * \brief Apply a colocation's score to node scores or resource priority * * Given a colocation constraint for some other resource with a group, apply the * score to the dependent's allowed node scores (if we are still placing * resources) or priority (if we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary group resource in colocation * \param[in] colocation Colocation constraint to apply */ static void colocate_with_group(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { const pcmk_resource_t *member = NULL; pcmk__rsc_trace(primary, "Processing colocation %s (%s with group %s) for primary", colocation->id, dependent->id, primary->id); if (pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) { return; } if (pe__group_flag_is_set(primary, pcmk__group_colocated)) { if (colocation->score >= PCMK_SCORE_INFINITY) { /* For mandatory colocations, the entire group must be assignable * (and in the specified role if any), so apply the colocation based * on the last member. */ member = pe__last_group_member(primary); } else if (primary->private->children != NULL) { /* For optional colocations, whether the group is partially or fully * up doesn't matter, so apply the colocation based on the first * member. */ member = (pcmk_resource_t *) primary->private->children->data; } if (member == NULL) { return; // Nothing to colocate with } member->private->cmds->apply_coloc_score(dependent, member, colocation, false); return; } if (colocation->score >= PCMK_SCORE_INFINITY) { pcmk__config_err("%s: Cannot perform mandatory colocation with" " non-colocated group %s", dependent->id, primary->id); return; } // Colocate dependent with each member individually for (const GList *iter = primary->private->children; iter != NULL; iter = iter->next) { member = iter->data; member->private->cmds->apply_coloc_score(dependent, member, colocation, false); } } /*! * \internal * \brief Apply a colocation's score to node scores or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node scores (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void pcmk__group_apply_coloc_score(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent) { CRM_ASSERT((dependent != NULL) && (primary != NULL) && (colocation != NULL)); if (for_dependent) { colocate_group_with(dependent, primary, colocation); } else { // Method should only be called for primitive dependents CRM_ASSERT(pcmk__is_primitive(dependent)); colocate_with_group(dependent, primary, colocation); } } /*! * \internal * \brief Return action flags for a given group resource action * * \param[in,out] action Group action to get flags for * \param[in] node If not NULL, limit effects to this node * * \return Flags appropriate to \p action on \p node */ uint32_t pcmk__group_action_flags(pcmk_action_t *action, const pcmk_node_t *node) { // Default flags for a group action uint32_t flags = pcmk__action_optional |pcmk__action_runnable |pcmk__action_pseudo; CRM_ASSERT(action != NULL); // Update flags considering each member's own flags for same action for (GList *iter = action->rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; // Check whether member has the same action enum pcmk__action_type task = get_complex_task(member, action->task); const char *task_s = pcmk__action_text(task); pcmk_action_t *member_action = NULL; member_action = find_first_action(member->private->actions, NULL, task_s, node); if (member_action != NULL) { uint32_t member_flags = 0U; member_flags = member->private->cmds->action_flags(member_action, node); // Group action is mandatory if any member action is if (pcmk_is_set(flags, pcmk__action_optional) && !pcmk_is_set(member_flags, pcmk__action_optional)) { pcmk__rsc_trace(action->rsc, "%s is mandatory because %s is", action->uuid, member_action->uuid); pcmk__clear_raw_action_flags(flags, "group action", pcmk__action_optional); pcmk__clear_action_flags(action, pcmk__action_optional); } // Group action is unrunnable if any member action is if (!pcmk__str_eq(task_s, action->task, pcmk__str_none) && pcmk_is_set(flags, pcmk__action_runnable) && !pcmk_is_set(member_flags, pcmk__action_runnable)) { pcmk__rsc_trace(action->rsc, "%s is unrunnable because %s is", action->uuid, member_action->uuid); pcmk__clear_raw_action_flags(flags, "group action", pcmk__action_runnable); pcmk__clear_action_flags(action, pcmk__action_runnable); } /* Group (pseudo-)actions other than stop or demote are unrunnable * unless every member will do it. */ } else if ((task != pcmk__action_stop) && (task != pcmk__action_demote)) { pcmk__rsc_trace(action->rsc, "%s is not runnable because %s will not %s", action->uuid, member->id, task_s); pcmk__clear_raw_action_flags(flags, "group action", pcmk__action_runnable); } } return flags; } /*! * \internal * \brief Update two actions according to an ordering between them * * Given information about an ordering of two actions, update the actions' flags * (and runnable_before members if appropriate) as appropriate for the ordering. * Effects may cascade to other orderings involving the actions as well. * * \param[in,out] first 'First' action in an ordering * \param[in,out] then 'Then' action in an ordering * \param[in] node If not NULL, limit scope of ordering to this node * (only used when interleaving instances) * \param[in] flags Action flags for \p first for ordering purposes * \param[in] filter Action flags to limit scope of certain updates (may * include pcmk__action_optional to affect only * mandatory actions, and pcmk__action_runnable to * affect only runnable actions) * \param[in] type Group of enum pcmk__action_relation_flags to apply * \param[in,out] scheduler Scheduler data * * \return Group of enum pcmk__updated flags indicating what was updated */ uint32_t pcmk__group_update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then, const pcmk_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pcmk_scheduler_t *scheduler) { uint32_t changed = pcmk__updated_none; // Group method can be called only on behalf of "then" action CRM_ASSERT((first != NULL) && (then != NULL) && (then->rsc != NULL) && (scheduler != NULL)); // Update the actions for the group itself changed |= pcmk__update_ordered_actions(first, then, node, flags, filter, type, scheduler); // Update the actions for each group member for (GList *iter = then->rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; pcmk_action_t *member_action = NULL; member_action = find_first_action(member->private->actions, NULL, then->task, node); if (member_action == NULL) { continue; } changed |= member->private->cmds->update_ordered_actions(first, member_action, node, flags, filter, type, scheduler); } return changed; } /*! * \internal * \brief Apply a location constraint to a group's allowed node scores * * \param[in,out] rsc Group resource to apply constraint to * \param[in,out] location Location constraint to apply */ void pcmk__group_apply_location(pcmk_resource_t *rsc, pcmk__location_t *location) { GList *node_list_orig = NULL; GList *node_list_copy = NULL; - bool reset_scores = true; CRM_ASSERT(pcmk__is_group(rsc) && (location != NULL)); + // Save the constraint's original node list (with the constraint score) node_list_orig = location->nodes; - node_list_copy = pcmk__copy_node_list(node_list_orig, true); - reset_scores = pe__group_flag_is_set(rsc, pcmk__group_colocated); - // Apply the constraint for the group itself (updates node scores) + // Make a copy of the nodes with all zero scores + node_list_copy = pcmk__copy_node_list(node_list_orig, true); + + /* Apply the constraint to the group itself. This ensures that any nodes + * affected by the constraint are in the group's allowed nodes, with the + * constraint score added. + */ pcmk__apply_location(rsc, location); // Apply the constraint for each member for (GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; - member->private->cmds->apply_location(member, location); - - if (reset_scores) { - /* The first member of colocated groups needs to use the original - * node scores, but subsequent members should work on a copy, since - * the first member's scores already incorporate theirs. + if (pe__group_flag_is_set(rsc, pcmk__group_colocated) + && (iter != rsc->private->children)) { + /* When apply_location() is called below for the first member (iter + * == rsc->private->children), the constraint score will be added to + * the member's affected allowed nodes. + * + * For subsequent members, we reset the constraint's node table to + * the copy with all 0 scores. Otherwise, when assigning the member, + * the constraint score would be counted multiple times (once for + * each later member) due to internal group colocations. Though the + * 0 score will not affect these members' allowed node scores, it + * ensures that affected nodes are in each member's allowed nodes, + * enabling the member on those nodes in asymmetric clusters. + * + * This is suboptimal if a group member other than the first is + * explicitly colocated with another resource. In that case, when + * the colocation primary is assigned, it will not consider the + * group's location preferences. */ - reset_scores = false; location->nodes = node_list_copy; } + + member->private->cmds->apply_location(member, location); } location->nodes = node_list_orig; g_list_free_full(node_list_copy, free); } // Group implementation of pcmk__assignment_methods_t:colocated_resources() GList * pcmk__group_colocated_resources(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList *colocated_rscs) { CRM_ASSERT(pcmk__is_group(rsc)); if (orig_rsc == NULL) { orig_rsc = rsc; } if (pe__group_flag_is_set(rsc, pcmk__group_colocated) || pcmk__is_clone(rsc->private->parent)) { /* This group has colocated members and/or is cloned -- either way, * add every child's colocated resources to the list. The first and last * members will include the group's own colocations. */ colocated_rscs = g_list_prepend(colocated_rscs, (gpointer) rsc); for (const GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *member = iter->data; colocated_rscs = member->private->cmds->colocated_resources(member, orig_rsc, colocated_rscs); } } else if (rsc->private->children != NULL) { /* This group's members are not colocated, and the group is not cloned, * so just add the group's own colocations to the list. */ colocated_rscs = pcmk__colocated_resources(rsc, orig_rsc, colocated_rscs); } return colocated_rscs; } // Group implementation of pcmk__assignment_methods_t:with_this_colocations() void pcmk__with_group_colocations(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList **list) { const pcmk_resource_t *parent = NULL; CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc)); parent = rsc->private->parent; // Ignore empty groups if (rsc->private->children == NULL) { return; } /* "With this" colocations are needed only for the group itself and for its * last member. (Previous members will chain via the group internal * colocations.) */ if ((orig_rsc != rsc) && (orig_rsc != pe__last_group_member(rsc))) { return; } pcmk__rsc_trace(rsc, "Adding 'with %s' colocations to list for %s", rsc->id, orig_rsc->id); // Add the group's own colocations pcmk__add_with_this_list(list, rsc->private->with_this_colocations, orig_rsc); // If cloned, add any relevant colocations with the clone if (parent != NULL) { parent->private->cmds->with_this_colocations(parent, orig_rsc, list); } if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) { // @COMPAT Non-colocated groups are deprecated return; } // Add explicit colocations with the group's (other) children for (const GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *member = iter->data; if (member == orig_rsc) { continue; } member->private->cmds->with_this_colocations(member, orig_rsc, list); } } // Group implementation of pcmk__assignment_methods_t:this_with_colocations() void pcmk__group_with_colocations(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList **list) { const pcmk_resource_t *parent = NULL; const pcmk_resource_t *member = NULL; CRM_ASSERT((orig_rsc != NULL) && (list != NULL) && pcmk__is_group(rsc)); parent = rsc->private->parent; // Ignore empty groups if (rsc->private->children == NULL) { return; } /* "This with" colocations are normally needed only for the group itself and * for its first member. */ if ((rsc == orig_rsc) || (orig_rsc == rsc->private->children->data)) { pcmk__rsc_trace(rsc, "Adding '%s with' colocations to list for %s", rsc->id, orig_rsc->id); // Add the group's own colocations pcmk__add_this_with_list(list, rsc->private->this_with_colocations, orig_rsc); // If cloned, add any relevant colocations involving the clone if (parent != NULL) { parent->private->cmds->this_with_colocations(parent, orig_rsc, list); } if (!pe__group_flag_is_set(rsc, pcmk__group_colocated)) { // @COMPAT Non-colocated groups are deprecated return; } // Add explicit colocations involving the group's (other) children for (const GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { member = iter->data; if (member == orig_rsc) { continue; } member->private->cmds->this_with_colocations(member, orig_rsc, list); } return; } /* Later group members honor the group's colocations indirectly, due to the * internal group colocations that chain everything from the first member. * However, if an earlier group member is unmanaged, this chaining will not * happen, so the group's mandatory colocations must be explicitly added. */ for (const GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { member = iter->data; if (orig_rsc == member) { break; // We've seen all earlier members, and none are unmanaged } if (!pcmk_is_set(member->flags, pcmk__rsc_managed)) { crm_trace("Adding mandatory '%s with' colocations to list for " "member %s because earlier member %s is unmanaged", rsc->id, orig_rsc->id, member->id); for (const GList *cons_iter = rsc->private->this_with_colocations; cons_iter != NULL; cons_iter = cons_iter->next) { const pcmk__colocation_t *colocation = NULL; colocation = (const pcmk__colocation_t *) cons_iter->data; if (colocation->score == PCMK_SCORE_INFINITY) { pcmk__add_this_with(list, colocation, orig_rsc); } } // @TODO Add mandatory (or all?) clone constraints if cloned break; } } } /*! * \internal * \brief Update nodes with scores of colocated resources' nodes * * Given a table of nodes and a resource, update the nodes' scores with the * scores of the best nodes matching the attribute used for each of the * resource's relevant colocations. * * \param[in,out] source_rsc Group resource whose node scores to add * \param[in] target_rsc Resource on whose behalf to update \p *nodes * \param[in] log_id Resource ID for logs (if \c NULL, use * \p source_rsc ID) * \param[in,out] nodes Nodes to update (set initial contents to \c NULL * to copy allowed nodes from \p source_rsc) * \param[in] colocation Original colocation constraint (used to get * configured primary resource's stickiness, and * to get colocation node attribute; if \c NULL, * source_rsc's own matching node scores will * not be added, and \p *nodes must be \c NULL as * well) * \param[in] factor Incorporate scores multiplied by this factor * \param[in] flags Bitmask of enum pcmk__coloc_select values * * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and * the \c pcmk__coloc_select_this_with flag are used together (and only by * \c cmp_resources()). * \note The caller remains responsible for freeing \p *nodes. * \note This is the group implementation of * \c pcmk__assignment_methods_t:add_colocated_node_scores(). */ void pcmk__group_add_colocated_node_scores(pcmk_resource_t *source_rsc, const pcmk_resource_t *target_rsc, const char *log_id, GHashTable **nodes, const pcmk__colocation_t *colocation, float factor, uint32_t flags) { pcmk_resource_t *member = NULL; CRM_ASSERT(pcmk__is_group(source_rsc) && (nodes != NULL) && ((colocation != NULL) || ((target_rsc == NULL) && (*nodes == NULL)))); if (log_id == NULL) { log_id = source_rsc->id; } // Avoid infinite recursion if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) { pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s", log_id, source_rsc->id); return; } pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); // Ignore empty groups (only possible with schema validation disabled) if (source_rsc->private->children == NULL) { return; } /* Refer the operation to the first or last member as appropriate. * * cmp_resources() is the only caller that passes a NULL nodes table, * and is also the only caller using pcmk__coloc_select_this_with. * For "this with" colocations, the last member will recursively incorporate * all the other members' "this with" colocations via the internal group * colocations (and via the first member, the group's own colocations). * * For "with this" colocations, the first member works similarly. */ if (*nodes == NULL) { member = pe__last_group_member(source_rsc); } else { member = source_rsc->private->children->data; } pcmk__rsc_trace(source_rsc, "%s: Merging scores from group %s using member %s " "(at %.6f)", log_id, source_rsc->id, member->id, factor); member->private->cmds->add_colocated_node_scores(member, target_rsc, log_id, nodes, colocation, factor, flags); pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); } // Group implementation of pcmk__assignment_methods_t:add_utilization() void pcmk__group_add_utilization(const pcmk_resource_t *rsc, const pcmk_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization) { pcmk_resource_t *member = NULL; CRM_ASSERT((orig_rsc != NULL) && (utilization != NULL) && pcmk__is_group(rsc)); if (!pcmk_is_set(rsc->flags, pcmk__rsc_unassigned)) { return; } pcmk__rsc_trace(orig_rsc, "%s: Adding group %s as colocated utilization", orig_rsc->id, rsc->id); if (pe__group_flag_is_set(rsc, pcmk__group_colocated) || pcmk__is_clone(rsc->private->parent)) { // Every group member will be on same node, so sum all members for (GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { member = (pcmk_resource_t *) iter->data; if (pcmk_is_set(member->flags, pcmk__rsc_unassigned) && (g_list_find(all_rscs, member) == NULL)) { member->private->cmds->add_utilization(member, orig_rsc, all_rscs, utilization); } } } else if (rsc->private->children != NULL) { // Just add first member's utilization member = (pcmk_resource_t *) rsc->private->children->data; if ((member != NULL) && pcmk_is_set(member->flags, pcmk__rsc_unassigned) && (g_list_find(all_rscs, member) == NULL)) { member->private->cmds->add_utilization(member, orig_rsc, all_rscs, utilization); } } } void pcmk__group_shutdown_lock(pcmk_resource_t *rsc) { CRM_ASSERT(pcmk__is_group(rsc)); for (GList *iter = rsc->private->children; iter != NULL; iter = iter->next) { pcmk_resource_t *member = (pcmk_resource_t *) iter->data; member->private->cmds->shutdown_lock(member); } }