| /* |
| * Copyright 2004-2018 Andrew Beekhof <andrew@beekhof.net> |
| * |
| * This source code is licensed under the GNU General Public License version 2 |
| * or later (GPLv2+) WITHOUT ANY WARRANTY. |
| */ |
| |
| #include <crm_internal.h> |
| |
| #include <pengine.h> |
| #include <crm/msg_xml.h> |
| |
| #include <allocate.h> |
| #include <utils.h> |
| |
| #define VARIANT_GROUP 1 |
| #include <lib/pengine/variant.h> |
| |
| pe_node_t * |
| pcmk__group_allocate(pe_resource_t *rsc, pe_node_t *prefer, |
| pe_working_set_t *data_set) |
| { |
| node_t *node = NULL; |
| node_t *group_node = NULL; |
| GListPtr gIter = NULL; |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, rsc); |
| |
| if (is_not_set(rsc->flags, pe_rsc_provisional)) { |
| return rsc->allocated_to; |
| } |
| if (is_set(rsc->flags, pe_rsc_allocating)) { |
| pe_rsc_debug(rsc, "Dependency loop detected involving %s", rsc->id); |
| return NULL; |
| } |
| |
| if (group_data->first_child == NULL) { |
| /* nothign to allocate */ |
| clear_bit(rsc->flags, pe_rsc_provisional); |
| return NULL; |
| } |
| |
| set_bit(rsc->flags, pe_rsc_allocating); |
| rsc->role = group_data->first_child->role; |
| |
| group_data->first_child->rsc_cons = |
| g_list_concat(group_data->first_child->rsc_cons, rsc->rsc_cons); |
| rsc->rsc_cons = NULL; |
| |
| group_data->last_child->rsc_cons_lhs = |
| g_list_concat(group_data->last_child->rsc_cons_lhs, rsc->rsc_cons_lhs); |
| rsc->rsc_cons_lhs = NULL; |
| |
| pe__show_node_weights(!show_scores, rsc, __FUNCTION__, rsc->allowed_nodes); |
| |
| gIter = rsc->children; |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| pe_rsc_trace(rsc, "Allocating group %s member %s", |
| rsc->id, child_rsc->id); |
| node = child_rsc->cmds->allocate(child_rsc, prefer, data_set); |
| if (group_node == NULL) { |
| group_node = node; |
| } |
| } |
| |
| rsc->next_role = group_data->first_child->next_role; |
| clear_bit(rsc->flags, pe_rsc_allocating); |
| clear_bit(rsc->flags, pe_rsc_provisional); |
| |
| if (group_data->colocated) { |
| return group_node; |
| } |
| return NULL; |
| } |
| |
| void group_update_pseudo_status(resource_t * parent, resource_t * child); |
| |
| void |
| group_create_actions(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| action_t *op = NULL; |
| const char *value = NULL; |
| GListPtr gIter = rsc->children; |
| |
| pe_rsc_trace(rsc, "Creating actions for %s", rsc->id); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->create_actions(child_rsc, data_set); |
| group_update_pseudo_status(rsc, child_rsc); |
| } |
| |
| op = start_action(rsc, NULL, TRUE /* !group_data->child_starting */ ); |
| set_bit(op->flags, pe_action_pseudo | pe_action_runnable); |
| |
| op = custom_action(rsc, started_key(rsc), |
| RSC_STARTED, NULL, TRUE /* !group_data->child_starting */ , TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo | pe_action_runnable); |
| |
| op = stop_action(rsc, NULL, TRUE /* !group_data->child_stopping */ ); |
| set_bit(op->flags, pe_action_pseudo | pe_action_runnable); |
| |
| op = custom_action(rsc, stopped_key(rsc), |
| RSC_STOPPED, NULL, TRUE /* !group_data->child_stopping */ , TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo | pe_action_runnable); |
| |
| value = g_hash_table_lookup(rsc->meta, "stateful"); |
| if (crm_is_true(value)) { |
| op = custom_action(rsc, demote_key(rsc), RSC_DEMOTE, NULL, TRUE, TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo); |
| set_bit(op->flags, pe_action_runnable); |
| op = custom_action(rsc, demoted_key(rsc), RSC_DEMOTED, NULL, TRUE, TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo); |
| set_bit(op->flags, pe_action_runnable); |
| |
| op = custom_action(rsc, promote_key(rsc), RSC_PROMOTE, NULL, TRUE, TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo); |
| set_bit(op->flags, pe_action_runnable); |
| op = custom_action(rsc, promoted_key(rsc), RSC_PROMOTED, NULL, TRUE, TRUE, data_set); |
| set_bit(op->flags, pe_action_pseudo); |
| set_bit(op->flags, pe_action_runnable); |
| } |
| } |
| |
| void |
| group_update_pseudo_status(resource_t * parent, resource_t * child) |
| { |
| GListPtr gIter = child->actions; |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, parent); |
| |
| if (group_data->ordered == FALSE) { |
| /* If this group is not ordered, then leave the meta-actions as optional */ |
| return; |
| } |
| |
| if (group_data->child_stopping && group_data->child_starting) { |
| return; |
| } |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| action_t *action = (action_t *) gIter->data; |
| |
| if (is_set(action->flags, pe_action_optional)) { |
| continue; |
| } |
| if (safe_str_eq(RSC_STOP, action->task) && is_set(action->flags, pe_action_runnable)) { |
| group_data->child_stopping = TRUE; |
| pe_rsc_trace(action->rsc, "Based on %s the group is stopping", action->uuid); |
| |
| } else if (safe_str_eq(RSC_START, action->task) |
| && is_set(action->flags, pe_action_runnable)) { |
| group_data->child_starting = TRUE; |
| pe_rsc_trace(action->rsc, "Based on %s the group is starting", action->uuid); |
| } |
| } |
| } |
| |
| void |
| group_internal_constraints(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| GListPtr gIter = rsc->children; |
| resource_t *last_rsc = NULL; |
| resource_t *last_active = NULL; |
| resource_t *top = uber_parent(rsc); |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, rsc); |
| |
| new_rsc_order(rsc, RSC_STOPPED, rsc, RSC_START, pe_order_optional, data_set); |
| new_rsc_order(rsc, RSC_START, rsc, RSC_STARTED, pe_order_runnable_left, data_set); |
| new_rsc_order(rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_runnable_left, data_set); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| int stop = pe_order_none; |
| int stopped = pe_order_implies_then_printed; |
| int start = pe_order_implies_then | pe_order_runnable_left; |
| int started = |
| pe_order_runnable_left | pe_order_implies_then | pe_order_implies_then_printed; |
| |
| child_rsc->cmds->internal_constraints(child_rsc, data_set); |
| |
| if (last_rsc == NULL) { |
| if (group_data->ordered) { |
| stop |= pe_order_optional; |
| stopped = pe_order_implies_then; |
| } |
| |
| } else if (group_data->colocated) { |
| rsc_colocation_new("group:internal_colocation", NULL, INFINITY, |
| child_rsc, last_rsc, NULL, NULL, data_set); |
| } |
| |
| if (top->variant == pe_master) { |
| new_rsc_order(rsc, RSC_DEMOTE, child_rsc, RSC_DEMOTE, |
| stop | pe_order_implies_first_printed, data_set); |
| |
| new_rsc_order(child_rsc, RSC_DEMOTE, rsc, RSC_DEMOTED, stopped, data_set); |
| |
| new_rsc_order(child_rsc, RSC_PROMOTE, rsc, RSC_PROMOTED, started, data_set); |
| |
| new_rsc_order(rsc, RSC_PROMOTE, child_rsc, RSC_PROMOTE, |
| pe_order_implies_first_printed, data_set); |
| |
| } |
| |
| order_start_start(rsc, child_rsc, pe_order_implies_first_printed); |
| order_stop_stop(rsc, child_rsc, stop | pe_order_implies_first_printed); |
| |
| new_rsc_order(child_rsc, RSC_STOP, rsc, RSC_STOPPED, stopped, data_set); |
| |
| new_rsc_order(child_rsc, RSC_START, rsc, RSC_STARTED, started, data_set); |
| |
| if (group_data->ordered == FALSE) { |
| order_start_start(rsc, child_rsc, start | pe_order_implies_first_printed); |
| if (top->variant == pe_master) { |
| new_rsc_order(rsc, RSC_PROMOTE, child_rsc, RSC_PROMOTE, |
| start | pe_order_implies_first_printed, data_set); |
| } |
| |
| } else if (last_rsc != NULL) { |
| child_rsc->restart_type = pe_restart_restart; |
| |
| order_start_start(last_rsc, child_rsc, start); |
| order_stop_stop(child_rsc, last_rsc, pe_order_optional | pe_order_restart); |
| |
| if (top->variant == pe_master) { |
| new_rsc_order(last_rsc, RSC_PROMOTE, child_rsc, RSC_PROMOTE, start, data_set); |
| new_rsc_order(child_rsc, RSC_DEMOTE, last_rsc, RSC_DEMOTE, pe_order_optional, |
| data_set); |
| } |
| |
| } else { |
| /* If anyone in the group is starting, then |
| * pe_order_implies_then will cause _everyone_ in the group |
| * to be sent a start action |
| * But this is safe since starting something that is already |
| * started is required to be "safe" |
| */ |
| int flags = pe_order_none; |
| |
| order_start_start(rsc, child_rsc, flags); |
| if (top->variant == pe_master) { |
| new_rsc_order(rsc, RSC_PROMOTE, child_rsc, RSC_PROMOTE, flags, data_set); |
| } |
| |
| } |
| |
| /* Look for partially active groups |
| * Make sure they still shut down in sequence |
| */ |
| if (child_rsc->running_on) { |
| if (group_data->ordered |
| && last_rsc |
| && last_rsc->running_on == NULL && last_active && last_active->running_on) { |
| order_stop_stop(child_rsc, last_active, pe_order_optional); |
| } |
| last_active = child_rsc; |
| } |
| |
| last_rsc = child_rsc; |
| } |
| |
| if (group_data->ordered && last_rsc != NULL) { |
| int stop_stop_flags = pe_order_implies_then; |
| int stop_stopped_flags = pe_order_optional; |
| |
| order_stop_stop(rsc, last_rsc, stop_stop_flags); |
| new_rsc_order(last_rsc, RSC_STOP, rsc, RSC_STOPPED, stop_stopped_flags, data_set); |
| |
| if (top->variant == pe_master) { |
| new_rsc_order(rsc, RSC_DEMOTE, last_rsc, RSC_DEMOTE, stop_stop_flags, data_set); |
| new_rsc_order(last_rsc, RSC_DEMOTE, rsc, RSC_DEMOTED, stop_stopped_flags, data_set); |
| } |
| } |
| } |
| |
| void |
| group_rsc_colocation_lh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint) |
| { |
| GListPtr gIter = NULL; |
| group_variant_data_t *group_data = NULL; |
| |
| if (rsc_lh == NULL) { |
| pe_err("rsc_lh was NULL for %s", constraint->id); |
| return; |
| |
| } else if (rsc_rh == NULL) { |
| pe_err("rsc_rh was NULL for %s", constraint->id); |
| return; |
| } |
| if (constraint->score == 0) { |
| return; |
| } |
| |
| gIter = rsc_lh->children; |
| pe_rsc_trace(rsc_lh, "Processing constraints from %s", rsc_lh->id); |
| |
| get_group_variant_data(group_data, rsc_lh); |
| |
| if (group_data->colocated) { |
| group_data->first_child->cmds->rsc_colocation_lh(group_data->first_child, rsc_rh, |
| constraint); |
| return; |
| |
| } else if (constraint->score >= INFINITY) { |
| crm_config_err("%s: Cannot perform mandatory colocation" |
| " between non-colocated group and %s", rsc_lh->id, rsc_rh->id); |
| return; |
| } |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->rsc_colocation_lh(child_rsc, rsc_rh, constraint); |
| } |
| } |
| |
| void |
| group_rsc_colocation_rh(resource_t * rsc_lh, resource_t * rsc_rh, rsc_colocation_t * constraint) |
| { |
| GListPtr gIter = rsc_rh->children; |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, rsc_rh); |
| CRM_CHECK(rsc_lh->variant == pe_native, return); |
| |
| if (constraint->score == 0) { |
| return; |
| } |
| pe_rsc_trace(rsc_rh, "Processing RH %s of constraint %s (LH is %s)", |
| rsc_rh->id, constraint->id, rsc_lh->id); |
| |
| if (is_set(rsc_rh->flags, pe_rsc_provisional)) { |
| return; |
| |
| } else if (group_data->colocated && group_data->first_child) { |
| if (constraint->score >= INFINITY) { |
| /* Ensure RHS is _fully_ up before can start LHS */ |
| group_data->last_child->cmds->rsc_colocation_rh(rsc_lh, group_data->last_child, |
| constraint); |
| } else { |
| /* A partially active RHS is fine */ |
| group_data->first_child->cmds->rsc_colocation_rh(rsc_lh, group_data->first_child, |
| constraint); |
| } |
| |
| return; |
| |
| } else if (constraint->score >= INFINITY) { |
| crm_config_err("%s: Cannot perform mandatory colocation with" |
| " non-colocated group: %s", rsc_lh->id, rsc_rh->id); |
| return; |
| } |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->rsc_colocation_rh(rsc_lh, child_rsc, constraint); |
| } |
| } |
| |
| enum pe_action_flags |
| group_action_flags(action_t * action, node_t * node) |
| { |
| GListPtr gIter = NULL; |
| enum pe_action_flags flags = (pe_action_optional | pe_action_runnable | pe_action_pseudo); |
| |
| for (gIter = action->rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| enum action_tasks task = get_complex_task(child, action->task, TRUE); |
| const char *task_s = task2text(task); |
| action_t *child_action = find_first_action(child->actions, NULL, task_s, node); |
| |
| if (child_action) { |
| enum pe_action_flags child_flags = child->cmds->action_flags(child_action, node); |
| |
| if (is_set(flags, pe_action_optional) |
| && is_set(child_flags, pe_action_optional) == FALSE) { |
| pe_rsc_trace(action->rsc, "%s is mandatory because of %s", action->uuid, |
| child_action->uuid); |
| clear_bit(flags, pe_action_optional); |
| pe_clear_action_bit(action, pe_action_optional); |
| } |
| if (safe_str_neq(task_s, action->task) |
| && is_set(flags, pe_action_runnable) |
| && is_set(child_flags, pe_action_runnable) == FALSE) { |
| pe_rsc_trace(action->rsc, "%s is not runnable because of %s", action->uuid, |
| child_action->uuid); |
| clear_bit(flags, pe_action_runnable); |
| pe_clear_action_bit(action, pe_action_runnable); |
| } |
| |
| } else if (task != stop_rsc && task != action_demote) { |
| pe_rsc_trace(action->rsc, "%s is not runnable because of %s (not found in %s)", |
| action->uuid, task_s, child->id); |
| clear_bit(flags, pe_action_runnable); |
| } |
| } |
| |
| return flags; |
| } |
| |
| enum pe_graph_flags |
| group_update_actions(action_t * first, action_t * then, node_t * node, enum pe_action_flags flags, |
| enum pe_action_flags filter, enum pe_ordering type) |
| { |
| GListPtr gIter = then->rsc->children; |
| enum pe_graph_flags changed = pe_graph_none; |
| |
| CRM_ASSERT(then->rsc != NULL); |
| changed |= native_update_actions(first, then, node, flags, filter, type); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *) gIter->data; |
| action_t *child_action = find_first_action(child->actions, NULL, then->task, node); |
| |
| if (child_action) { |
| changed |= child->cmds->update_actions(first, child_action, node, flags, filter, type); |
| } |
| } |
| |
| return changed; |
| } |
| |
| void |
| group_rsc_location(pe_resource_t *rsc, pe__location_t *constraint) |
| { |
| GListPtr gIter = rsc->children; |
| GListPtr saved = constraint->node_list_rh; |
| GListPtr zero = node_list_dup(constraint->node_list_rh, TRUE, FALSE); |
| gboolean reset_scores = TRUE; |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, rsc); |
| |
| pe_rsc_debug(rsc, "Processing rsc_location %s for %s", constraint->id, rsc->id); |
| |
| native_rsc_location(rsc, constraint); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->rsc_location(child_rsc, constraint); |
| if (group_data->colocated && reset_scores) { |
| reset_scores = FALSE; |
| constraint->node_list_rh = zero; |
| } |
| } |
| |
| constraint->node_list_rh = saved; |
| g_list_free_full(zero, free); |
| } |
| |
| void |
| group_expand(resource_t * rsc, pe_working_set_t * data_set) |
| { |
| CRM_CHECK(rsc != NULL, return); |
| |
| pe_rsc_trace(rsc, "Processing actions from %s", rsc->id); |
| native_expand(rsc, data_set); |
| |
| for (GListPtr gIter = rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child_rsc = (resource_t *) gIter->data; |
| |
| child_rsc->cmds->expand(child_rsc, data_set); |
| } |
| } |
| |
| GHashTable * |
| pcmk__group_merge_weights(pe_resource_t *rsc, const char *rhs, |
| GHashTable *nodes, const char *attr, float factor, |
| uint32_t flags) |
| { |
| GListPtr gIter = rsc->rsc_cons_lhs; |
| group_variant_data_t *group_data = NULL; |
| |
| get_group_variant_data(group_data, rsc); |
| |
| if (is_set(rsc->flags, pe_rsc_merging)) { |
| pe_rsc_info(rsc, "Breaking dependency loop with %s at %s", rsc->id, rhs); |
| return nodes; |
| } |
| |
| set_bit(rsc->flags, pe_rsc_merging); |
| |
| nodes = |
| group_data->first_child->cmds->merge_weights(group_data->first_child, rhs, nodes, attr, |
| factor, flags); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *constraint = (rsc_colocation_t *) gIter->data; |
| |
| if (constraint->score == 0) { |
| continue; |
| } |
| nodes = pcmk__native_merge_weights(constraint->rsc_lh, rsc->id, nodes, |
| constraint->node_attribute, |
| constraint->score / (float) INFINITY, |
| flags); |
| } |
| |
| clear_bit(rsc->flags, pe_rsc_merging); |
| return nodes; |
| } |
| |
| void |
| group_append_meta(resource_t * rsc, xmlNode * xml) |
| { |
| } |