| /* |
| * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <crm_internal.h> |
| |
| #include <sys/param.h> |
| #include <crm/crm.h> |
| #include <crm/cib.h> |
| #include <crm/msg_xml.h> |
| #include <crm/common/xml.h> |
| |
| #include <glib.h> |
| |
| #include <allocate.h> |
| #include <utils.h> |
| |
| void update_colo_start_chain(action_t * action); |
| gboolean rsc_update_action(action_t * first, action_t * then, enum pe_ordering type); |
| |
| static enum pe_action_flags |
| get_action_flags(action_t * action, node_t * node) |
| { |
| enum pe_action_flags flags = action->flags; |
| |
| if (action->rsc) { |
| flags = action->rsc->cmds->action_flags(action, NULL); |
| |
| if (pe_rsc_is_clone(action->rsc) && node) { |
| |
| /* We only care about activity on $node */ |
| enum pe_action_flags clone_flags = action->rsc->cmds->action_flags(action, node); |
| |
| /* Go to great lengths to ensure the correct value for pe_action_runnable... |
| * |
| * If we are a clone, then for _ordering_ constraints, it's only relevant |
| * if we are runnable _anywhere_. |
| * |
| * This only applies to _runnable_ though, and only for ordering constraints. |
| * If this function is ever used during colocation, then we'll need additional logic |
| * |
| * Not very satisfying, but it's logical and appears to work well. |
| */ |
| if (is_not_set(clone_flags, pe_action_runnable) |
| && is_set(flags, pe_action_runnable)) { |
| pe_rsc_trace(action->rsc, "Fixing up runnable flag for %s", action->uuid); |
| set_bit(clone_flags, pe_action_runnable); |
| } |
| flags = clone_flags; |
| } |
| } |
| return flags; |
| } |
| |
| static char * |
| convert_non_atomic_uuid(char *old_uuid, resource_t * rsc, gboolean allow_notify, |
| gboolean free_original) |
| { |
| int interval = 0; |
| char *uuid = NULL; |
| char *rid = NULL; |
| char *raw_task = NULL; |
| int task = no_action; |
| |
| CRM_ASSERT(rsc); |
| pe_rsc_trace(rsc, "Processing %s", old_uuid); |
| if (old_uuid == NULL) { |
| return NULL; |
| |
| } else if (strstr(old_uuid, "notify") != NULL) { |
| goto done; /* no conversion */ |
| |
| } else if (rsc->variant < pe_group) { |
| goto done; /* no conversion */ |
| } |
| |
| CRM_ASSERT(parse_op_key(old_uuid, &rid, &raw_task, &interval)); |
| if (interval > 0) { |
| goto done; /* no conversion */ |
| } |
| |
| task = text2task(raw_task); |
| switch (task) { |
| case stop_rsc: |
| case start_rsc: |
| case action_notify: |
| case action_promote: |
| case action_demote: |
| break; |
| case stopped_rsc: |
| case started_rsc: |
| case action_notified: |
| case action_promoted: |
| case action_demoted: |
| task--; |
| break; |
| case monitor_rsc: |
| case shutdown_crm: |
| case stonith_node: |
| task = no_action; |
| break; |
| default: |
| crm_err("Unknown action: %s", raw_task); |
| task = no_action; |
| break; |
| } |
| |
| if (task != no_action) { |
| if (is_set(rsc->flags, pe_rsc_notify) && allow_notify) { |
| uuid = generate_notify_key(rid, "confirmed-post", task2text(task + 1)); |
| |
| } else { |
| uuid = generate_op_key(rid, task2text(task + 1), 0); |
| } |
| pe_rsc_trace(rsc, "Converted %s -> %s", old_uuid, uuid); |
| } |
| |
| done: |
| if (uuid == NULL) { |
| uuid = strdup(old_uuid); |
| } |
| |
| if (free_original) { |
| free(old_uuid); |
| } |
| |
| free(raw_task); |
| free(rid); |
| return uuid; |
| } |
| |
| static action_t * |
| rsc_expand_action(action_t * action) |
| { |
| gboolean notify = FALSE; |
| action_t *result = action; |
| resource_t *rsc = action->rsc; |
| |
| if (rsc == NULL) { |
| return action; |
| } |
| |
| if ((rsc->parent == NULL) |
| || (pe_rsc_is_clone(rsc) && (rsc->parent->variant == pe_container))) { |
| /* Only outermost resources have notification actions. |
| * The exception is those in bundles. |
| */ |
| notify = is_set(rsc->flags, pe_rsc_notify); |
| } |
| |
| if (rsc->variant >= pe_group) { |
| /* Expand 'start' -> 'started' */ |
| char *uuid = NULL; |
| |
| uuid = convert_non_atomic_uuid(action->uuid, rsc, notify, FALSE); |
| if (uuid) { |
| pe_rsc_trace(rsc, "Converting %s to %s %d", action->uuid, uuid, |
| is_set(rsc->flags, pe_rsc_notify)); |
| result = find_first_action(rsc->actions, uuid, NULL, NULL); |
| if (result == NULL) { |
| crm_err("Couldn't expand %s to %s in %s", action->uuid, uuid, rsc->id); |
| result = action; |
| } |
| free(uuid); |
| } |
| } |
| return result; |
| } |
| |
| static enum pe_graph_flags |
| graph_update_action(action_t * first, action_t * then, node_t * node, |
| enum pe_action_flags first_flags, enum pe_action_flags then_flags, |
| action_wrapper_t *order) |
| { |
| enum pe_graph_flags changed = pe_graph_none; |
| enum pe_ordering type = order->type; |
| gboolean processed = FALSE; |
| |
| /* TODO: Do as many of these in parallel as possible */ |
| |
| if(is_set(type, pe_order_implies_then_on_node)) { |
| /* Normally we want the _whole_ 'then' clone to |
| * restart if 'first' is restarted, so then->node is |
| * needed. |
| * |
| * However for unfencing, we want to limit this to |
| * instances on the same node as 'first' (the |
| * unfencing operation), so first->node is supplied. |
| * |
| * Swap the node, from then on we can can treat it |
| * like any other 'pe_order_implies_then' |
| */ |
| |
| clear_bit(type, pe_order_implies_then_on_node); |
| set_bit(type, pe_order_implies_then); |
| node = first->node; |
| } |
| |
| clear_bit(first_flags, pe_action_pseudo); |
| |
| if (type & pe_order_implies_then) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags & pe_action_optional, |
| pe_action_optional, pe_order_implies_then); |
| |
| } else if (is_set(first_flags, pe_action_optional) == FALSE) { |
| if (update_action_flags(then, pe_action_optional | pe_action_clear, __FUNCTION__, __LINE__)) { |
| changed |= pe_graph_updated_then; |
| } |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "implies right: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("implies right: %s then %s %p", first->uuid, then->uuid, then->rsc); |
| } |
| } |
| |
| if ((type & pe_order_restart) && then->rsc) { |
| enum pe_action_flags restart = (pe_action_optional | pe_action_runnable); |
| |
| processed = TRUE; |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, restart, pe_order_restart); |
| if (changed) { |
| pe_rsc_trace(then->rsc, "restart: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("restart: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_implies_first) { |
| processed = TRUE; |
| if (first->rsc) { |
| changed |= |
| first->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_optional, pe_order_implies_first); |
| |
| } else if (is_set(first_flags, pe_action_optional) == FALSE) { |
| pe_rsc_trace(first->rsc, "first unrunnable: %s (%d) then %s (%d)", |
| first->uuid, is_set(first_flags, pe_action_optional), |
| then->uuid, is_set(then_flags, pe_action_optional)); |
| if (update_action_flags(first, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) { |
| changed |= pe_graph_updated_first; |
| } |
| } |
| |
| if (changed) { |
| pe_rsc_trace(then->rsc, "implies left: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("implies left: %s (%d) then %s (%d)", |
| first->uuid, is_set(first_flags, pe_action_optional), |
| then->uuid, is_set(then_flags, pe_action_optional)); |
| } |
| } |
| |
| if (type & pe_order_implies_first_master) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags & pe_action_optional, |
| pe_action_optional, pe_order_implies_first_master); |
| } |
| |
| if (changed) { |
| pe_rsc_trace(then->rsc, |
| "implies left when right rsc is Master role: %s then %s: changed", |
| first->uuid, then->uuid); |
| } else { |
| crm_trace("implies left when right rsc is Master role: %s then %s", first->uuid, |
| then->uuid); |
| } |
| } |
| |
| if (type & pe_order_one_or_more) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_runnable, pe_order_one_or_more); |
| |
| } else if (is_set(first_flags, pe_action_runnable)) { |
| /* alright. a "first" action is considered runnable, incremente |
| * the 'runnable_before' counter */ |
| then->runnable_before++; |
| |
| /* if the runnable before count for then exceeds the required number |
| * of "before" runnable actions... mark then as runnable */ |
| if (then->runnable_before >= then->required_runnable_before) { |
| if (update_action_flags(then, pe_action_runnable, __FUNCTION__, __LINE__)) { |
| changed |= pe_graph_updated_then; |
| } |
| } |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "runnable_one_or_more: %s then %s: changed", first->uuid, |
| then->uuid); |
| } else { |
| crm_trace("runnable_one_or_more: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (then->rsc && is_set(type, pe_order_probe)) { |
| processed = TRUE; |
| |
| if (is_not_set(first_flags, pe_action_runnable) && first->rsc->running_on != NULL) { |
| pe_rsc_trace(then->rsc, "Ignoring %s then %s - %s is about to be stopped", |
| first->uuid, then->uuid, first->rsc->id); |
| type = pe_order_none; |
| order->type = pe_order_none; |
| |
| } else { |
| pe_rsc_trace(then->rsc, "Enforcing %s then %s", first->uuid, then->uuid); |
| changed |= then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_runnable, pe_order_runnable_left); |
| } |
| |
| if (changed) { |
| pe_rsc_trace(then->rsc, "runnable: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("runnable: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_runnable_left) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_runnable, pe_order_runnable_left); |
| |
| } else if (is_set(first_flags, pe_action_runnable) == FALSE) { |
| pe_rsc_trace(then->rsc, "then unrunnable: %s then %s", first->uuid, then->uuid); |
| if (update_action_flags(then, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) { |
| changed |= pe_graph_updated_then; |
| } |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "runnable: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("runnable: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_implies_first_migratable) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_optional, pe_order_implies_first_migratable); |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("optional: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_pseudo_left) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_optional, pe_order_pseudo_left); |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("optional: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_optional) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_runnable, pe_order_optional); |
| } |
| if (changed) { |
| pe_rsc_trace(then->rsc, "optional: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("optional: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (type & pe_order_asymmetrical) { |
| processed = TRUE; |
| if (then->rsc) { |
| changed |= |
| then->rsc->cmds->update_actions(first, then, node, first_flags, |
| pe_action_runnable, pe_order_asymmetrical); |
| } |
| |
| if (changed) { |
| pe_rsc_trace(then->rsc, "asymmetrical: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("asymmetrical: %s then %s", first->uuid, then->uuid); |
| } |
| |
| } |
| |
| if ((first->flags & pe_action_runnable) && (type & pe_order_implies_then_printed) |
| && (first_flags & pe_action_optional) == 0) { |
| processed = TRUE; |
| crm_trace("%s implies %s printed", first->uuid, then->uuid); |
| update_action_flags(then, pe_action_print_always, __FUNCTION__, __LINE__); /* don't care about changed */ |
| } |
| |
| if (is_set(type, pe_order_implies_first_printed) && is_set(then_flags, pe_action_optional) == FALSE) { |
| processed = TRUE; |
| crm_trace("%s implies %s printed", then->uuid, first->uuid); |
| update_action_flags(first, pe_action_print_always, __FUNCTION__, __LINE__); /* don't care about changed */ |
| } |
| |
| if ((type & pe_order_implies_then |
| || type & pe_order_implies_first |
| || type & pe_order_restart) |
| && first->rsc |
| && safe_str_eq(first->task, RSC_STOP) |
| && is_not_set(first->rsc->flags, pe_rsc_managed) |
| && is_set(first->rsc->flags, pe_rsc_block) |
| && is_not_set(first->flags, pe_action_runnable)) { |
| |
| if (update_action_flags(then, pe_action_runnable | pe_action_clear, __FUNCTION__, __LINE__)) { |
| changed |= pe_graph_updated_then; |
| } |
| |
| if (changed) { |
| pe_rsc_trace(then->rsc, "unmanaged left: %s then %s: changed", first->uuid, then->uuid); |
| } else { |
| crm_trace("unmanaged left: %s then %s", first->uuid, then->uuid); |
| } |
| } |
| |
| if (processed == FALSE) { |
| crm_trace("Constraint 0x%.6x not applicable", type); |
| } |
| |
| return changed; |
| } |
| |
| static void |
| mark_start_blocked(resource_t *rsc, resource_t *reason) |
| { |
| GListPtr gIter = rsc->actions; |
| char *reason_text = crm_strdup_printf("colocation with %s", reason->id); |
| |
| for (; gIter != NULL; gIter = gIter->next) { |
| action_t *action = (action_t *) gIter->data; |
| |
| if (safe_str_neq(action->task, RSC_START)) { |
| continue; |
| } |
| if (is_set(action->flags, pe_action_runnable)) { |
| pe_action_set_flag_reason(__FUNCTION__, __LINE__, action, NULL, reason_text, pe_action_runnable, FALSE); |
| update_colo_start_chain(action); |
| update_action(action); |
| } |
| } |
| free(reason_text); |
| } |
| |
| void |
| update_colo_start_chain(action_t *action) |
| { |
| GListPtr gIter = NULL; |
| resource_t *rsc = NULL; |
| |
| if (is_not_set(action->flags, pe_action_runnable) && safe_str_eq(action->task, RSC_START)) { |
| rsc = uber_parent(action->rsc); |
| if (rsc->parent) { |
| /* For bundles, uber_parent() returns the clone/master, not the |
| * bundle, so the existence of rsc->parent implies this is a bundle. |
| * In this case, we need the bundle resource, so that we can check |
| * if all containers are stopped/stopping. |
| */ |
| rsc = rsc->parent; |
| } |
| } |
| |
| if (rsc == NULL || rsc->rsc_cons_lhs == NULL) { |
| return; |
| } |
| |
| /* if rsc has children, all the children need to have start set to |
| * unrunnable before we follow the colo chain for the parent. */ |
| for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { |
| resource_t *child = (resource_t *)gIter->data; |
| action_t *start = find_first_action(child->actions, NULL, RSC_START, NULL); |
| if (start == NULL || is_set(start->flags, pe_action_runnable)) { |
| return; |
| } |
| } |
| |
| for (gIter = rsc->rsc_cons_lhs; gIter != NULL; gIter = gIter->next) { |
| rsc_colocation_t *colocate_with = (rsc_colocation_t *)gIter->data; |
| if (colocate_with->score == INFINITY) { |
| mark_start_blocked(colocate_with->rsc_lh, action->rsc); |
| } |
| } |
| } |
| |
| gboolean |
| update_action(action_t * then) |
| { |
| GListPtr lpc = NULL; |
| enum pe_graph_flags changed = pe_graph_none; |
| int last_flags = then->flags; |
| |
| crm_trace("Processing %s (%s %s %s)", |
| then->uuid, |
| is_set(then->flags, pe_action_optional) ? "optional" : "required", |
| is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable", |
| is_set(then->flags, |
| pe_action_pseudo) ? "pseudo" : then->node ? then->node->details->uname : ""); |
| |
| if (is_set(then->flags, pe_action_requires_any)) { |
| /* initialize current known runnable before actions to 0 |
| * from here as graph_update_action is called for each of |
| * then's before actions, this number will increment as |
| * runnable 'first' actions are encountered */ |
| then->runnable_before = 0; |
| |
| /* for backwards compatibility with previous options that use |
| * the 'requires_any' flag, initialize required to 1 if it is |
| * not set. */ |
| if (then->required_runnable_before == 0) { |
| then->required_runnable_before = 1; |
| } |
| pe_clear_action_bit(then, pe_action_runnable); |
| /* We are relying on the pe_order_one_or_more clause of |
| * graph_update_action(), called as part of the: |
| * |
| * 'if (first == other->action)' |
| * |
| * block below, to set this back if appropriate |
| */ |
| } |
| |
| for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) { |
| action_wrapper_t *other = (action_wrapper_t *) lpc->data; |
| action_t *first = other->action; |
| |
| node_t *then_node = then->node; |
| node_t *first_node = first->node; |
| |
| enum pe_action_flags then_flags = 0; |
| enum pe_action_flags first_flags = 0; |
| |
| if (first->rsc && first->rsc->variant == pe_group && safe_str_eq(first->task, RSC_START)) { |
| first_node = first->rsc->fns->location(first->rsc, NULL, FALSE); |
| if (first_node) { |
| crm_trace("First: Found node %s for %s", first_node->details->uname, first->uuid); |
| } |
| } |
| |
| if (then->rsc && then->rsc->variant == pe_group && safe_str_eq(then->task, RSC_START)) { |
| then_node = then->rsc->fns->location(then->rsc, NULL, FALSE); |
| if (then_node) { |
| crm_trace("Then: Found node %s for %s", then_node->details->uname, then->uuid); |
| } |
| } |
| /* Disable constraint if it only applies when on same node, but isn't */ |
| if (is_set(other->type, pe_order_same_node) && first_node && then_node |
| && (first_node->details != then_node->details)) { |
| |
| crm_trace("Disabled constraint %s on %s -> %s on %s", |
| other->action->uuid, first_node->details->uname, |
| then->uuid, then_node->details->uname); |
| other->type = pe_order_none; |
| continue; |
| } |
| |
| clear_bit(changed, pe_graph_updated_first); |
| |
| if (first->rsc && is_set(other->type, pe_order_then_cancels_first) |
| && is_not_set(then->flags, pe_action_optional)) { |
| |
| /* 'then' is required, so we must abandon 'first' |
| * (e.g. a required stop cancels any reload). |
| * Only used with reload actions as 'first'. |
| */ |
| set_bit(other->action->flags, pe_action_optional); |
| clear_bit(first->rsc->flags, pe_rsc_reload); |
| } |
| |
| if (first->rsc && then->rsc && (first->rsc != then->rsc) |
| && (is_parent(then->rsc, first->rsc) == FALSE)) { |
| first = rsc_expand_action(first); |
| } |
| if (first != other->action) { |
| crm_trace("Ordering %s after %s instead of %s", then->uuid, first->uuid, |
| other->action->uuid); |
| } |
| |
| first_flags = get_action_flags(first, then_node); |
| then_flags = get_action_flags(then, first_node); |
| |
| crm_trace("Checking %s (%s %s %s) against %s (%s %s %s) filter=0x%.6x type=0x%.6x", |
| then->uuid, |
| is_set(then_flags, pe_action_optional) ? "optional" : "required", |
| is_set(then_flags, pe_action_runnable) ? "runnable" : "unrunnable", |
| is_set(then_flags, |
| pe_action_pseudo) ? "pseudo" : then->node ? then->node->details-> |
| uname : "", first->uuid, is_set(first_flags, |
| pe_action_optional) ? "optional" : "required", |
| is_set(first_flags, pe_action_runnable) ? "runnable" : "unrunnable", |
| is_set(first_flags, |
| pe_action_pseudo) ? "pseudo" : first->node ? first->node->details-> |
| uname : "", first_flags, other->type); |
| |
| if (first == other->action) { |
| /* |
| * 'first' was not expanded (e.g. from 'start' to 'running'), which could mean it: |
| * - has no associated resource, |
| * - was a primitive, |
| * - was pre-expanded (e.g. 'running' instead of 'start') |
| * |
| * The third argument here to graph_update_action() is a node which is used under two conditions: |
| * - Interleaving, in which case first->node and |
| * then->node are equal (and NULL) |
| * - If 'then' is a clone, to limit the scope of the |
| * constraint to instances on the supplied node |
| * |
| */ |
| node_t *node = then->node; |
| changed |= graph_update_action(first, then, node, first_flags, then_flags, other); |
| |
| /* 'first' was for a complex resource (clone, group, etc), |
| * create a new dependency if necessary |
| */ |
| } else if (order_actions(first, then, other->type)) { |
| /* This was the first time 'first' and 'then' were associated, |
| * start again to get the new actions_before list |
| */ |
| changed |= (pe_graph_updated_then | pe_graph_disable); |
| } |
| |
| if (changed & pe_graph_disable) { |
| crm_trace("Disabled constraint %s -> %s in favor of %s -> %s", |
| other->action->uuid, then->uuid, first->uuid, then->uuid); |
| clear_bit(changed, pe_graph_disable); |
| other->type = pe_order_none; |
| } |
| |
| if (changed & pe_graph_updated_first) { |
| GListPtr lpc2 = NULL; |
| |
| crm_trace("Updated %s (first %s %s %s), processing dependents ", |
| first->uuid, |
| is_set(first->flags, pe_action_optional) ? "optional" : "required", |
| is_set(first->flags, pe_action_runnable) ? "runnable" : "unrunnable", |
| is_set(first->flags, |
| pe_action_pseudo) ? "pseudo" : first->node ? first->node->details-> |
| uname : ""); |
| for (lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) { |
| action_wrapper_t *other = (action_wrapper_t *) lpc2->data; |
| |
| update_action(other->action); |
| } |
| update_action(first); |
| } |
| } |
| |
| if (is_set(then->flags, pe_action_requires_any)) { |
| if (last_flags != then->flags) { |
| changed |= pe_graph_updated_then; |
| } else { |
| clear_bit(changed, pe_graph_updated_then); |
| } |
| } |
| |
| if (changed & pe_graph_updated_then) { |
| crm_trace("Updated %s (then %s %s %s), processing dependents ", |
| then->uuid, |
| is_set(then->flags, pe_action_optional) ? "optional" : "required", |
| is_set(then->flags, pe_action_runnable) ? "runnable" : "unrunnable", |
| is_set(then->flags, |
| pe_action_pseudo) ? "pseudo" : then->node ? then->node->details-> |
| uname : ""); |
| |
| if (is_set(last_flags, pe_action_runnable) && is_not_set(then->flags, pe_action_runnable)) { |
| update_colo_start_chain(then); |
| } |
| update_action(then); |
| for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) { |
| action_wrapper_t *other = (action_wrapper_t *) lpc->data; |
| |
| update_action(other->action); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| gboolean |
| shutdown_constraints(node_t * node, action_t * shutdown_op, pe_working_set_t * data_set) |
| { |
| /* add the stop to the before lists so it counts as a pre-req |
| * for the shutdown |
| */ |
| GListPtr lpc = NULL; |
| |
| for (lpc = data_set->actions; lpc != NULL; lpc = lpc->next) { |
| action_t *action = (action_t *) lpc->data; |
| |
| if (action->rsc == NULL || action->node == NULL) { |
| continue; |
| } else if (action->node->details != node->details) { |
| continue; |
| } else if (is_set(action->rsc->flags, pe_rsc_maintenance)) { |
| pe_rsc_trace(action->rsc, "Skipping %s: maintenance mode", action->uuid); |
| continue; |
| } else if (node->details->maintenance) { |
| pe_rsc_trace(action->rsc, "Skipping %s: node %s is in maintenance mode", |
| action->uuid, node->details->uname); |
| continue; |
| } else if (safe_str_neq(action->task, RSC_STOP)) { |
| continue; |
| } else if (is_not_set(action->rsc->flags, pe_rsc_managed) |
| && is_not_set(action->rsc->flags, pe_rsc_block)) { |
| /* |
| * If another action depends on this one, we may still end up blocking |
| */ |
| pe_rsc_trace(action->rsc, "Skipping %s: unmanaged", action->uuid); |
| continue; |
| } |
| |
| pe_rsc_trace(action->rsc, "Ordering %s before shutdown on %s", action->uuid, |
| node->details->uname); |
| pe_clear_action_bit(action, pe_action_optional); |
| custom_action_order(action->rsc, NULL, action, |
| NULL, strdup(CRM_OP_SHUTDOWN), shutdown_op, |
| pe_order_optional | pe_order_runnable_left, data_set); |
| } |
| |
| return TRUE; |
| } |
| |
| /*! |
| * \internal |
| * \brief Order all actions appropriately relative to a fencing operation |
| * |
| * Ensure start operations of affected resources are ordered after fencing, |
| * imply stop and demote operations of affected resources by marking them as |
| * pseudo-actions, etc. |
| * |
| * \param[in] node Node to be fenced |
| * \param[in] stonith_op Fencing operation |
| * \param[in,out] data_set Working set of cluster |
| */ |
| gboolean |
| stonith_constraints(node_t * node, action_t * stonith_op, pe_working_set_t * data_set) |
| { |
| GListPtr r = NULL; |
| |
| CRM_CHECK(stonith_op != NULL, return FALSE); |
| for (r = data_set->resources; r != NULL; r = r->next) { |
| rsc_stonith_ordering((resource_t *) r->data, stonith_op, data_set); |
| } |
| return TRUE; |
| } |
| |
| static node_t * |
| get_router_node(action_t *action) |
| { |
| node_t *began_on = NULL; |
| node_t *ended_on = NULL; |
| node_t *router_node = NULL; |
| bool partial_migration = FALSE; |
| const char *task = action->task; |
| |
| if (safe_str_eq(task, CRM_OP_FENCE) || is_remote_node(action->node) == FALSE) { |
| return NULL; |
| } |
| |
| CRM_ASSERT(action->node->details->remote_rsc != NULL); |
| |
| began_on = pe__current_node(action->node->details->remote_rsc); |
| ended_on = action->node->details->remote_rsc->allocated_to; |
| if (action->node->details->remote_rsc |
| && (action->node->details->remote_rsc->container == NULL) |
| && action->node->details->remote_rsc->partial_migration_target) { |
| partial_migration = TRUE; |
| } |
| |
| /* if there is only one location to choose from, |
| * this is easy. Check for those conditions first */ |
| if (!began_on || !ended_on) { |
| /* remote rsc is either shutting down or starting up */ |
| return began_on ? began_on : ended_on; |
| } else if (began_on->details == ended_on->details) { |
| /* remote rsc didn't move nodes. */ |
| return began_on; |
| } |
| |
| /* If we have get here, we know the remote resource |
| * began on one node and is moving to another node. |
| * |
| * This means some actions will get routed through the cluster |
| * node the connection rsc began on, and others are routed through |
| * the cluster node the connection rsc ends up on. |
| * |
| * 1. stop, demote, migrate actions of resources living in the remote |
| * node _MUST_ occur _BEFORE_ the connection can move (these actions |
| * are all required before the remote rsc stop action can occur.) In |
| * this case, we know these actions have to be routed through the initial |
| * cluster node the connection resource lived on before the move takes place. |
| * The exception is a partial migration of a (non-guest) remote |
| * connection resource; in that case, all actions (even these) will be |
| * ordered after the connection's pseudo-start on the migration target, |
| * so the target is the router node. |
| * |
| * 2. Everything else (start, promote, monitor, probe, refresh, clear failcount |
| * delete ....) must occur after the resource starts on the node it is |
| * moving to. |
| */ |
| |
| if (safe_str_eq(task, "notify")) { |
| task = g_hash_table_lookup(action->meta, "notify_operation"); |
| } |
| |
| /* 1. before connection rsc moves. */ |
| if ((safe_str_eq(task, "stop") || |
| safe_str_eq(task, "demote") || |
| safe_str_eq(task, "migrate_from") || |
| safe_str_eq(task, "migrate_to")) && !partial_migration) { |
| |
| router_node = began_on; |
| |
| /* 2. after connection rsc moves. */ |
| } else { |
| router_node = ended_on; |
| } |
| return router_node; |
| } |
| |
| /*! |
| * \internal |
| * \brief Add an XML node tag for a specified ID |
| * |
| * \param[in] id Node UUID to add |
| * \param[in,out] xml Parent XML tag to add to |
| */ |
| static xmlNode* |
| add_node_to_xml_by_id(const char *id, xmlNode *xml) |
| { |
| xmlNode *node_xml; |
| |
| node_xml = create_xml_node(xml, XML_CIB_TAG_NODE); |
| crm_xml_add(node_xml, XML_ATTR_UUID, id); |
| |
| return node_xml; |
| } |
| |
| /*! |
| * \internal |
| * \brief Add an XML node tag for a specified node |
| * |
| * \param[in] node Node to add |
| * \param[in,out] xml XML to add node to |
| */ |
| static void |
| add_node_to_xml(const node_t *node, void *xml) |
| { |
| add_node_to_xml_by_id(node->details->id, (xmlNode *) xml); |
| } |
| |
| /*! |
| * \internal |
| * \brief Add XML with nodes that need an update of their maintenance state |
| * |
| * \param[in,out] xml Parent XML tag to add to |
| * \param[in] data_set Working set for cluster |
| */ |
| static int |
| add_maintenance_nodes(xmlNode *xml, const pe_working_set_t *data_set) |
| { |
| GListPtr gIter = NULL; |
| xmlNode *maintenance = |
| xml?create_xml_node(xml, XML_GRAPH_TAG_MAINTENANCE):NULL; |
| int count = 0; |
| |
| for (gIter = data_set->nodes; gIter != NULL; |
| gIter = gIter->next) { |
| node_t *node = (node_t *) gIter->data; |
| struct node_shared_s *details = node->details; |
| |
| if (!(is_remote_node(node))) { |
| continue; /* just remote nodes need to know atm */ |
| } |
| |
| if (details->maintenance != details->remote_maintenance) { |
| if (maintenance) { |
| crm_xml_add( |
| add_node_to_xml_by_id(node->details->id, maintenance), |
| XML_NODE_IS_MAINTENANCE, details->maintenance?"1":"0"); |
| } |
| count++; |
| } |
| } |
| crm_trace("%s %d nodes to adjust maintenance-mode " |
| "to transition", maintenance?"Added":"Counted", count); |
| return count; |
| } |
| |
| /*! |
| * \internal |
| * \brief Add pseudo action with nodes needing maintenance state update |
| * |
| * \param[in,out] data_set Working set for cluster |
| */ |
| void |
| add_maintenance_update(pe_working_set_t *data_set) |
| { |
| action_t *action = NULL; |
| |
| if (add_maintenance_nodes(NULL, data_set)) { |
| crm_trace("adding maintenance state update pseudo action"); |
| action = get_pseudo_op(CRM_OP_MAINTENANCE_NODES, data_set); |
| set_bit(action->flags, pe_action_print_always); |
| } |
| } |
| |
| /*! |
| * \internal |
| * \brief Add XML with nodes that an action is expected to bring down |
| * |
| * If a specified action is expected to bring any nodes down, add an XML block |
| * with their UUIDs. When a node is lost, this allows the crmd to determine |
| * whether it was expected. |
| * |
| * \param[in,out] xml Parent XML tag to add to |
| * \param[in] action Action to check for downed nodes |
| * \param[in] data_set Working set for cluster |
| */ |
| static void |
| add_downed_nodes(xmlNode *xml, const action_t *action, |
| const pe_working_set_t *data_set) |
| { |
| CRM_CHECK(xml && action && action->node && data_set, return); |
| |
| if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) { |
| |
| /* Shutdown makes the action's node down */ |
| xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED); |
| add_node_to_xml_by_id(action->node->details->id, downed); |
| |
| } else if (safe_str_eq(action->task, CRM_OP_FENCE)) { |
| |
| /* Fencing makes the action's node and any hosted guest nodes down */ |
| const char *fence = g_hash_table_lookup(action->meta, "stonith_action"); |
| |
| if (safe_str_eq(fence, "off") || safe_str_eq(fence, "reboot")) { |
| xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED); |
| add_node_to_xml_by_id(action->node->details->id, downed); |
| pe_foreach_guest_node(data_set, action->node, add_node_to_xml, downed); |
| } |
| |
| } else if (action->rsc && action->rsc->is_remote_node |
| && safe_str_eq(action->task, CRMD_ACTION_STOP)) { |
| |
| /* Stopping a remote connection resource makes connected node down, |
| * unless it's part of a migration |
| */ |
| GListPtr iter; |
| action_t *input; |
| gboolean migrating = FALSE; |
| |
| for (iter = action->actions_before; iter != NULL; iter = iter->next) { |
| input = ((action_wrapper_t *) iter->data)->action; |
| if (input->rsc && safe_str_eq(action->rsc->id, input->rsc->id) |
| && safe_str_eq(input->task, CRMD_ACTION_MIGRATED)) { |
| migrating = TRUE; |
| break; |
| } |
| } |
| if (!migrating) { |
| xmlNode *downed = create_xml_node(xml, XML_GRAPH_TAG_DOWNED); |
| add_node_to_xml_by_id(action->rsc->id, downed); |
| } |
| } |
| } |
| |
| static xmlNode * |
| action2xml(action_t * action, gboolean as_input, pe_working_set_t *data_set) |
| { |
| gboolean needs_node_info = TRUE; |
| gboolean needs_maintenance_info = FALSE; |
| xmlNode *action_xml = NULL; |
| xmlNode *args_xml = NULL; |
| #if ENABLE_VERSIONED_ATTRS |
| pe_rsc_action_details_t *rsc_details = NULL; |
| #endif |
| |
| if (action == NULL) { |
| return NULL; |
| } |
| |
| if (safe_str_eq(action->task, CRM_OP_FENCE)) { |
| /* All fences need node info; guest node fences are pseudo-events */ |
| action_xml = create_xml_node(NULL, |
| is_set(action->flags, pe_action_pseudo)? |
| XML_GRAPH_TAG_PSEUDO_EVENT : |
| XML_GRAPH_TAG_CRM_EVENT); |
| |
| } else if (safe_str_eq(action->task, CRM_OP_SHUTDOWN)) { |
| action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); |
| |
| } else if (safe_str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT)) { |
| action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); |
| |
| } else if (safe_str_eq(action->task, CRM_OP_LRM_REFRESH)) { |
| action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); |
| |
| /* } else if(safe_str_eq(action->task, RSC_PROBED)) { */ |
| /* action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); */ |
| |
| } else if (is_set(action->flags, pe_action_pseudo)) { |
| if (safe_str_eq(action->task, CRM_OP_MAINTENANCE_NODES)) { |
| needs_maintenance_info = TRUE; |
| } |
| action_xml = create_xml_node(NULL, XML_GRAPH_TAG_PSEUDO_EVENT); |
| needs_node_info = FALSE; |
| |
| } else { |
| action_xml = create_xml_node(NULL, XML_GRAPH_TAG_RSC_OP); |
| #if ENABLE_VERSIONED_ATTRS |
| rsc_details = pe_rsc_action_details(action); |
| #endif |
| } |
| |
| crm_xml_add_int(action_xml, XML_ATTR_ID, action->id); |
| crm_xml_add(action_xml, XML_LRM_ATTR_TASK, action->task); |
| if (action->rsc != NULL && action->rsc->clone_name != NULL) { |
| char *clone_key = NULL; |
| const char *interval_s = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL); |
| int interval = crm_parse_int(interval_s, "0"); |
| |
| if (safe_str_eq(action->task, RSC_NOTIFY)) { |
| const char *n_type = g_hash_table_lookup(action->meta, "notify_type"); |
| const char *n_task = g_hash_table_lookup(action->meta, "notify_operation"); |
| |
| CRM_CHECK(n_type != NULL, crm_err("No notify type value found for %s", action->uuid)); |
| CRM_CHECK(n_task != NULL, |
| crm_err("No notify operation value found for %s", action->uuid)); |
| clone_key = generate_notify_key(action->rsc->clone_name, n_type, n_task); |
| |
| } else if(action->cancel_task) { |
| clone_key = generate_op_key(action->rsc->clone_name, action->cancel_task, interval); |
| } else { |
| clone_key = generate_op_key(action->rsc->clone_name, action->task, interval); |
| } |
| |
| CRM_CHECK(clone_key != NULL, crm_err("Could not generate a key for %s", action->uuid)); |
| crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, clone_key); |
| crm_xml_add(action_xml, "internal_" XML_LRM_ATTR_TASK_KEY, action->uuid); |
| free(clone_key); |
| |
| } else { |
| crm_xml_add(action_xml, XML_LRM_ATTR_TASK_KEY, action->uuid); |
| } |
| |
| if (needs_node_info && action->node != NULL) { |
| node_t *router_node = get_router_node(action); |
| |
| crm_xml_add(action_xml, XML_LRM_ATTR_TARGET, action->node->details->uname); |
| crm_xml_add(action_xml, XML_LRM_ATTR_TARGET_UUID, action->node->details->id); |
| if (router_node) { |
| crm_xml_add(action_xml, XML_LRM_ATTR_ROUTER_NODE, router_node->details->uname); |
| } |
| |
| g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET), strdup(action->node->details->uname)); |
| g_hash_table_insert(action->meta, strdup(XML_LRM_ATTR_TARGET_UUID), strdup(action->node->details->id)); |
| } |
| |
| /* No details if this action is only being listed in the inputs section */ |
| if (as_input) { |
| return action_xml; |
| } |
| |
| if (action->rsc && is_not_set(action->flags, pe_action_pseudo)) { |
| int lpc = 0; |
| xmlNode *rsc_xml = NULL; |
| const char *attr_list[] = { |
| XML_AGENT_ATTR_CLASS, |
| XML_AGENT_ATTR_PROVIDER, |
| XML_ATTR_TYPE |
| }; |
| |
| // List affected resource |
| |
| rsc_xml = create_xml_node(action_xml, |
| crm_element_name(action->rsc->xml)); |
| if (is_set(action->rsc->flags, pe_rsc_orphan) |
| && action->rsc->clone_name) { |
| /* Do not use the 'instance free' name here as that |
| * might interfere with the instance we plan to keep. |
| * Ie. if there are more than two named /anonymous/ |
| * instances on a given node, we need to make sure the |
| * command goes to the right one. |
| * |
| * Keep this block, even when everyone is using |
| * 'instance free' anonymous clone names - it means |
| * we'll do the right thing if anyone toggles the |
| * unique flag to 'off' |
| */ |
| crm_debug("Using orphan clone name %s instead of %s", action->rsc->id, |
| action->rsc->clone_name); |
| crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->clone_name); |
| crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id); |
| |
| } else if (is_not_set(action->rsc->flags, pe_rsc_unique)) { |
| const char *xml_id = ID(action->rsc->xml); |
| |
| crm_debug("Using anonymous clone name %s for %s (aka. %s)", xml_id, action->rsc->id, |
| action->rsc->clone_name); |
| |
| /* ID is what we'd like client to use |
| * ID_LONG is what they might know it as instead |
| * |
| * ID_LONG is only strictly needed /here/ during the |
| * transition period until all nodes in the cluster |
| * are running the new software /and/ have rebooted |
| * once (meaning that they've only ever spoken to a DC |
| * supporting this feature). |
| * |
| * If anyone toggles the unique flag to 'on', the |
| * 'instance free' name will correspond to an orphan |
| * and fall into the clause above instead |
| */ |
| crm_xml_add(rsc_xml, XML_ATTR_ID, xml_id); |
| if (action->rsc->clone_name && safe_str_neq(xml_id, action->rsc->clone_name)) { |
| crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->clone_name); |
| } else { |
| crm_xml_add(rsc_xml, XML_ATTR_ID_LONG, action->rsc->id); |
| } |
| |
| } else { |
| CRM_ASSERT(action->rsc->clone_name == NULL); |
| crm_xml_add(rsc_xml, XML_ATTR_ID, action->rsc->id); |
| } |
| |
| for (lpc = 0; lpc < DIMOF(attr_list); lpc++) { |
| crm_xml_add(rsc_xml, attr_list[lpc], |
| g_hash_table_lookup(action->rsc->meta, attr_list[lpc])); |
| } |
| } |
| |
| /* List any attributes in effect */ |
| args_xml = create_xml_node(NULL, XML_TAG_ATTRS); |
| crm_xml_add(args_xml, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); |
| |
| g_hash_table_foreach(action->extra, hash2field, args_xml); |
| if (action->rsc != NULL && action->node) { |
| GHashTable *p = crm_str_table_new(); |
| |
| get_rsc_attributes(p, action->rsc, action->node, data_set); |
| g_hash_table_foreach(p, hash2smartfield, args_xml); |
| g_hash_table_destroy(p); |
| |
| #if ENABLE_VERSIONED_ATTRS |
| { |
| xmlNode *versioned_parameters = create_xml_node(NULL, XML_TAG_RSC_VER_ATTRS); |
| |
| pe_get_versioned_attributes(versioned_parameters, action->rsc, |
| action->node, data_set); |
| if (xml_has_children(versioned_parameters)) { |
| add_node_copy(action_xml, versioned_parameters); |
| } |
| free_xml(versioned_parameters); |
| } |
| #endif |
| |
| } else if(action->rsc && action->rsc->variant <= pe_native) { |
| g_hash_table_foreach(action->rsc->parameters, hash2smartfield, args_xml); |
| |
| #if ENABLE_VERSIONED_ATTRS |
| if (xml_has_children(action->rsc->versioned_parameters)) { |
| add_node_copy(action_xml, action->rsc->versioned_parameters); |
| } |
| #endif |
| } |
| |
| #if ENABLE_VERSIONED_ATTRS |
| if (rsc_details) { |
| if (xml_has_children(rsc_details->versioned_parameters)) { |
| add_node_copy(action_xml, rsc_details->versioned_parameters); |
| } |
| |
| if (xml_has_children(rsc_details->versioned_meta)) { |
| add_node_copy(action_xml, rsc_details->versioned_meta); |
| } |
| } |
| #endif |
| |
| g_hash_table_foreach(action->meta, hash2metafield, args_xml); |
| if (action->rsc != NULL) { |
| int isolated = 0; |
| const char *value = g_hash_table_lookup(action->rsc->meta, "external-ip"); |
| resource_t *parent = action->rsc; |
| |
| while (parent != NULL) { |
| isolated |= parent->isolation_wrapper ? 1 : 0; |
| parent->cmds->append_meta(parent, args_xml); |
| parent = parent->parent; |
| } |
| |
| if (isolated && action->node) { |
| char *nodeattr = crm_meta_name(XML_RSC_ATTR_ISOLATION_HOST); |
| crm_xml_add(args_xml, nodeattr, action->node->details->uname); |
| free(nodeattr); |
| } |
| |
| if(value) { |
| hash2smartfield((gpointer)"pcmk_external_ip", (gpointer)value, (gpointer)args_xml); |
| } |
| |
| if(is_container_remote_node(action->node)) { |
| pe_node_t *host = NULL; |
| enum action_tasks task = text2task(action->task); |
| |
| if(task == action_notify || task == action_notified) { |
| const char *n_task = g_hash_table_lookup(action->meta, "notify_operation"); |
| task = text2task(n_task); |
| } |
| |
| // Differentiate between up and down actions |
| switch (task) { |
| case stop_rsc: |
| case stopped_rsc: |
| case action_demote: |
| case action_demoted: |
| host = pe__current_node(action->node->details->remote_rsc->container); |
| break; |
| case start_rsc: |
| case started_rsc: |
| case monitor_rsc: |
| case action_promote: |
| case action_promoted: |
| host = action->node->details->remote_rsc->container->allocated_to; |
| break; |
| default: |
| break; |
| } |
| |
| if(host) { |
| hash2metafield((gpointer)XML_RSC_ATTR_TARGET, |
| (gpointer)g_hash_table_lookup(action->rsc->meta, XML_RSC_ATTR_TARGET), (gpointer)args_xml); |
| hash2metafield((gpointer)PCMK_ENV_PHYSICAL_HOST, (gpointer)host->details->uname, (gpointer)args_xml); |
| } |
| } |
| |
| } else if (safe_str_eq(action->task, CRM_OP_FENCE) && action->node) { |
| /* Pass the node's attributes as meta-attributes. |
| * |
| * @TODO: Determine whether it is still necessary to do this. It was |
| * added in 33d99707, probably for the libfence-based implementation in |
| * c9a90bd, which is no longer used. |
| */ |
| g_hash_table_foreach(action->node->details->attrs, hash2metafield, args_xml); |
| } |
| |
| sorted_xml(args_xml, action_xml, FALSE); |
| free_xml(args_xml); |
| |
| /* List any nodes this action is expected to make down */ |
| if (needs_node_info && (action->node != NULL)) { |
| add_downed_nodes(action_xml, action, data_set); |
| } |
| |
| if (needs_maintenance_info) { |
| add_maintenance_nodes(action_xml, data_set); |
| } |
| |
| crm_log_xml_trace(action_xml, "dumped action"); |
| return action_xml; |
| } |
| |
| static gboolean |
| should_dump_action(action_t * action) |
| { |
| CRM_CHECK(action != NULL, return FALSE); |
| |
| if (is_set(action->flags, pe_action_dumped)) { |
| crm_trace("action %d (%s) was already dumped", action->id, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(action->flags, pe_action_pseudo) && safe_str_eq(action->task, CRM_OP_PROBED)) { |
| GListPtr lpc = NULL; |
| |
| /* This is a horrible but convenient hack |
| * |
| * It mimimizes the number of actions with unsatisfied inputs |
| * (i.e. not included in the graph) |
| * |
| * This in turn, means we can be more concise when printing |
| * aborted/incomplete graphs. |
| * |
| * It also makes it obvious which node is preventing |
| * probe_complete from running (presumably because it is only |
| * partially up) |
| * |
| * For these reasons we tolerate such perversions |
| */ |
| |
| for (lpc = action->actions_after; lpc != NULL; lpc = lpc->next) { |
| action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data; |
| |
| if (is_not_set(wrapper->action->flags, pe_action_runnable)) { |
| /* Only interested in runnable operations */ |
| } else if (safe_str_neq(wrapper->action->task, RSC_START)) { |
| /* Only interested in start operations */ |
| } else if (is_set(wrapper->action->flags, pe_action_dumped)) { |
| crm_trace("action %d (%s) dependency of %s", |
| action->id, action->uuid, wrapper->action->uuid); |
| return TRUE; |
| |
| } else if (should_dump_action(wrapper->action)) { |
| crm_trace("action %d (%s) dependency of %s", |
| action->id, action->uuid, wrapper->action->uuid); |
| return TRUE; |
| } |
| } |
| } |
| |
| if (is_set(action->flags, pe_action_runnable) == FALSE) { |
| crm_trace("action %d (%s) was not runnable", action->id, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(action->flags, pe_action_optional) |
| && is_set(action->flags, pe_action_print_always) == FALSE) { |
| crm_trace("action %d (%s) was optional", action->id, action->uuid); |
| return FALSE; |
| |
| } else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) { |
| const char *interval = NULL; |
| |
| interval = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL); |
| |
| /* make sure probes and recurring monitors go through */ |
| if (safe_str_neq(action->task, RSC_STATUS) && interval == NULL) { |
| crm_trace("action %d (%s) was for an unmanaged resource (%s)", |
| action->id, action->uuid, action->rsc->id); |
| return FALSE; |
| } |
| } |
| |
| if (is_set(action->flags, pe_action_pseudo) |
| || safe_str_eq(action->task, CRM_OP_FENCE) |
| || safe_str_eq(action->task, CRM_OP_SHUTDOWN)) { |
| /* skip the next checks */ |
| return TRUE; |
| } |
| |
| if (action->node == NULL) { |
| pe_err("action %d (%s) was not allocated", action->id, action->uuid); |
| log_action(LOG_DEBUG, "Unallocated action", action, FALSE); |
| return FALSE; |
| |
| } else if(is_container_remote_node(action->node) && action->node->details->remote_requires_reset == FALSE) { |
| crm_trace("Assuming action %s for %s will be runnable", action->uuid, action->node->details->uname); |
| |
| } else if (action->node->details->online == FALSE) { |
| pe_err("action %d was (%s) scheduled for offline node", action->id, action->uuid); |
| log_action(LOG_DEBUG, "Action for offline node", action, FALSE); |
| return FALSE; |
| #if 0 |
| /* but this would also affect resources that can be safely |
| * migrated before a fencing op |
| */ |
| } else if (action->node->details->unclean == FALSE) { |
| pe_err("action %d was (%s) scheduled for unclean node", action->id, action->uuid); |
| log_action(LOG_DEBUG, "Action for unclean node", action, FALSE); |
| return FALSE; |
| #endif |
| } |
| return TRUE; |
| } |
| |
| /* lowest to highest */ |
| static gint |
| sort_action_id(gconstpointer a, gconstpointer b) |
| { |
| const action_wrapper_t *action_wrapper2 = (const action_wrapper_t *)a; |
| const action_wrapper_t *action_wrapper1 = (const action_wrapper_t *)b; |
| |
| if (a == NULL) { |
| return 1; |
| } |
| if (b == NULL) { |
| return -1; |
| } |
| |
| if (action_wrapper1->action->id > action_wrapper2->action->id) { |
| return -1; |
| } |
| |
| if (action_wrapper1->action->id < action_wrapper2->action->id) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static gboolean |
| check_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper) |
| { |
| int type = wrapper->type; |
| |
| if (wrapper->state == pe_link_dumped) { |
| return TRUE; |
| |
| } else if (wrapper->state == pe_link_dup) { |
| return FALSE; |
| } |
| |
| type &= ~pe_order_implies_first_printed; |
| type &= ~pe_order_implies_then_printed; |
| type &= ~pe_order_optional; |
| |
| if (is_not_set(type, pe_order_preserve) |
| && action->rsc && action->rsc->fillers |
| && wrapper->action->rsc && wrapper->action->node |
| && wrapper->action->node->details->remote_rsc |
| && (wrapper->action->node->details->remote_rsc->container == action->rsc)) { |
| /* This prevents user-defined ordering constraints between resources |
| * running in a guest node and the resource that defines that node. |
| */ |
| crm_warn("Invalid ordering constraint between %s and %s", |
| wrapper->action->rsc->id, action->rsc->id); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| } |
| |
| if (last_action == wrapper->action->id) { |
| crm_trace("Input (%d) %s duplicated for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| wrapper->state = pe_link_dup; |
| return FALSE; |
| |
| } else if (wrapper->type == pe_order_none) { |
| crm_trace("Input (%d) %s suppressed for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE |
| && type == pe_order_none && safe_str_neq(wrapper->action->uuid, CRM_OP_PROBED)) { |
| crm_trace("Input (%d) %s optional (ordering) for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(wrapper->action->flags, pe_action_runnable) == FALSE |
| && is_set(type, pe_order_one_or_more)) { |
| crm_trace("Input (%d) %s optional (one-or-more) for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(action->flags, pe_action_pseudo) |
| && (wrapper->type & pe_order_stonith_stop)) { |
| crm_trace("Input (%d) %s suppressed for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| return FALSE; |
| |
| } else if ((wrapper->type & pe_order_implies_first_migratable) && (is_set(wrapper->action->flags, pe_action_runnable) == FALSE)) { |
| return FALSE; |
| |
| } else if ((wrapper->type & pe_order_apply_first_non_migratable) |
| && (is_set(wrapper->action->flags, pe_action_migrate_runnable))) { |
| return FALSE; |
| |
| } else if ((wrapper->type == pe_order_optional) |
| && crm_ends_with(wrapper->action->uuid, "_stop_0") |
| && is_set(wrapper->action->flags, pe_action_migrate_runnable)) { |
| |
| /* for optional only ordering, ordering is not preserved for |
| * a stop action that is actually involved with a migration. */ |
| return FALSE; |
| |
| } else if (wrapper->type == pe_order_load) { |
| crm_trace("check load filter %s.%s -> %s.%s", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", action->uuid, |
| action->node ? action->node->details->uname : ""); |
| |
| if (action->rsc && safe_str_eq(action->task, RSC_MIGRATE)) { |
| /* Remove the orders like the following if not relevant: |
| * "load_stopped_node2" -> "rscA_migrate_to node1" |
| * which were created also from: pengine/native.c: MigrateRsc() |
| * order_actions(other, then, other_w->type); |
| */ |
| |
| /* For migrate_to ops, we care about where it has been |
| * allocated to, not where the action will be executed |
| */ |
| if (wrapper->action->node == NULL || action->rsc->allocated_to == NULL |
| || wrapper->action->node->details != action->rsc->allocated_to->details) { |
| /* Check if the actions are for the same node, ignore otherwise */ |
| crm_trace("load filter - migrate"); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| } |
| |
| } else if (wrapper->action->node == NULL || action->node == NULL |
| || wrapper->action->node->details != action->node->details) { |
| /* Check if the actions are for the same node, ignore otherwise */ |
| crm_trace("load filter - node"); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| |
| } else if (is_set(wrapper->action->flags, pe_action_optional)) { |
| /* Check if the pre-req is optional, ignore if so */ |
| crm_trace("load filter - optional"); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| } |
| |
| } else if (wrapper->type == pe_order_anti_colocation) { |
| crm_trace("check anti-colocation filter %s.%s -> %s.%s", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", |
| action->uuid, |
| action->node ? action->node->details->uname : ""); |
| |
| if (wrapper->action->node && action->node |
| && wrapper->action->node->details != action->node->details) { |
| /* Check if the actions are for the same node, ignore otherwise */ |
| crm_trace("anti-colocation filter - node"); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| |
| } else if (is_set(wrapper->action->flags, pe_action_optional)) { |
| /* Check if the pre-req is optional, ignore if so */ |
| crm_trace("anti-colocation filter - optional"); |
| wrapper->type = pe_order_none; |
| return FALSE; |
| } |
| |
| } else if (wrapper->action->rsc |
| && wrapper->action->rsc != action->rsc |
| && is_set(wrapper->action->rsc->flags, pe_rsc_failed) |
| && is_not_set(wrapper->action->rsc->flags, pe_rsc_managed) |
| && crm_ends_with(wrapper->action->uuid, "_stop_0") |
| && action->rsc && pe_rsc_is_clone(action->rsc)) { |
| crm_warn("Ignoring requirement that %s complete before %s:" |
| " unmanaged failed resources cannot prevent clone shutdown", |
| wrapper->action->uuid, action->uuid); |
| return FALSE; |
| |
| } else if (is_set(wrapper->action->flags, pe_action_dumped) |
| || should_dump_action(wrapper->action)) { |
| crm_trace("Input (%d) %s should be dumped for %s", wrapper->action->id, |
| wrapper->action->uuid, action->uuid); |
| goto dump; |
| |
| #if 0 |
| } else if (is_set(wrapper->action->flags, pe_action_runnable) |
| && is_set(wrapper->action->flags, pe_action_pseudo) |
| && wrapper->action->rsc->variant != pe_native) { |
| crm_crit("Input (%d) %s should be dumped for %s", |
| wrapper->action->id, wrapper->action->uuid, action->uuid); |
| goto dump; |
| #endif |
| } else if (is_set(wrapper->action->flags, pe_action_optional) == TRUE |
| && is_set(wrapper->action->flags, pe_action_print_always) == FALSE) { |
| crm_trace("Input (%d) %s optional for %s", wrapper->action->id, |
| wrapper->action->uuid, action->uuid); |
| crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x", |
| wrapper->action->id, wrapper->action->uuid, wrapper->action->node, |
| is_set(wrapper->action->flags, pe_action_pseudo), |
| is_set(wrapper->action->flags, pe_action_runnable), |
| is_set(wrapper->action->flags, pe_action_optional), |
| is_set(wrapper->action->flags, pe_action_print_always), wrapper->type); |
| return FALSE; |
| |
| } |
| |
| dump: |
| return TRUE; |
| } |
| |
| static gboolean |
| graph_has_loop(action_t * init_action, action_t * action, action_wrapper_t * wrapper) |
| { |
| GListPtr lpc = NULL; |
| gboolean has_loop = FALSE; |
| |
| if (is_set(wrapper->action->flags, pe_action_tracking)) { |
| crm_trace("Breaking tracking loop: %s.%s -> %s.%s (0x%.6x)", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", |
| action->uuid, |
| action->node ? action->node->details->uname : "", |
| wrapper->type); |
| return FALSE; |
| } |
| |
| if (check_dump_input(-1, action, wrapper) == FALSE) { |
| return FALSE; |
| } |
| |
| /* If there's any order like: |
| * "rscB_stop node2"-> "load_stopped_node2" -> "rscA_migrate_to node1" |
| * rscA is being migrated from node1 to node2, |
| * while rscB is being migrated from node2 to node1. |
| * There will be potential graph loop. |
| * Break the order "load_stopped_node2" -> "rscA_migrate_to node1". |
| */ |
| |
| crm_trace("Checking graph loop: %s.%s -> %s.%s (0x%.6x)", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", |
| action->uuid, |
| action->node ? action->node->details->uname : "", |
| wrapper->type); |
| |
| if (wrapper->action == init_action) { |
| crm_debug("Found graph loop: %s.%s ->...-> %s.%s", |
| action->uuid, |
| action->node ? action->node->details->uname : "", |
| init_action->uuid, |
| init_action->node ? init_action->node->details->uname : ""); |
| |
| return TRUE; |
| } |
| |
| set_bit(wrapper->action->flags, pe_action_tracking); |
| |
| for (lpc = wrapper->action->actions_before; lpc != NULL; lpc = lpc->next) { |
| action_wrapper_t *wrapper_before = (action_wrapper_t *) lpc->data; |
| |
| if (graph_has_loop(init_action, wrapper->action, wrapper_before)) { |
| has_loop = TRUE; |
| goto done; |
| } |
| } |
| |
| done: |
| pe_clear_action_bit(wrapper->action, pe_action_tracking); |
| |
| return has_loop; |
| } |
| |
| static gboolean |
| should_dump_input(int last_action, action_t * action, action_wrapper_t * wrapper) |
| { |
| wrapper->state = pe_link_not_dumped; |
| |
| if (check_dump_input(last_action, action, wrapper) == FALSE) { |
| return FALSE; |
| } |
| |
| if (wrapper->type == pe_order_load |
| && action->rsc |
| && safe_str_eq(action->task, RSC_MIGRATE)) { |
| crm_trace("Checking graph loop - load migrate: %s.%s -> %s.%s", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", |
| action->uuid, |
| action->node ? action->node->details->uname : ""); |
| |
| if (graph_has_loop(action, action, wrapper)) { |
| /* Remove the orders like the following if they are introducing any graph loops: |
| * "load_stopped_node2" -> "rscA_migrate_to node1" |
| * which were created also from: pengine/native.c: MigrateRsc() |
| * order_actions(other, then, other_w->type); |
| */ |
| crm_debug("Breaking graph loop - load migrate: %s.%s -> %s.%s", |
| wrapper->action->uuid, |
| wrapper->action->node ? wrapper->action->node->details->uname : "", |
| action->uuid, |
| action->node ? action->node->details->uname : ""); |
| |
| wrapper->type = pe_order_none; |
| return FALSE; |
| } |
| } |
| |
| crm_trace("Input (%d) %s n=%p p=%d r=%d o=%d a=%d f=0x%.6x dumped for %s", |
| wrapper->action->id, |
| wrapper->action->uuid, |
| wrapper->action->node, |
| is_set(wrapper->action->flags, pe_action_pseudo), |
| is_set(wrapper->action->flags, pe_action_runnable), |
| is_set(wrapper->action->flags, pe_action_optional), |
| is_set(wrapper->action->flags, pe_action_print_always), wrapper->type, action->uuid); |
| return TRUE; |
| } |
| |
| void |
| graph_element_from_action(action_t * action, pe_working_set_t * data_set) |
| { |
| GListPtr lpc = NULL; |
| int last_action = -1; |
| int synapse_priority = 0; |
| xmlNode *syn = NULL; |
| xmlNode *set = NULL; |
| xmlNode *in = NULL; |
| xmlNode *input = NULL; |
| xmlNode *xml_action = NULL; |
| |
| if (should_dump_action(action) == FALSE) { |
| return; |
| } |
| |
| set_bit(action->flags, pe_action_dumped); |
| |
| syn = create_xml_node(data_set->graph, "synapse"); |
| set = create_xml_node(syn, "action_set"); |
| in = create_xml_node(syn, "inputs"); |
| |
| crm_xml_add_int(syn, XML_ATTR_ID, data_set->num_synapse); |
| data_set->num_synapse++; |
| |
| if (action->rsc != NULL) { |
| synapse_priority = action->rsc->priority; |
| } |
| if (action->priority > synapse_priority) { |
| synapse_priority = action->priority; |
| } |
| if (synapse_priority > 0) { |
| crm_xml_add_int(syn, XML_CIB_ATTR_PRIORITY, synapse_priority); |
| } |
| |
| xml_action = action2xml(action, FALSE, data_set); |
| add_node_nocopy(set, crm_element_name(xml_action), xml_action); |
| |
| action->actions_before = g_list_sort(action->actions_before, sort_action_id); |
| |
| for (lpc = action->actions_before; lpc != NULL; lpc = lpc->next) { |
| action_wrapper_t *wrapper = (action_wrapper_t *) lpc->data; |
| |
| if (should_dump_input(last_action, action, wrapper) == FALSE) { |
| continue; |
| } |
| |
| wrapper->state = pe_link_dumped; |
| CRM_CHECK(last_action < wrapper->action->id,; |
| ); |
| last_action = wrapper->action->id; |
| input = create_xml_node(in, "trigger"); |
| |
| xml_action = action2xml(wrapper->action, TRUE, data_set); |
| add_node_nocopy(input, crm_element_name(xml_action), xml_action); |
| } |
| } |