diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am index b02a412a2a..dd75c06889 100644 --- a/lib/pacemaker/Makefile.am +++ b/lib/pacemaker/Makefile.am @@ -1,60 +1,61 @@ # # Copyright 2004-2021 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 $(top_srcdir)/mk/common.mk AM_CPPFLAGS += -I$(top_builddir) -I$(top_srcdir) noinst_HEADERS = libpacemaker_private.h ## libraries lib_LTLIBRARIES = libpacemaker.la ## SOURCES libpacemaker_la_LDFLAGS = -version-info 4:0:3 libpacemaker_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libpacemaker_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) libpacemaker_la_LIBADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/common/libcrmcommon.la # -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version # Use += rather than backlashed continuation lines for parsing by bumplibs libpacemaker_la_SOURCES = libpacemaker_la_SOURCES += pcmk_cluster_queries.c libpacemaker_la_SOURCES += pcmk_fence.c libpacemaker_la_SOURCES += pcmk_graph_consumer.c libpacemaker_la_SOURCES += pcmk_graph_logging.c libpacemaker_la_SOURCES += pcmk_graph_producer.c libpacemaker_la_SOURCES += pcmk_output.c libpacemaker_la_SOURCES += pcmk_output_utils.c libpacemaker_la_SOURCES += pcmk_resource.c +libpacemaker_la_SOURCES += pcmk_sched_actions.c libpacemaker_la_SOURCES += pcmk_sched_allocate.c libpacemaker_la_SOURCES += pcmk_sched_bundle.c libpacemaker_la_SOURCES += pcmk_sched_clone.c libpacemaker_la_SOURCES += pcmk_sched_colocation.c libpacemaker_la_SOURCES += pcmk_sched_constraints.c libpacemaker_la_SOURCES += pcmk_sched_fencing.c libpacemaker_la_SOURCES += pcmk_sched_group.c libpacemaker_la_SOURCES += pcmk_sched_location.c libpacemaker_la_SOURCES += pcmk_sched_messages.c libpacemaker_la_SOURCES += pcmk_sched_native.c libpacemaker_la_SOURCES += pcmk_sched_notif.c libpacemaker_la_SOURCES += pcmk_sched_ordering.c libpacemaker_la_SOURCES += pcmk_sched_promotable.c libpacemaker_la_SOURCES += pcmk_sched_remote.c libpacemaker_la_SOURCES += pcmk_sched_resource.c libpacemaker_la_SOURCES += pcmk_sched_tickets.c libpacemaker_la_SOURCES += pcmk_sched_transition.c libpacemaker_la_SOURCES += pcmk_sched_utilization.c libpacemaker_la_SOURCES += pcmk_sched_utils.c libpacemaker_la_SOURCES += pcmk_simulate.c diff --git a/lib/pacemaker/pcmk_graph_producer.c b/lib/pacemaker/pcmk_graph_producer.c index 5bec9d8cea..7061f37d20 100644 --- a/lib/pacemaker/pcmk_graph_producer.c +++ b/lib/pacemaker/pcmk_graph_producer.c @@ -1,1558 +1,988 @@ /* * Copyright 2004-2021 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 #include #include #include #include "libpacemaker_private.h" -gboolean rsc_update_action(pe_action_t * first, pe_action_t * then, enum pe_ordering type); - -static enum pe_action_flags -get_action_flags(pe_action_t * action, pe_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 (!pcmk_is_set(clone_flags, pe_action_runnable) - && pcmk_is_set(flags, pe_action_runnable)) { - - pe__set_raw_action_flags(clone_flags, action->rsc->id, - pe_action_runnable); - } - flags = clone_flags; - } - } - return flags; -} - -static char * -convert_non_atomic_uuid(char *old_uuid, pe_resource_t * rsc, gboolean allow_notify, - gboolean free_original) -{ - guint interval_ms = 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_ms)); - if (interval_ms > 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 (pcmk_is_set(rsc->flags, pe_rsc_notify) && allow_notify) { - uuid = pcmk__notify_key(rid, "confirmed-post", task2text(task + 1)); - - } else { - uuid = pcmk__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 pe_action_t * -rsc_expand_action(pe_action_t * action) -{ - gboolean notify = FALSE; - pe_action_t *result = action; - pe_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 = pcmk_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, - pcmk_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(pe_action_t * first, pe_action_t * then, pe_node_t * node, - enum pe_action_flags first_flags, enum pe_action_flags then_flags, - pe_action_wrapper_t *order, pe_working_set_t *data_set) -{ - enum pe_graph_flags changed = pe_graph_none; - enum pe_ordering type = order->type; - - /* TODO: Do as many of these in parallel as possible */ - - if (pcmk_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' - */ - - pe__clear_order_flags(type, pe_order_implies_then_on_node); - pe__set_order_flags(type, pe_order_implies_then); - node = first->node; - pe_rsc_trace(then->rsc, - "%s then %s: mapped pe_order_implies_then_on_node to " - "pe_order_implies_then on %s", - first->uuid, then->uuid, node->details->uname); - } - - if (type & pe_order_implies_then) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags & pe_action_optional, pe_action_optional, - pe_order_implies_then, data_set); - - } else if (!pcmk_is_set(first_flags, pe_action_optional) - && pcmk_is_set(then->flags, pe_action_optional)) { - pe__clear_action_flags(then, pe_action_optional); - pe__set_graph_flags(changed, first, pe_graph_updated_then); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_then", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if ((type & pe_order_restart) && then->rsc) { - enum pe_action_flags restart = (pe_action_optional | pe_action_runnable); - - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, restart, - pe_order_restart, data_set); - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_restart", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_implies_first) { - if (first->rsc) { - changed |= first->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_optional, pe_order_implies_first, - data_set); - - } else if (!pcmk_is_set(first_flags, pe_action_optional) - && pcmk_is_set(first->flags, pe_action_runnable)) { - pe__clear_action_flags(first, pe_action_runnable); - pe__set_graph_flags(changed, first, pe_graph_updated_first); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_first", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_promoted_implies_first) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags & pe_action_optional, pe_action_optional, - pe_order_promoted_implies_first, data_set); - } - pe_rsc_trace(then->rsc, - "%s then %s: %s after pe_order_promoted_implies_first", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_one_or_more) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_runnable, pe_order_one_or_more, - data_set); - - } else if (pcmk_is_set(first_flags, pe_action_runnable)) { - // We have another runnable instance of "first" - then->runnable_before++; - - /* Mark "then" as runnable if it requires a certain number of - * "before" instances to be runnable, and they now are. - */ - if ((then->runnable_before >= then->required_runnable_before) - && !pcmk_is_set(then->flags, pe_action_runnable)) { - - pe__set_action_flags(then, pe_action_runnable); - pe__set_graph_flags(changed, first, pe_graph_updated_then); - } - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_one_or_more", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (then->rsc && pcmk_is_set(type, pe_order_probe)) { - if (!pcmk_is_set(first_flags, pe_action_runnable) - && (first->rsc->running_on != NULL)) { - - pe_rsc_trace(then->rsc, - "%s then %s: ignoring because first is stopping", - first->uuid, then->uuid); - type = pe_order_none; - order->type = pe_order_none; - - } else { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_runnable, pe_order_runnable_left, - data_set); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_probe", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_runnable_left) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_runnable, pe_order_runnable_left, - data_set); - - } else if (!pcmk_is_set(first_flags, pe_action_runnable) - && pcmk_is_set(then->flags, pe_action_runnable)) { - - pe__clear_action_flags(then, pe_action_runnable); - pe__set_graph_flags(changed, first, pe_graph_updated_then); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_runnable_left", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_implies_first_migratable) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_optional, - pe_order_implies_first_migratable, data_set); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after " - "pe_order_implies_first_migratable", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_pseudo_left) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_optional, pe_order_pseudo_left, - data_set); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_pseudo_left", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_optional) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_runnable, pe_order_optional, data_set); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_optional", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if (type & pe_order_asymmetrical) { - if (then->rsc) { - changed |= then->rsc->cmds->update_actions(first, then, node, - first_flags, pe_action_runnable, pe_order_asymmetrical, - data_set); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_asymmetrical", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - if ((first->flags & pe_action_runnable) && (type & pe_order_implies_then_printed) - && (first_flags & pe_action_optional) == 0) { - pe_rsc_trace(then->rsc, "%s will be in graph because %s is required", - then->uuid, first->uuid); - pe__set_action_flags(then, pe_action_print_always); - // Don't bother marking 'then' as changed just for this - } - - if (pcmk_is_set(type, pe_order_implies_first_printed) - && !pcmk_is_set(then_flags, pe_action_optional)) { - - pe_rsc_trace(then->rsc, "%s will be in graph because %s is required", - first->uuid, then->uuid); - pe__set_action_flags(first, pe_action_print_always); - // Don't bother marking 'first' as changed just for this - } - - if ((type & pe_order_implies_then - || type & pe_order_implies_first - || type & pe_order_restart) - && first->rsc - && pcmk__str_eq(first->task, RSC_STOP, pcmk__str_casei) - && !pcmk_is_set(first->rsc->flags, pe_rsc_managed) - && pcmk_is_set(first->rsc->flags, pe_rsc_block) - && !pcmk_is_set(first->flags, pe_action_runnable)) { - - if (pcmk_is_set(then->flags, pe_action_runnable)) { - pe__clear_action_flags(then, pe_action_runnable); - pe__set_graph_flags(changed, first, pe_graph_updated_then); - } - pe_rsc_trace(then->rsc, "%s then %s: %s after checking whether first " - "is blocked, unmanaged, unrunnable stop", - first->uuid, then->uuid, - (changed? "changed" : "unchanged")); - } - - return changed; -} - // Convenience macros for logging action properties #define action_type_str(flags) \ (pcmk_is_set((flags), pe_action_pseudo)? "pseudo-action" : "action") #define action_optional_str(flags) \ (pcmk_is_set((flags), pe_action_optional)? "optional" : "required") #define action_runnable_str(flags) \ (pcmk_is_set((flags), pe_action_runnable)? "runnable" : "unrunnable") #define action_node_str(a) \ (((a)->node == NULL)? "no node" : (a)->node->details->uname) -gboolean -update_action(pe_action_t *then, pe_working_set_t *data_set) -{ - GList *lpc = NULL; - enum pe_graph_flags changed = pe_graph_none; - int last_flags = then->flags; - - pe_rsc_trace(then->rsc, "Updating %s %s (%s %s) on %s", - action_type_str(then->flags), then->uuid, - action_optional_str(then->flags), - action_runnable_str(then->flags), action_node_str(then)); - - if (pcmk_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_flags(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) { - pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data; - pe_action_t *first = other->action; - - pe_node_t *then_node = then->node; - pe_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 && pcmk__str_eq(first->task, RSC_START, pcmk__str_casei)) { - first_node = first->rsc->fns->location(first->rsc, NULL, FALSE); - if (first_node) { - pe_rsc_trace(first->rsc, "Found node %s for 'first' %s", - first_node->details->uname, first->uuid); - } - } - - if (then->rsc && then->rsc->variant == pe_group && pcmk__str_eq(then->task, RSC_START, pcmk__str_casei)) { - then_node = then->rsc->fns->location(then->rsc, NULL, FALSE); - if (then_node) { - pe_rsc_trace(then->rsc, "Found node %s for 'then' %s", - then_node->details->uname, then->uuid); - } - } - /* Disable constraint if it only applies when on same node, but isn't */ - if (pcmk_is_set(other->type, pe_order_same_node) - && (first_node != NULL) && (then_node != NULL) - && (first_node->details != then_node->details)) { - - pe_rsc_trace(then->rsc, - "Disabled ordering %s on %s then %s on %s: not same node", - other->action->uuid, first_node->details->uname, - then->uuid, then_node->details->uname); - other->type = pe_order_none; - continue; - } - - pe__clear_graph_flags(changed, then, pe_graph_updated_first); - - if (first->rsc && pcmk_is_set(other->type, pe_order_then_cancels_first) - && !pcmk_is_set(then->flags, pe_action_optional)) { - - /* 'then' is required, so we must abandon 'first' - * (e.g. a required stop cancels any agent reload). - */ - pe__set_action_flags(other->action, pe_action_optional); - if (!strcmp(first->task, CRMD_ACTION_RELOAD_AGENT)) { - pe__clear_resource_flags(first->rsc, 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) { - pe_rsc_trace(then->rsc, "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); - - pe_rsc_trace(then->rsc, - "%s then %s: type=0x%.6x filter=0x%.6x " - "(%s %s %s on %s 0x%.6x then 0x%.6x)", - first->uuid, then->uuid, other->type, first_flags, - action_optional_str(first_flags), - action_runnable_str(first_flags), - action_type_str(first_flags), action_node_str(first), - first->flags, then->flags); - - 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 - * - */ - pe_node_t *node = then->node; - changed |= graph_update_action(first, then, node, first_flags, - then_flags, other, data_set); - - /* '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 - */ - pe__set_graph_flags(changed, then, - pe_graph_updated_then|pe_graph_disable); - } - - if (changed & pe_graph_disable) { - pe_rsc_trace(then->rsc, - "Disabled ordering %s then %s in favor of %s then %s", - other->action->uuid, then->uuid, first->uuid, - then->uuid); - pe__clear_graph_flags(changed, then, pe_graph_disable); - other->type = pe_order_none; - } - - if (changed & pe_graph_updated_first) { - GList *lpc2 = NULL; - - crm_trace("Re-processing %s and its 'after' actions since it changed", - first->uuid); - for (lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) { - pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc2->data; - - update_action(other->action, data_set); - } - update_action(first, data_set); - } - } - - if (pcmk_is_set(then->flags, pe_action_requires_any)) { - if (last_flags != then->flags) { - pe__set_graph_flags(changed, then, pe_graph_updated_then); - } else { - pe__clear_graph_flags(changed, then, pe_graph_updated_then); - } - } - - if (changed & pe_graph_updated_then) { - crm_trace("Re-processing %s and its 'after' actions since it changed", - then->uuid); - if (pcmk_is_set(last_flags, pe_action_runnable) - && !pcmk_is_set(then->flags, pe_action_runnable)) { - pcmk__block_colocated_starts(then, data_set); - } - update_action(then, data_set); - for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) { - pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data; - - update_action(other->action, data_set); - } - } - - return FALSE; -} - /*! * \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 pe_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) { GList *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) { pe_node_t *node = (pe_node_t *) gIter->data; struct pe_node_shared_s *details = node->details; if (!pe__is_guest_or_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) { pe_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); pe__set_action_flags(action, 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 controller 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 pe_action_t *action, const pe_working_set_t *data_set) { CRM_CHECK(xml && action && action->node && data_set, return); if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) { /* 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 (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) { /* Fencing makes the action's node and any hosted guest nodes down */ const char *fence = g_hash_table_lookup(action->meta, "stonith_action"); if (pcmk__is_fencing_action(fence)) { 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 && pcmk__str_eq(action->task, CRMD_ACTION_STOP, pcmk__str_casei)) { /* Stopping a remote connection resource makes connected node down, * unless it's part of a migration */ GList *iter; pe_action_t *input; gboolean migrating = FALSE; for (iter = action->actions_before; iter != NULL; iter = iter->next) { input = ((pe_action_wrapper_t *) iter->data)->action; if (input->rsc && pcmk__str_eq(action->rsc->id, input->rsc->id, pcmk__str_casei) && pcmk__str_eq(input->task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) { 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 bool should_lock_action(pe_action_t *action) { // Only actions taking place on resource's lock node are locked if ((action->rsc->lock_node == NULL) || (action->node == NULL) || (action->node->details != action->rsc->lock_node->details)) { return false; } /* During shutdown, only stops are locked (otherwise, another action such as * a demote would cause the controller to clear the lock) */ if (action->node->details->shutdown && action->task && strcmp(action->task, RSC_STOP)) { return false; } return true; } static xmlNode * action2xml(pe_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 (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei)) { /* All fences need node info; guest node fences are pseudo-events */ action_xml = create_xml_node(NULL, pcmk_is_set(action->flags, pe_action_pseudo)? XML_GRAPH_TAG_PSEUDO_EVENT : XML_GRAPH_TAG_CRM_EVENT); } else if (pcmk__str_eq(action->task, CRM_OP_SHUTDOWN, pcmk__str_casei)) { action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); } else if (pcmk__str_eq(action->task, CRM_OP_CLEAR_FAILCOUNT, pcmk__str_casei)) { action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); } else if (pcmk__str_eq(action->task, CRM_OP_LRM_REFRESH, pcmk__str_casei)) { action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); } else if (pcmk__str_eq(action->task, CRM_OP_LRM_DELETE, pcmk__str_casei)) { // CIB-only clean-up for shutdown locks action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); crm_xml_add(action_xml, PCMK__XA_MODE, XML_TAG_CIB); /* } else if(pcmk__str_eq(action->task, RSC_PROBED, pcmk__str_casei)) { */ /* action_xml = create_xml_node(NULL, XML_GRAPH_TAG_CRM_EVENT); */ } else if (pcmk_is_set(action->flags, pe_action_pseudo)) { if (pcmk__str_eq(action->task, CRM_OP_MAINTENANCE_NODES, pcmk__str_casei)) { 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; guint interval_ms; if (pcmk__guint_from_hash(action->meta, XML_LRM_ATTR_INTERVAL_MS, 0, &interval_ms) != pcmk_rc_ok) { interval_ms = 0; } if (pcmk__str_eq(action->task, RSC_NOTIFY, pcmk__str_casei)) { 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 = pcmk__notify_key(action->rsc->clone_name, n_type, n_task); } else if(action->cancel_task) { clone_key = pcmk__op_key(action->rsc->clone_name, action->cancel_task, interval_ms); } else { clone_key = pcmk__op_key(action->rsc->clone_name, action->task, interval_ms); } 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) { pe_node_t *router_node = pcmk__connection_host_for_action(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 && !pcmk_is_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 }; /* If a resource is locked to a node via shutdown-lock, mark its actions * so the controller can preserve the lock when the action completes. */ if (should_lock_action(action)) { crm_xml_add_ll(action_xml, XML_CONFIG_ATTR_SHUTDOWN_LOCK, (long long) action->rsc->lock_time); } // List affected resource rsc_xml = create_xml_node(action_xml, crm_element_name(action->rsc->xml)); if (pcmk_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 (!pcmk_is_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 && !pcmk__str_eq(xml_id, action->rsc->clone_name, pcmk__str_casei)) { 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 < PCMK__NELEM(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) { // Get the resource instance attributes, evaluated properly for node GHashTable *params = pe_rsc_params(action->rsc, action->node, data_set); pcmk__substitute_remote_addr(action->rsc, params, data_set); g_hash_table_foreach(params, hash2smartfield, args_xml); #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) { GHashTable *params = pe_rsc_params(action->rsc, NULL, data_set); g_hash_table_foreach(params, 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) { const char *value = g_hash_table_lookup(action->rsc->meta, "external-ip"); pe_resource_t *parent = action->rsc; while (parent != NULL) { parent->cmds->append_meta(parent, args_xml); parent = parent->parent; } if(value) { hash2smartfield((gpointer)"pcmk_external_ip", (gpointer)value, (gpointer)args_xml); } pcmk__add_bundle_meta_to_xml(args_xml, action); } else if (pcmk__str_eq(action->task, CRM_OP_FENCE, pcmk__str_casei) && 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 bool should_dump_action(pe_action_t *action) { CRM_CHECK(action != NULL, return false); if (pcmk_is_set(action->flags, pe_action_dumped)) { crm_trace("Action %s (%d) already dumped", action->uuid, action->id); return false; } else if (pcmk_is_set(action->flags, pe_action_pseudo) && pcmk__str_eq(action->task, CRM_OP_PROBED, pcmk__str_casei)) { GList *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) { pe_action_wrapper_t *wrapper = (pe_action_wrapper_t *) lpc->data; if (!pcmk_is_set(wrapper->action->flags, pe_action_runnable)) { /* Only interested in runnable operations */ } else if (!pcmk__str_eq(wrapper->action->task, RSC_START, pcmk__str_casei)) { /* Only interested in start operations */ } else if (pcmk_is_set(wrapper->action->flags, pe_action_dumped) || should_dump_action(wrapper->action)) { crm_trace("Action %s (%d) should be dumped: " "dependency of %s (%d)", action->uuid, action->id, wrapper->action->uuid, wrapper->action->id); return true; } } } if (!pcmk_is_set(action->flags, pe_action_runnable)) { crm_trace("Ignoring action %s (%d): unrunnable", action->uuid, action->id); return false; } else if (pcmk_is_set(action->flags, pe_action_optional) && !pcmk_is_set(action->flags, pe_action_print_always)) { crm_trace("Ignoring action %s (%d): optional", action->uuid, action->id); return false; // Monitors should be dumped even for unmanaged resources } else if (action->rsc && !pcmk_is_set(action->rsc->flags, pe_rsc_managed) && !pcmk__str_eq(action->task, RSC_STATUS, pcmk__str_casei)) { const char *interval_ms_s = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL_MS); // Cancellation of recurring monitors should still be dumped if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches)) { crm_trace("Ignoring action %s (%d): for unmanaged resource (%s)", action->uuid, action->id, action->rsc->id); return false; } } if (pcmk_is_set(action->flags, pe_action_pseudo) || pcmk__strcase_any_of(action->task, CRM_OP_FENCE, CRM_OP_SHUTDOWN, NULL)) { /* skip the next checks */ return true; } if (action->node == NULL) { pe_err("Skipping action %s (%d) " "because it was not allocated to a node (bug?)", action->uuid, action->id); log_action(LOG_DEBUG, "Unallocated action", action, false); return false; } else if (pcmk_is_set(action->flags, pe_action_dc)) { crm_trace("Action %s (%d) should be dumped: " "can run on DC instead of %s", action->uuid, action->id, action->node->details->uname); } else if (pe__is_guest_node(action->node) && !action->node->details->remote_requires_reset) { crm_trace("Action %s (%d) should be dumped: " "assuming will be runnable on guest node %s", action->uuid, action->id, action->node->details->uname); } else if (action->node->details->online == false) { pe_err("Skipping action %s (%d) " "because it was scheduled for offline node (bug?)", action->uuid, action->id); 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("Skipping action %s (%d) " "because it was scheduled for unclean node (bug?)", action->uuid, action->id); 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 pe_action_wrapper_t *action_wrapper2 = (const pe_action_wrapper_t *)a; const pe_action_wrapper_t *action_wrapper1 = (const pe_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; } /*! * \internal * \brief Check whether an ordering's flags can change an action * * \param[in] ordering Ordering to check * * \return true if ordering has flags that can change an action, false otherwise */ static bool ordering_can_change_actions(pe_action_wrapper_t *ordering) { return pcmk_any_flags_set(ordering->type, ~(pe_order_implies_first_printed |pe_order_implies_then_printed |pe_order_optional)); } /*! * \internal * \brief Check whether an action input should be in the transition graph * * \param[in] action Action to check * \param[in,out] input Action input to check * * \return true if input should be in graph, false otherwise * \note This function may not only check an input, but disable it under certian * circumstances (load or anti-colocation orderings that are not needed). */ static bool check_dump_input(pe_action_t *action, pe_action_wrapper_t *input) { if (input->state == pe_link_dumped) { return true; } if (input->type == pe_order_none) { crm_trace("Ignoring %s (%d) input %s (%d): " "ordering disabled", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (!pcmk_is_set(input->action->flags, pe_action_runnable) && !ordering_can_change_actions(input) && !pcmk__str_eq(input->action->uuid, CRM_OP_PROBED, pcmk__str_casei)) { crm_trace("Ignoring %s (%d) input %s (%d): " "optional and input unrunnable", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (!pcmk_is_set(input->action->flags, pe_action_runnable) && pcmk_is_set(input->type, pe_order_one_or_more)) { crm_trace("Ignoring %s (%d) input %s (%d): " "one-or-more and input unrunnable", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (pcmk_is_set(action->flags, pe_action_pseudo) && pcmk_is_set(input->type, pe_order_stonith_stop)) { crm_trace("Ignoring %s (%d) input %s (%d): " "stonith stop but action is pseudo", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (pcmk_is_set(input->type, pe_order_implies_first_migratable) && !pcmk_is_set(input->action->flags, pe_action_runnable)) { crm_trace("Ignoring %s (%d) input %s (%d): " "implies input migratable but input unrunnable", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (pcmk_is_set(input->type, pe_order_apply_first_non_migratable) && pcmk_is_set(input->action->flags, pe_action_migrate_runnable)) { crm_trace("Ignoring %s (%d) input %s (%d): " "only if input unmigratable but input unrunnable", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if ((input->type == pe_order_optional) && pcmk_is_set(input->action->flags, pe_action_migrate_runnable) && pcmk__ends_with(input->action->uuid, "_stop_0")) { crm_trace("Ignoring %s (%d) input %s (%d): " "optional but stop in migration", action->uuid, action->id, input->action->uuid, input->action->id); return false; } else if (input->type == pe_order_load) { pe_node_t *input_node = input->action->node; // load orderings are relevant only if actions are for same node if (action->rsc && pcmk__str_eq(action->task, RSC_MIGRATE, pcmk__str_casei)) { pe_node_t *allocated = action->rsc->allocated_to; /* For load_stopped -> migrate_to orderings, we care about where it * has been allocated to, not where it will be executed. */ if ((input_node == NULL) || (allocated == NULL) || (input_node->details != allocated->details)) { crm_trace("Ignoring %s (%d) input %s (%d): " "load ordering node mismatch %s vs %s", action->uuid, action->id, input->action->uuid, input->action->id, (allocated? allocated->details->uname : ""), (input_node? input_node->details->uname : "")); input->type = pe_order_none; return false; } } else if ((input_node == NULL) || (action->node == NULL) || (input_node->details != action->node->details)) { crm_trace("Ignoring %s (%d) input %s (%d): " "load ordering node mismatch %s vs %s", action->uuid, action->id, input->action->uuid, input->action->id, (action->node? action->node->details->uname : ""), (input_node? input_node->details->uname : "")); input->type = pe_order_none; return false; } else if (pcmk_is_set(input->action->flags, pe_action_optional)) { crm_trace("Ignoring %s (%d) input %s (%d): " "load ordering input optional", action->uuid, action->id, input->action->uuid, input->action->id); input->type = pe_order_none; return false; } } else if (input->type == pe_order_anti_colocation) { if (input->action->node && action->node && (input->action->node->details != action->node->details)) { crm_trace("Ignoring %s (%d) input %s (%d): " "anti-colocation node mismatch %s vs %s", action->uuid, action->id, input->action->uuid, input->action->id, action->node->details->uname, input->action->node->details->uname); input->type = pe_order_none; return false; } else if (pcmk_is_set(input->action->flags, pe_action_optional)) { crm_trace("Ignoring %s (%d) input %s (%d): " "anti-colocation input optional", action->uuid, action->id, input->action->uuid, input->action->id); input->type = pe_order_none; return false; } } else if (input->action->rsc && input->action->rsc != action->rsc && pcmk_is_set(input->action->rsc->flags, pe_rsc_failed) && !pcmk_is_set(input->action->rsc->flags, pe_rsc_managed) && pcmk__ends_with(input->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", input->action->uuid, action->uuid); return false; } else if (pcmk_is_set(input->action->flags, pe_action_optional) && !pcmk_any_flags_set(input->action->flags, pe_action_print_always|pe_action_dumped) && !should_dump_action(input->action)) { crm_trace("Ignoring %s (%d) input %s (%d): " "input optional", action->uuid, action->id, input->action->uuid, input->action->id); return false; } crm_trace("%s (%d) input %s %s (%d) on %s should be dumped: %s %s 0x%.6x", action->uuid, action->id, action_type_str(input->action->flags), input->action->uuid, input->action->id, action_node_str(input->action), action_runnable_str(input->action->flags), action_optional_str(input->action->flags), input->type); return true; } bool pcmk__graph_has_loop(pe_action_t *init_action, pe_action_t *action, pe_action_wrapper_t *input) { bool has_loop = false; if (pcmk_is_set(input->action->flags, pe_action_tracking)) { crm_trace("Breaking tracking loop: %s@%s -> %s@%s (0x%.6x)", input->action->uuid, input->action->node? input->action->node->details->uname : "", action->uuid, action->node? action->node->details->uname : "", input->type); return false; } // Don't need to check inputs that won't be used if (!check_dump_input(action, input)) { return false; } if (input->action == init_action) { crm_debug("Input loop found in %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; } pe__set_action_flags(input->action, pe_action_tracking); crm_trace("Checking inputs of action %s@%s input %s@%s (0x%.6x)" "for graph loop with %s@%s ", action->uuid, action->node? action->node->details->uname : "", input->action->uuid, input->action->node? input->action->node->details->uname : "", input->type, init_action->uuid, init_action->node? init_action->node->details->uname : ""); // Recursively check input itself for loops for (GList *iter = input->action->actions_before; iter != NULL; iter = iter->next) { if (pcmk__graph_has_loop(init_action, input->action, (pe_action_wrapper_t *) iter->data)) { // Recursive call already logged a debug message has_loop = true; goto done; } } done: pe__clear_action_flags(input->action, pe_action_tracking); if (!has_loop) { crm_trace("No input loop found in %s@%s -> %s@%s (0x%.6x)", input->action->uuid, input->action->node? input->action->node->details->uname : "", action->uuid, action->node? action->node->details->uname : "", input->type); } return has_loop; } // Remove duplicate inputs (regardless of flags) static void deduplicate_inputs(pe_action_t *action) { GList *item = NULL; GList *next = NULL; pe_action_wrapper_t *last_input = NULL; action->actions_before = g_list_sort(action->actions_before, sort_action_id); for (item = action->actions_before; item != NULL; item = next) { pe_action_wrapper_t *input = (pe_action_wrapper_t *) item->data; next = item->next; if (last_input && (input->action->id == last_input->action->id)) { crm_trace("Input %s (%d) duplicate skipped for action %s (%d)", input->action->uuid, input->action->id, action->uuid, action->id); /* For the purposes of scheduling, the ordering flags no longer * matter, but crm_simulate looks at certain ones when creating a * dot graph. Combining the flags is sufficient for that purpose. */ last_input->type |= input->type; if (input->state == pe_link_dumped) { last_input->state = pe_link_dumped; } free(item->data); action->actions_before = g_list_delete_link(action->actions_before, item); } else { last_input = input; input->state = pe_link_not_dumped; } } } /*! * \internal * \brief Add an action to the transition graph XML if appropriate * * \param[in] action Action to possibly add * \param[in] data_set Cluster working set * * \note This will de-duplicate the action inputs, meaning that the * pe_action_wrapper_t:type flags can no longer be relied on to retain * their original settings. That means this MUST be called after * pcmk__apply_orderings() is complete, and nothing after this should rely * on those type flags. (For example, some code looks for type equal to * some flag rather than whether the flag is set, and some code looks for * particular combinations of flags -- such code must be done before * stage8().) */ void graph_element_from_action(pe_action_t *action, pe_working_set_t *data_set) { GList *lpc = NULL; int synapse_priority = 0; xmlNode *syn = NULL; xmlNode *set = NULL; xmlNode *in = NULL; xmlNode *xml_action = NULL; pe_action_wrapper_t *input = NULL; /* If we haven't already, de-duplicate inputs -- even if we won't be dumping * the action, so that crm_simulate dot graphs don't have duplicates. */ if (!pcmk_is_set(action->flags, pe_action_dedup)) { deduplicate_inputs(action); pe__set_action_flags(action, pe_action_dedup); } if (should_dump_action(action) == FALSE) { return; } pe__set_action_flags(action, 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); for (lpc = action->actions_before; lpc != NULL; lpc = lpc->next) { input = (pe_action_wrapper_t *) lpc->data; if (check_dump_input(action, input)) { xmlNode *input_xml = create_xml_node(in, "trigger"); input->state = pe_link_dumped; xml_action = action2xml(input->action, TRUE, data_set); add_node_nocopy(input_xml, crm_element_name(xml_action), xml_action); } } } diff --git a/lib/pacemaker/pcmk_sched_actions.c b/lib/pacemaker/pcmk_sched_actions.c new file mode 100644 index 0000000000..27b6b42f88 --- /dev/null +++ b/lib/pacemaker/pcmk_sched_actions.c @@ -0,0 +1,599 @@ +/* + * Copyright 2004-2021 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 +#include "libpacemaker_private.h" + +static enum pe_action_flags +get_action_flags(pe_action_t *action, pe_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 (!pcmk_is_set(clone_flags, pe_action_runnable) + && pcmk_is_set(flags, pe_action_runnable)) { + + pe__set_raw_action_flags(clone_flags, action->rsc->id, + pe_action_runnable); + } + flags = clone_flags; + } + } + return flags; +} + +static char * +convert_non_atomic_uuid(char *old_uuid, pe_resource_t * rsc, gboolean allow_notify, + gboolean free_original) +{ + guint interval_ms = 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_ms)); + if (interval_ms > 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 (pcmk_is_set(rsc->flags, pe_rsc_notify) && allow_notify) { + uuid = pcmk__notify_key(rid, "confirmed-post", task2text(task + 1)); + + } else { + uuid = pcmk__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 pe_action_t * +rsc_expand_action(pe_action_t * action) +{ + gboolean notify = FALSE; + pe_action_t *result = action; + pe_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 = pcmk_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, + pcmk_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(pe_action_t * first, pe_action_t * then, pe_node_t * node, + enum pe_action_flags first_flags, enum pe_action_flags then_flags, + pe_action_wrapper_t *order, pe_working_set_t *data_set) +{ + enum pe_graph_flags changed = pe_graph_none; + enum pe_ordering type = order->type; + + /* TODO: Do as many of these in parallel as possible */ + + if (pcmk_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' + */ + + pe__clear_order_flags(type, pe_order_implies_then_on_node); + pe__set_order_flags(type, pe_order_implies_then); + node = first->node; + pe_rsc_trace(then->rsc, + "%s then %s: mapped pe_order_implies_then_on_node to " + "pe_order_implies_then on %s", + first->uuid, then->uuid, node->details->uname); + } + + if (type & pe_order_implies_then) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags & pe_action_optional, pe_action_optional, + pe_order_implies_then, data_set); + + } else if (!pcmk_is_set(first_flags, pe_action_optional) + && pcmk_is_set(then->flags, pe_action_optional)) { + pe__clear_action_flags(then, pe_action_optional); + pe__set_graph_flags(changed, first, pe_graph_updated_then); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_then", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if ((type & pe_order_restart) && then->rsc) { + enum pe_action_flags restart = (pe_action_optional | pe_action_runnable); + + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, restart, + pe_order_restart, data_set); + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_restart", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_implies_first) { + if (first->rsc) { + changed |= first->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_optional, pe_order_implies_first, + data_set); + + } else if (!pcmk_is_set(first_flags, pe_action_optional) + && pcmk_is_set(first->flags, pe_action_runnable)) { + pe__clear_action_flags(first, pe_action_runnable); + pe__set_graph_flags(changed, first, pe_graph_updated_first); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_implies_first", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_promoted_implies_first) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags & pe_action_optional, pe_action_optional, + pe_order_promoted_implies_first, data_set); + } + pe_rsc_trace(then->rsc, + "%s then %s: %s after pe_order_promoted_implies_first", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_one_or_more) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_runnable, pe_order_one_or_more, + data_set); + + } else if (pcmk_is_set(first_flags, pe_action_runnable)) { + // We have another runnable instance of "first" + then->runnable_before++; + + /* Mark "then" as runnable if it requires a certain number of + * "before" instances to be runnable, and they now are. + */ + if ((then->runnable_before >= then->required_runnable_before) + && !pcmk_is_set(then->flags, pe_action_runnable)) { + + pe__set_action_flags(then, pe_action_runnable); + pe__set_graph_flags(changed, first, pe_graph_updated_then); + } + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_one_or_more", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (then->rsc && pcmk_is_set(type, pe_order_probe)) { + if (!pcmk_is_set(first_flags, pe_action_runnable) + && (first->rsc->running_on != NULL)) { + + pe_rsc_trace(then->rsc, + "%s then %s: ignoring because first is stopping", + first->uuid, then->uuid); + type = pe_order_none; + order->type = pe_order_none; + + } else { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_runnable, pe_order_runnable_left, + data_set); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_probe", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_runnable_left) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_runnable, pe_order_runnable_left, + data_set); + + } else if (!pcmk_is_set(first_flags, pe_action_runnable) + && pcmk_is_set(then->flags, pe_action_runnable)) { + + pe__clear_action_flags(then, pe_action_runnable); + pe__set_graph_flags(changed, first, pe_graph_updated_then); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_runnable_left", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_implies_first_migratable) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_optional, + pe_order_implies_first_migratable, data_set); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after " + "pe_order_implies_first_migratable", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_pseudo_left) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_optional, pe_order_pseudo_left, + data_set); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_pseudo_left", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_optional) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_runnable, pe_order_optional, data_set); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_optional", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if (type & pe_order_asymmetrical) { + if (then->rsc) { + changed |= then->rsc->cmds->update_actions(first, then, node, + first_flags, pe_action_runnable, pe_order_asymmetrical, + data_set); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after pe_order_asymmetrical", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + if ((first->flags & pe_action_runnable) && (type & pe_order_implies_then_printed) + && (first_flags & pe_action_optional) == 0) { + pe_rsc_trace(then->rsc, "%s will be in graph because %s is required", + then->uuid, first->uuid); + pe__set_action_flags(then, pe_action_print_always); + // Don't bother marking 'then' as changed just for this + } + + if (pcmk_is_set(type, pe_order_implies_first_printed) + && !pcmk_is_set(then_flags, pe_action_optional)) { + + pe_rsc_trace(then->rsc, "%s will be in graph because %s is required", + first->uuid, then->uuid); + pe__set_action_flags(first, pe_action_print_always); + // Don't bother marking 'first' as changed just for this + } + + if ((type & pe_order_implies_then + || type & pe_order_implies_first + || type & pe_order_restart) + && first->rsc + && pcmk__str_eq(first->task, RSC_STOP, pcmk__str_casei) + && !pcmk_is_set(first->rsc->flags, pe_rsc_managed) + && pcmk_is_set(first->rsc->flags, pe_rsc_block) + && !pcmk_is_set(first->flags, pe_action_runnable)) { + + if (pcmk_is_set(then->flags, pe_action_runnable)) { + pe__clear_action_flags(then, pe_action_runnable); + pe__set_graph_flags(changed, first, pe_graph_updated_then); + } + pe_rsc_trace(then->rsc, "%s then %s: %s after checking whether first " + "is blocked, unmanaged, unrunnable stop", + first->uuid, then->uuid, + (changed? "changed" : "unchanged")); + } + + return changed; +} + +// Convenience macros for logging action properties + +#define action_type_str(flags) \ + (pcmk_is_set((flags), pe_action_pseudo)? "pseudo-action" : "action") + +#define action_optional_str(flags) \ + (pcmk_is_set((flags), pe_action_optional)? "optional" : "required") + +#define action_runnable_str(flags) \ + (pcmk_is_set((flags), pe_action_runnable)? "runnable" : "unrunnable") + +#define action_node_str(a) \ + (((a)->node == NULL)? "no node" : (a)->node->details->uname) + +gboolean +update_action(pe_action_t *then, pe_working_set_t *data_set) +{ + GList *lpc = NULL; + enum pe_graph_flags changed = pe_graph_none; + int last_flags = then->flags; + + pe_rsc_trace(then->rsc, "Updating %s %s (%s %s) on %s", + action_type_str(then->flags), then->uuid, + action_optional_str(then->flags), + action_runnable_str(then->flags), action_node_str(then)); + + if (pcmk_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_flags(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) { + pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data; + pe_action_t *first = other->action; + + pe_node_t *then_node = then->node; + pe_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 && pcmk__str_eq(first->task, RSC_START, pcmk__str_casei)) { + first_node = first->rsc->fns->location(first->rsc, NULL, FALSE); + if (first_node) { + pe_rsc_trace(first->rsc, "Found node %s for 'first' %s", + first_node->details->uname, first->uuid); + } + } + + if (then->rsc && then->rsc->variant == pe_group && pcmk__str_eq(then->task, RSC_START, pcmk__str_casei)) { + then_node = then->rsc->fns->location(then->rsc, NULL, FALSE); + if (then_node) { + pe_rsc_trace(then->rsc, "Found node %s for 'then' %s", + then_node->details->uname, then->uuid); + } + } + /* Disable constraint if it only applies when on same node, but isn't */ + if (pcmk_is_set(other->type, pe_order_same_node) + && (first_node != NULL) && (then_node != NULL) + && (first_node->details != then_node->details)) { + + pe_rsc_trace(then->rsc, + "Disabled ordering %s on %s then %s on %s: not same node", + other->action->uuid, first_node->details->uname, + then->uuid, then_node->details->uname); + other->type = pe_order_none; + continue; + } + + pe__clear_graph_flags(changed, then, pe_graph_updated_first); + + if (first->rsc && pcmk_is_set(other->type, pe_order_then_cancels_first) + && !pcmk_is_set(then->flags, pe_action_optional)) { + + /* 'then' is required, so we must abandon 'first' + * (e.g. a required stop cancels any agent reload). + */ + pe__set_action_flags(other->action, pe_action_optional); + if (!strcmp(first->task, CRMD_ACTION_RELOAD_AGENT)) { + pe__clear_resource_flags(first->rsc, 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) { + pe_rsc_trace(then->rsc, "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); + + pe_rsc_trace(then->rsc, + "%s then %s: type=0x%.6x filter=0x%.6x " + "(%s %s %s on %s 0x%.6x then 0x%.6x)", + first->uuid, then->uuid, other->type, first_flags, + action_optional_str(first_flags), + action_runnable_str(first_flags), + action_type_str(first_flags), action_node_str(first), + first->flags, then->flags); + + 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 + * + */ + pe_node_t *node = then->node; + changed |= graph_update_action(first, then, node, first_flags, + then_flags, other, data_set); + + /* '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 + */ + pe__set_graph_flags(changed, then, + pe_graph_updated_then|pe_graph_disable); + } + + if (changed & pe_graph_disable) { + pe_rsc_trace(then->rsc, + "Disabled ordering %s then %s in favor of %s then %s", + other->action->uuid, then->uuid, first->uuid, + then->uuid); + pe__clear_graph_flags(changed, then, pe_graph_disable); + other->type = pe_order_none; + } + + if (changed & pe_graph_updated_first) { + GList *lpc2 = NULL; + + crm_trace("Re-processing %s and its 'after' actions since it changed", + first->uuid); + for (lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) { + pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc2->data; + + update_action(other->action, data_set); + } + update_action(first, data_set); + } + } + + if (pcmk_is_set(then->flags, pe_action_requires_any)) { + if (last_flags != then->flags) { + pe__set_graph_flags(changed, then, pe_graph_updated_then); + } else { + pe__clear_graph_flags(changed, then, pe_graph_updated_then); + } + } + + if (changed & pe_graph_updated_then) { + crm_trace("Re-processing %s and its 'after' actions since it changed", + then->uuid); + if (pcmk_is_set(last_flags, pe_action_runnable) + && !pcmk_is_set(then->flags, pe_action_runnable)) { + pcmk__block_colocated_starts(then, data_set); + } + update_action(then, data_set); + for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) { + pe_action_wrapper_t *other = (pe_action_wrapper_t *) lpc->data; + + update_action(other->action, data_set); + } + } + + return FALSE; +}