diff --git a/include/pacemaker-internal.h b/include/pacemaker-internal.h index 020fce9de6..33aa396cb7 100644 --- a/include/pacemaker-internal.h +++ b/include/pacemaker-internal.h @@ -1,26 +1,25 @@ /* * Copyright 2019-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PACEMAKER_INTERNAL__H # define PACEMAKER_INTERNAL__H # include # include # include # include # include # include # include -# include # include # include # include # include #endif diff --git a/include/pcmki/Makefile.am b/include/pcmki/Makefile.am index a95cf0c134..117739a97f 100644 --- a/include/pcmki/Makefile.am +++ b/include/pcmki/Makefile.am @@ -1,25 +1,24 @@ # # Copyright 2019-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. # MAINTAINERCLEANFILES = Makefile.in noinst_HEADERS = pcmki_acl.h \ pcmki_error.h \ pcmki_cluster_queries.h \ pcmki_fence.h \ pcmki_output.h \ pcmki_resource.h \ pcmki_sched_allocate.h \ - pcmki_sched_notif.h \ pcmki_sched_utils.h \ pcmki_scheduler.h \ pcmki_simulate.h \ pcmki_transition.h .PHONY: $(ARCHIVE_VERSION) diff --git a/include/pcmki/pcmki_sched_notif.h b/include/pcmki/pcmki_sched_notif.h deleted file mode 100644 index 1adcdad7f9..0000000000 --- a/include/pcmki/pcmki_sched_notif.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 Lesser General Public License - * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. - */ -#ifndef PCMK__PCMKI_PCMKI_SCHED_NOTIF__H -# define PCMK__PCMKI_PCMKI_SCHED_NOTIF__H - -# include -# include - -void create_secondary_notification(pe_action_t *action, pe_resource_t *rsc, - pe_action_t *stonith_op, - pe_working_set_t *data_set); - -#endif /* PCMK__PCMKI_SCHED_NOTIF__H */ diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h index 2e7e14b94c..28b3837465 100644 --- a/lib/pacemaker/libpacemaker_private.h +++ b/lib/pacemaker/libpacemaker_private.h @@ -1,346 +1,351 @@ /* - * Copyright 2021 the Pacemaker project contributors + * Copyright 2021-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__LIBPACEMAKER_PRIVATE__H # define PCMK__LIBPACEMAKER_PRIVATE__H /* This header is for the sole use of libpacemaker, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #include // pe_action_t, pe_node_t, pe_working_set_t // Actions G_GNUC_INTERNAL void pcmk__update_action_for_orderings(pe_action_t *action, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__log_action(const char *pre_text, pe_action_t *action, bool details); G_GNUC_INTERNAL pe_action_t *pcmk__new_rsc_pseudo_action(pe_resource_t *rsc, const char *task, bool optional, bool runnable); G_GNUC_INTERNAL pe_action_t *pcmk__new_cancel_action(pe_resource_t *rsc, const char *name, guint interval_ms, pe_node_t *node); G_GNUC_INTERNAL pe_action_t *pcmk__new_shutdown_action(pe_node_t *node, pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__action_locks_rsc_to_node(const pe_action_t *action); G_GNUC_INTERNAL void pcmk__deduplicate_action_inputs(pe_action_t *action); G_GNUC_INTERNAL void pcmk__output_actions(pe_working_set_t *data_set); // Producing transition graphs (pcmk_graph_producer.c) G_GNUC_INTERNAL bool pcmk__graph_has_loop(pe_action_t *init_action, pe_action_t *action, pe_action_wrapper_t *input); G_GNUC_INTERNAL void pcmk__add_action_to_graph(pe_action_t *action, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__create_graph(pe_working_set_t *data_set); // Fencing (pcmk_sched_fencing.c) G_GNUC_INTERNAL void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_vs_unfence(pe_resource_t *rsc, pe_node_t *node, pe_action_t *action, enum pe_ordering order, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__fence_guest(pe_node_t *node, pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__node_unfenced(pe_node_t *node); G_GNUC_INTERNAL bool pcmk__is_unfence_device(const pe_resource_t *rsc, const pe_working_set_t *data_set); // Injected scheduler inputs (pcmk_sched_injections.c) void pcmk__inject_scheduler_input(pe_working_set_t *data_set, cib_t *cib, pcmk_injections_t *injections); // Constraints of any type (pcmk_sched_constraints.c) G_GNUC_INTERNAL pe_resource_t *pcmk__find_constraint_resource(GList *rsc_list, const char *id); G_GNUC_INTERNAL xmlNode *pcmk__expand_tags_in_sets(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__valid_resource_or_tag(pe_working_set_t *data_set, const char *id, pe_resource_t **rsc, pe_tag_t **tag); G_GNUC_INTERNAL bool pcmk__tag_to_set(xmlNode *xml_obj, xmlNode **rsc_set, const char *attr, bool convert_rsc, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__create_internal_constraints(pe_working_set_t *data_set); // Location constraints G_GNUC_INTERNAL void pcmk__unpack_location(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL pe__location_t *pcmk__new_location(const char *id, pe_resource_t *rsc, int node_weight, const char *discover_mode, pe_node_t *foo_node, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__apply_locations(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__apply_location(pe__location_t *constraint, pe_resource_t *rsc); // Colocation constraints enum pcmk__coloc_affects { pcmk__coloc_affects_nothing = 0, pcmk__coloc_affects_location, pcmk__coloc_affects_role, }; G_GNUC_INTERNAL enum pcmk__coloc_affects pcmk__colocation_affects(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *constraint, bool preview); G_GNUC_INTERNAL void pcmk__apply_coloc_to_weights(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *constraint); G_GNUC_INTERNAL void pcmk__apply_coloc_to_priority(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *constraint); G_GNUC_INTERNAL void pcmk__unpack_colocation(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__new_colocation(const char *id, const char *node_attr, int score, pe_resource_t *dependent, pe_resource_t *primary, const char *dependent_role, const char *primary_role, bool influence, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__block_colocated_starts(pe_action_t *action, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__new_ordering(pe_resource_t *lh_rsc, char *lh_task, pe_action_t *lh_action, pe_resource_t *rh_rsc, char *rh_task, pe_action_t *rh_action, enum pe_ordering type, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__unpack_ordering(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__disable_invalid_orderings(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_stops_before_shutdown(pe_node_t *node, pe_action_t *shutdown_op, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__apply_orderings(pe_working_set_t *data_set); /*! * \internal * \brief Create a new ordering between two resource actions * * \param[in] lh_rsc Resource for 'first' action * \param[in] rh_rsc Resource for 'then' action * \param[in] lh_task Action key for 'first' action * \param[in] rh_task Action key for 'then' action * \param[in] flags Bitmask of enum pe_ordering flags * \param[in] data_set Cluster working set to add ordering to */ #define pcmk__order_resource_actions(lh_rsc, lh_task, rh_rsc, rh_task, \ flags, data_set) \ pcmk__new_ordering((lh_rsc), pcmk__op_key((lh_rsc)->id, (lh_task), 0), \ NULL, \ (rh_rsc), pcmk__op_key((rh_rsc)->id, (rh_task), 0), \ NULL, (flags), (data_set)) #define pcmk__order_starts(rsc1, rsc2, type, data_set) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \ (rsc2), CRMD_ACTION_START, (type), (data_set)) #define pcmk__order_stops(rsc1, rsc2, type, data_set) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \ (rsc2), CRMD_ACTION_STOP, (type), (data_set)) G_GNUC_INTERNAL void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__is_failed_remote_node(pe_node_t *node); G_GNUC_INTERNAL void pcmk__order_remote_connection_actions(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__rsc_corresponds_to_guest(pe_resource_t *rsc, pe_node_t *node); G_GNUC_INTERNAL pe_node_t *pcmk__connection_host_for_action(pe_action_t *action); G_GNUC_INTERNAL void pcmk__substitute_remote_addr(pe_resource_t *rsc, GHashTable *params, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action); // Groups (pcmk_sched_group.c) G_GNUC_INTERNAL GList *pcmk__group_colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *colocated_rscs); // Bundles (pcmk_sched_bundle.c) G_GNUC_INTERNAL void pcmk__output_bundle_actions(pe_resource_t *rsc); // Injections (pcmk_injections.c) G_GNUC_INTERNAL xmlNode *pcmk__inject_node(cib_t *cib_conn, const char *node, const char *uuid); G_GNUC_INTERNAL xmlNode *pcmk__inject_node_state_change(cib_t *cib_conn, const char *node, bool up); G_GNUC_INTERNAL xmlNode *pcmk__inject_resource_history(pcmk__output_t *out, xmlNode *cib_node, const char *resource, const char *lrm_name, const char *rclass, const char *rtype, const char *rprovider); G_GNUC_INTERNAL void pcmk__inject_failcount(pcmk__output_t *out, xmlNode *cib_node, const char *resource, const char *task, guint interval_ms, int rc); G_GNUC_INTERNAL xmlNode *pcmk__inject_action_result(xmlNode *cib_resource, lrmd_event_data_t *op, int target_rc); // Clone notifictions (pcmk_sched_notif.c) G_GNUC_INTERNAL void pcmk__create_notifications(pe_resource_t *rsc, notify_data_t *n_data); G_GNUC_INTERNAL notify_data_t *pcmk__clone_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__free_notification_data(notify_data_t *n_data); +G_GNUC_INTERNAL +void pcmk__order_notifs_after_fencing(pe_action_t *action, pe_resource_t *rsc, + pe_action_t *stonith_op, + pe_working_set_t *data_set); + // Functions applying to more than one variant (pcmk_sched_resource.c) G_GNUC_INTERNAL GList *pcmk__colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *colocated_rscs); G_GNUC_INTERNAL void pcmk__output_resource_actions(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__assign_primitive(pe_resource_t *rsc, pe_node_t *chosen, bool force); G_GNUC_INTERNAL bool pcmk__assign_resource(pe_resource_t *rsc, pe_node_t *node, bool force); G_GNUC_INTERNAL void pcmk__unassign_resource(pe_resource_t *rsc); G_GNUC_INTERNAL bool pcmk__threshold_reached(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set, pe_resource_t **failed); // Functions related to probes (pcmk_sched_probes.c) G_GNUC_INTERNAL void pcmk__order_probes(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__schedule_probes(pe_working_set_t *data_set); // Functions related to node utilization (pcmk_sched_utilization.c) G_GNUC_INTERNAL int pcmk__compare_node_capacities(const pe_node_t *node1, const pe_node_t *node2); G_GNUC_INTERNAL void pcmk__consume_node_capacity(GHashTable *current_utilization, pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__release_node_capacity(GHashTable *current_utilization, pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__ban_insufficient_capacity(pe_resource_t *rsc, pe_node_t **prefer, pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__create_utilization_constraints(pe_resource_t *rsc, GList *allowed_nodes); #endif // PCMK__LIBPACEMAKER_PRIVATE__H diff --git a/lib/pacemaker/pcmk_sched_fencing.c b/lib/pacemaker/pcmk_sched_fencing.c index bb34cb3975..b973f3a964 100644 --- a/lib/pacemaker/pcmk_sched_fencing.c +++ b/lib/pacemaker/pcmk_sched_fencing.c @@ -1,487 +1,467 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 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" /*! * \internal * \brief Check whether a resource is known on a particular node * * \param[in] rsc Resource to check * \param[in] node Node to check * * \return TRUE if resource (or parent if an anonymous clone) is known */ static bool rsc_is_known_on(pe_resource_t *rsc, const pe_node_t *node) { if (pe_hash_table_lookup(rsc->known_on, node->details->id)) { return TRUE; } else if ((rsc->variant == pe_native) && pe_rsc_is_anon_clone(rsc->parent) && pe_hash_table_lookup(rsc->parent->known_on, node->details->id)) { /* We check only the parent, not the uber-parent, because we cannot * assume that the resource is known if it is in an anonymously cloned * group (which may be only partially known). */ return TRUE; } return FALSE; } /*! * \internal * \brief Order a resource's start and promote actions relative to fencing * * \param[in] rsc Resource to be ordered * \param[in] stonith_op Fence action * \param[in] data_set Cluster working set */ static void order_start_vs_fencing(pe_resource_t *rsc, pe_action_t *stonith_op, pe_working_set_t *data_set) { pe_node_t *target; GList *gIter = NULL; CRM_CHECK(stonith_op && stonith_op->node, return); target = stonith_op->node; for (gIter = rsc->actions; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; switch (action->needs) { case rsc_req_nothing: // Anything other than start or promote requires nothing break; case rsc_req_stonith: order_actions(stonith_op, action, pe_order_optional); break; case rsc_req_quorum: if (pcmk__str_eq(action->task, RSC_START, pcmk__str_casei) && pe_hash_table_lookup(rsc->allowed_nodes, target->details->id) && !rsc_is_known_on(rsc, target)) { /* If we don't know the status of the resource on the node * we're about to shoot, we have to assume it may be active * there. Order the resource start after the fencing. This * is analogous to waiting for all the probes for a resource * to complete before starting it. * * The most likely explanation is that the DC died and took * its status with it. */ pe_rsc_debug(rsc, "Ordering %s after %s recovery", action->uuid, target->details->uname); order_actions(stonith_op, action, pe_order_optional | pe_order_runnable_left); } break; } } } /*! * \internal * \brief Order a resource's stop and demote actions relative to fencing * * \param[in] rsc Resource to be ordered * \param[in] stonith_op Fence action * \param[in] data_set Cluster working set */ static void order_stop_vs_fencing(pe_resource_t *rsc, pe_action_t *stonith_op, pe_working_set_t *data_set) { GList *gIter = NULL; GList *action_list = NULL; bool order_implicit = false; pe_resource_t *top = uber_parent(rsc); pe_action_t *parent_stop = NULL; pe_node_t *target; CRM_CHECK(stonith_op && stonith_op->node, return); target = stonith_op->node; /* Get a list of stop actions potentially implied by the fencing */ action_list = pe__resource_actions(rsc, target, RSC_STOP, FALSE); /* If resource requires fencing, implicit actions must occur after fencing. * * Implied stops and demotes of resources running on guest nodes are always * ordered after fencing, even if the resource does not require fencing, * because guest node "fencing" is actually just a resource stop. */ if (pcmk_is_set(rsc->flags, pe_rsc_needs_fencing) || pe__is_guest_node(target)) { order_implicit = true; } if (action_list && order_implicit) { parent_stop = find_first_action(top->actions, NULL, RSC_STOP, NULL); } for (gIter = action_list; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; // The stop would never complete, so convert it into a pseudo-action. pe__set_action_flags(action, pe_action_pseudo|pe_action_runnable); if (order_implicit) { pe__set_action_flags(action, pe_action_implied_by_stonith); /* Order the stonith before the parent stop (if any). * * Also order the stonith before the resource stop, unless the * resource is inside a bundle -- that would cause a graph loop. * We can rely on the parent stop's ordering instead. * * User constraints must not order a resource in a guest node * relative to the guest node container resource. The * pe_order_preserve flag marks constraints as generated by the * cluster and thus immune to that check (and is irrelevant if * target is not a guest). */ if (!pe_rsc_is_bundled(rsc)) { order_actions(stonith_op, action, pe_order_preserve); } order_actions(stonith_op, parent_stop, pe_order_preserve); } if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { crm_notice("Stop of failed resource %s is implicit %s %s is fenced", rsc->id, (order_implicit? "after" : "because"), target->details->uname); } else { crm_info("%s is implicit %s %s is fenced", action->uuid, (order_implicit? "after" : "because"), target->details->uname); } if (pcmk_is_set(rsc->flags, pe_rsc_notify)) { - /* Create a second notification that will be delivered - * immediately after the node is fenced - * - * Basic problem: - * - C is a clone active on the node to be shot and stopping on another - * - R is a resource that depends on C - * - * + C.stop depends on R.stop - * + C.stopped depends on STONITH - * + C.notify depends on C.stopped - * + C.healthy depends on C.notify - * + R.stop depends on C.healthy - * - * The extra notification here changes - * + C.healthy depends on C.notify - * into: - * + C.healthy depends on C.notify' - * + C.notify' depends on STONITH' - * thus breaking the loop - */ - create_secondary_notification(action, rsc, stonith_op, data_set); + pcmk__order_notifs_after_fencing(action, rsc, stonith_op, data_set); } #if 0 /* It might be a good idea to stop healthy resources on a node about to * be fenced, when possible. * * However, fencing must be done before a failed resource's * (pseudo-)stop action, so that could create a loop. For example, given * a group of A and B running on node N with a failed stop of B: * * fence N -> stop B (pseudo-op) -> stop A -> fence N * * The block below creates the stop A -> fence N ordering and therefore * must (at least for now) be disabled. Instead, run the block above and * treat all resources on N as B would be (i.e., as a pseudo-op after * the fencing). * * @TODO Maybe break the "A requires B" dependency in * pcmk__update_action_for_orderings() and use this block for healthy * resources instead of the above. */ crm_info("Moving healthy resource %s off %s before fencing", rsc->id, node->details->uname); pcmk__new_ordering(rsc, stop_key(rsc), NULL, NULL, strdup(CRM_OP_FENCE), stonith_op, pe_order_optional, data_set); #endif } g_list_free(action_list); /* Get a list of demote actions potentially implied by the fencing */ action_list = pe__resource_actions(rsc, target, RSC_DEMOTE, FALSE); for (gIter = action_list; gIter != NULL; gIter = gIter->next) { pe_action_t *action = (pe_action_t *) gIter->data; if (!(action->node->details->online) || action->node->details->unclean || pcmk_is_set(rsc->flags, pe_rsc_failed)) { if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { pe_rsc_info(rsc, "Demote of failed resource %s is implicit after %s is fenced", rsc->id, target->details->uname); } else { pe_rsc_info(rsc, "%s is implicit after %s is fenced", action->uuid, target->details->uname); } /* The demote would never complete and is now implied by the * fencing, so convert it into a pseudo-action. */ pe__set_action_flags(action, pe_action_pseudo|pe_action_runnable); if (pe_rsc_is_bundled(rsc)) { // Do nothing, let recovery be ordered after parent's implied stop } else if (order_implicit) { order_actions(stonith_op, action, pe_order_preserve|pe_order_optional); } } } g_list_free(action_list); } /*! * \internal * \brief Order resource actions properly relative to fencing * * \param[in] rsc Resource whose actions should be ordered * \param[in] stonith_op Fencing operation to be ordered against * \param[in] data_set Cluster working set */ static void rsc_stonith_ordering(pe_resource_t *rsc, pe_action_t *stonith_op, pe_working_set_t *data_set) { if (rsc->children) { GList *gIter = NULL; for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; rsc_stonith_ordering(child_rsc, stonith_op, data_set); } } else if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_rsc_trace(rsc, "Skipping fencing constraints for unmanaged resource: %s", rsc->id); } else { order_start_vs_fencing(rsc, stonith_op, data_set); order_stop_vs_fencing(rsc, stonith_op, data_set); } } /*! * \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] stonith_op Fencing operation * \param[in,out] data_set Working set of cluster */ void pcmk__order_vs_fence(pe_action_t *stonith_op, pe_working_set_t *data_set) { CRM_CHECK(stonith_op && data_set, return); for (GList *r = data_set->resources; r != NULL; r = r->next) { rsc_stonith_ordering((pe_resource_t *) r->data, stonith_op, data_set); } } /*! * \internal * \brief Order an action after unfencing * * \param[in] rsc Resource that action is for * \param[in] node Node that action is on * \param[in] action Action to be ordered after unfencing * \param[in] order Ordering flags * \param[in] data_set Cluster working set */ void pcmk__order_vs_unfence(pe_resource_t *rsc, pe_node_t *node, pe_action_t *action, enum pe_ordering order, pe_working_set_t *data_set) { /* When unfencing is in use, we order unfence actions before any probe or * start of resources that require unfencing, and also of fence devices. * * This might seem to violate the principle that fence devices require * only quorum. However, fence agents that unfence often don't have enough * information to even probe or start unless the node is first unfenced. */ if (pcmk__is_unfence_device(rsc, data_set) || pcmk_is_set(rsc->flags, pe_rsc_needs_unfencing)) { /* Start with an optional ordering. Requiring unfencing would result in * the node being unfenced, and all its resources being stopped, * whenever a new resource is added -- which would be highly suboptimal. */ pe_action_t *unfence = pe_fence_op(node, "on", TRUE, NULL, FALSE, data_set); order_actions(unfence, action, order); if (!pcmk__node_unfenced(node)) { // But unfencing is required if it has never been done char *reason = crm_strdup_printf("required by %s %s", rsc->id, action->task); trigger_unfencing(NULL, node, reason, NULL, data_set); free(reason); } } } /*! * \internal * \brief Create pseudo-op for guest node fence, and order relative to it * * \param[in] node Guest node to fence * \param[in] data_set Working set of CIB state */ void pcmk__fence_guest(pe_node_t *node, pe_working_set_t *data_set) { pe_resource_t *container = node->details->remote_rsc->container; pe_action_t *stop = NULL; pe_action_t *stonith_op = NULL; /* The fence action is just a label; we don't do anything differently for * off vs. reboot. We specify it explicitly, rather than let it default to * cluster's default action, because we are not _initiating_ fencing -- we * are creating a pseudo-event to describe fencing that is already occurring * by other means (container recovery). */ const char *fence_action = "off"; /* Check whether guest's container resource has any explicit stop or * start (the stop may be implied by fencing of the guest's host). */ if (container) { stop = find_first_action(container->actions, NULL, CRMD_ACTION_STOP, NULL); if (find_first_action(container->actions, NULL, CRMD_ACTION_START, NULL)) { fence_action = "reboot"; } } /* Create a fence pseudo-event, so we have an event to order actions * against, and the controller can always detect it. */ stonith_op = pe_fence_op(node, fence_action, FALSE, "guest is unclean", FALSE, data_set); pe__set_action_flags(stonith_op, pe_action_pseudo|pe_action_runnable); /* We want to imply stops/demotes after the guest is stopped, not wait until * it is restarted, so we always order pseudo-fencing after stop, not start * (even though start might be closer to what is done for a real reboot). */ if ((stop != NULL) && pcmk_is_set(stop->flags, pe_action_pseudo)) { pe_action_t *parent_stonith_op = pe_fence_op(stop->node, NULL, FALSE, NULL, FALSE, data_set); crm_info("Implying guest node %s is down (action %d) after %s fencing", node->details->uname, stonith_op->id, stop->node->details->uname); order_actions(parent_stonith_op, stonith_op, pe_order_runnable_left|pe_order_implies_then); } else if (stop) { order_actions(stop, stonith_op, pe_order_runnable_left|pe_order_implies_then); crm_info("Implying guest node %s is down (action %d) " "after container %s is stopped (action %d)", node->details->uname, stonith_op->id, container->id, stop->id); } else { /* If we're fencing the guest node but there's no stop for the guest * resource, we must think the guest is already stopped. However, we may * think so because its resource history was just cleaned. To avoid * unnecessarily considering the guest node down if it's really up, * order the pseudo-fencing after any stop of the connection resource, * which will be ordered after any container (re-)probe. */ stop = find_first_action(node->details->remote_rsc->actions, NULL, RSC_STOP, NULL); if (stop) { order_actions(stop, stonith_op, pe_order_optional); crm_info("Implying guest node %s is down (action %d) " "after connection is stopped (action %d)", node->details->uname, stonith_op->id, stop->id); } else { /* Not sure why we're fencing, but everything must already be * cleanly stopped. */ crm_info("Implying guest node %s is down (action %d) ", node->details->uname, stonith_op->id); } } // Order/imply other actions relative to pseudo-fence as with real fence pcmk__order_vs_fence(stonith_op, data_set); } /*! * \internal * \brief Check whether node has already been unfenced * * \param[in] node Node to check * * \return true if node has a nonzero #node-unfenced attribute (or none), * otherwise false */ bool pcmk__node_unfenced(pe_node_t *node) { const char *unfenced = pe_node_attribute_raw(node, CRM_ATTR_UNFENCED); return !pcmk__str_eq(unfenced, "0", pcmk__str_null_matches); } /*! * \internal * \brief Check whether a resource is a fencing device that supports unfencing * * \param[in] rsc Resource to check * \param[in] data_set Cluster working set * * \return true if \p rsc is a fencing device that supports unfencing, * otherwise false */ bool pcmk__is_unfence_device(const pe_resource_t *rsc, const pe_working_set_t *data_set) { return pcmk_is_set(rsc->flags, pe_rsc_fence_device) && pcmk_is_set(data_set->flags, pe_flag_enable_unfencing); } diff --git a/lib/pacemaker/pcmk_sched_notif.c b/lib/pacemaker/pcmk_sched_notif.c index 0f3a9359a3..5a5c681532 100644 --- a/lib/pacemaker/pcmk_sched_notif.c +++ b/lib/pacemaker/pcmk_sched_notif.c @@ -1,984 +1,999 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 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 "libpacemaker_private.h" typedef struct notify_entry_s { pe_resource_t *rsc; pe_node_t *node; } notify_entry_t; /*! * \internal * \brief Compare two notification entries * * Compare two notification entries, where the one with the alphabetically first * resource name (or if equal, node name) sorts as first, with NULL sorting as * less than non-NULL. * * \param[in] a First notification entry to compare * \param[in] b Second notification entry to compare * * \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1 */ static gint compare_notify_entries(gconstpointer a, gconstpointer b) { int tmp; const notify_entry_t *entry_a = a; const notify_entry_t *entry_b = b; // NULL a or b is not actually possible if ((entry_a == NULL) && (entry_b == NULL)) { return 0; } if (entry_a == NULL) { return 1; } if (entry_b == NULL) { return -1; } // NULL resources sort first if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) { return 0; } if (entry_a->rsc == NULL) { return 1; } if (entry_b->rsc == NULL) { return -1; } // Compare resource names tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id); if (tmp != 0) { return tmp; } // Otherwise NULL nodes sort first if ((entry_a->node == NULL) && (entry_b->node == NULL)) { return 0; } if (entry_a->node == NULL) { return 1; } if (entry_b->node == NULL) { return -1; } // Finally, compare node names return strcmp(entry_a->node->details->id, entry_b->node->details->id); } /*! * \internal * \brief Duplicate a notification entry * * \param[in] entry Entry to duplicate * * \return Newly allocated duplicate of \p entry * \note It is the caller's responsibility to free the return value. */ static notify_entry_t * dup_notify_entry(notify_entry_t *entry) { notify_entry_t *dup = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(dup != NULL); dup->rsc = entry->rsc; dup->node = entry->node; return dup; } /*! * \internal * \brief Given a list of nodes, create strings with node names * * \param[in] list List of nodes (as pe_node_t *) * \param[out] all_node_names If not NULL, will be set to space-separated list * of the names of all nodes in \p list * \param[out] host_node_names Same as \p all_node_names, except active * guest nodes will list the name of their host * * \note The caller is responsible for freeing the output arguments. */ static void get_node_names(GList *list, char **all_node_names, char **host_node_names) { size_t all_len = 0; size_t host_len = 0; if (all_node_names != NULL) { *all_node_names = NULL; } if (host_node_names != NULL) { *host_node_names = NULL; } for (GList *iter = list; iter != NULL; iter = iter->next) { pe_node_t *node = (pe_node_t *) iter->data; if (node->details->uname == NULL) { continue; } // Always add to list of all node names if (all_node_names != NULL) { pcmk__add_word(all_node_names, &all_len, node->details->uname); } // Add to host node name list if appropriate if (host_node_names != NULL) { if (pe__is_guest_node(node) && (node->details->remote_rsc->container->running_on != NULL)) { node = pe__current_node(node->details->remote_rsc->container); if (node->details->uname == NULL) { continue; } } pcmk__add_word(host_node_names, &host_len, node->details->uname); } } if ((all_node_names != NULL) && (*all_node_names == NULL)) { *all_node_names = strdup(" "); CRM_ASSERT(*all_node_names != NULL); } if ((host_node_names != NULL) && (*host_node_names == NULL)) { *host_node_names = strdup(" "); CRM_ASSERT(*host_node_names != NULL); } } /*! * \internal * \brief Create strings of instance and node names from notification entries * * \param[in,out] list List of notification entries (will be sorted here) * \param[out] rsc_names If not NULL, will be set to space-separated list * of clone instances from \p list * \param[out] node_names If not NULL, will be set to space-separated list * of node names from \p list * * \return (Possibly new) head of sorted \p list * \note The caller is responsible for freeing the output argument values. */ static GList * notify_entries_to_strings(GList *list, char **rsc_names, char **node_names) { const char *last_rsc_id = NULL; size_t rsc_names_len = 0; size_t node_names_len = 0; // Initialize output lists to NULL if (rsc_names != NULL) { *rsc_names = NULL; } if (node_names != NULL) { *node_names = NULL; } // Sort input list for user-friendliness (and ease of filtering duplicates) list = g_list_sort(list, compare_notify_entries); for (GList *gIter = list; gIter != NULL; gIter = gIter->next) { notify_entry_t *entry = (notify_entry_t *) gIter->data; // Entry must have a resource (with ID) CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL) && (entry->rsc->id != NULL)); if ((entry == NULL) || (entry->rsc == NULL) || (entry->rsc->id == NULL)) { continue; } // Entry must have a node unless listing inactive resources CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL)); if ((node_names != NULL) && (entry->node == NULL)) { continue; } // Don't add duplicates of a particular clone instance if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) { continue; } last_rsc_id = entry->rsc->id; if (rsc_names != NULL) { pcmk__add_word(rsc_names, &rsc_names_len, entry->rsc->id); } if ((node_names != NULL) && (entry->node->details->uname != NULL)) { pcmk__add_word(node_names, &node_names_len, entry->node->details->uname); } } // If there are no entries, return "empty" lists if ((rsc_names != NULL) && (*rsc_names == NULL)) { *rsc_names = strdup(" "); CRM_ASSERT(*rsc_names != NULL); } if ((node_names != NULL) && (*node_names == NULL)) { *node_names = strdup(" "); CRM_ASSERT(*node_names != NULL); } return list; } /*! * \internal * \brief Copy a meta-attribute into a notify action * * \param[in] key Name of meta-attribute to copy * \param[in] value Value of meta-attribute to copy * \param[in] user_data Notify action to copy into */ static void copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data) { pe_action_t *notify = (pe_action_t *) user_data; /* Any existing meta-attributes (for example, the action timeout) are for * the notify action itself, so don't override those. */ if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) { return; } g_hash_table_insert(notify->meta, strdup((const char *) key), strdup((const char *) value)); } static void add_notify_data_to_action_meta(notify_data_t *n_data, pe_action_t *action) { for (GSList *item = n_data->keys; item; item = item->next) { pcmk_nvpair_t *nvpair = item->data; add_hash_param(action->meta, nvpair->name, nvpair->value); } } /*! * \internal * \brief Create a new notify pseudo-action for a clone resource * * \param[in] rsc Clone resource that notification is for * \param[in] action Action to use in notify action key * \param[in] notif_action RSC_NOTIFY or RSC_NOTIFIED * \param[in] notif_type "pre", "post", "confirmed-pre", or "confirmed-post" * * \return Newly created notify pseudo-action */ static pe_action_t * new_notify_pseudo_action(pe_resource_t *rsc, const pe_action_t *action, const char *notif_action, const char *notif_type) { pe_action_t *notify = NULL; notify = custom_action(rsc, pcmk__notify_key(rsc->id, notif_type, action->task), notif_action, NULL, pcmk_is_set(action->flags, pe_action_optional), TRUE, rsc->cluster); pe__set_action_flags(notify, pe_action_pseudo); add_hash_param(notify->meta, "notify_key_type", notif_type); add_hash_param(notify->meta, "notify_key_operation", action->task); return notify; } /*! * \internal * \brief Create a new notify action for a clone instance * * \param[in] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in] op Action that notification is for * \param[in] notify_done Parent pseudo-action for notifications complete * \param[in] n_data Notification values to add to action meta-data * \param[in] data_set Cluster working set * * \return Newly created notify action */ static pe_action_t * new_notify_action(pe_resource_t *rsc, pe_node_t *node, pe_action_t *op, pe_action_t *notify_done, notify_data_t *n_data, pe_working_set_t *data_set) { char *key = NULL; pe_action_t *notify_action = NULL; const char *value = NULL; const char *task = NULL; const char *skip_reason = NULL; CRM_CHECK((rsc != NULL) && (node != NULL), return NULL); // Ensure we have all the info we need if (op == NULL) { skip_reason = "no action"; } else if (notify_done == NULL) { skip_reason = "no parent notification"; } else if (!node->details->online) { skip_reason = "node offline"; } else if (!pcmk_is_set(op->flags, pe_action_runnable)) { skip_reason = "original action not runnable"; } if (skip_reason != NULL) { pe_rsc_trace(rsc, "Skipping notify action for %s on %s: %s", rsc->id, node->details->uname, skip_reason); return NULL; } value = g_hash_table_lookup(op->meta, "notify_type"); // "pre" or "post" task = g_hash_table_lookup(op->meta, "notify_operation"); // original action pe_rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)", rsc->id, node->details->uname, value, task); // Create the notify action key = pcmk__notify_key(rsc->id, value, task); notify_action = custom_action(rsc, key, op->task, node, pcmk_is_set(op->flags, pe_action_optional), TRUE, data_set); // Add meta-data to notify action g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action); add_notify_data_to_action_meta(n_data, notify_action); // Order notify after original action and before parent notification order_actions(op, notify_action, pe_order_optional); order_actions(notify_action, notify_done, pe_order_optional); return notify_action; } /*! * \internal * \brief Create a new "post-" notify action for a clone instance * * \param[in] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in] n_data Notification values to add to action meta-data * \param[in] data_set Cluster working set */ static void new_post_notify_action(pe_resource_t *rsc, pe_node_t *node, notify_data_t *n_data, pe_working_set_t *data_set) { pe_action_t *notify = NULL; // Create the "post-" notify action for specified instance notify = new_notify_action(rsc, node, n_data->post, n_data->post_done, n_data, data_set); if (notify != NULL) { notify->priority = INFINITY; } // Order recurring monitors after all "post-" notifications complete if (n_data->post_done == NULL) { return; } for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) { pe_action_t *mon = (pe_action_t *) iter->data; const char *interval_ms_s = NULL; interval_ms_s = g_hash_table_lookup(mon->meta, XML_LRM_ATTR_INTERVAL_MS); if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches) || pcmk__str_eq(mon->task, RSC_CANCEL, pcmk__str_none)) { continue; // Not a recurring monitor } order_actions(n_data->post_done, mon, pe_order_optional); } } /*! * \internal * \brief Create and order notification pseudo-actions for a clone action * * In addition to the actual notify actions needed for each clone instance, * clone notifications also require pseudo-actions to provide ordering points * in the notification process. This creates the notification data, along with * appropriate pseudo-actions and their orderings. * * For example, the ordering sequence for starting a clone is: * * "pre-" notify pseudo-action for clone * -> "pre-" notify actions for each clone instance * -> "pre-" notifications complete pseudo-action for clone * -> start actions for each clone instance * -> "started" pseudo-action for clone * -> "post-" notify pseudo-action for clone * -> "post-" notify actions for each clone instance * -> "post-" notifications complete pseudo-action for clone * * \param[in] rsc Clone that notifications are for * \param[in] task Name of action that notifications are for * \param[in] action If not NULL, create a "pre-" pseudo-action ordered * before a "pre-" complete pseudo-action, ordered before * this action * \param[in] complete If not NULL, create a "post-" pseudo-action ordered * after this action, and a "post-" complete pseudo-action * ordered after that * \param[in] data_set Cluster working set * * \return Newly created notification data */ notify_data_t * pcmk__clone_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete, pe_working_set_t *data_set) { notify_data_t *n_data = NULL; if (!pcmk_is_set(rsc->flags, pe_rsc_notify)) { return NULL; } n_data = calloc(1, sizeof(notify_data_t)); CRM_ASSERT(n_data != NULL); n_data->action = task; if (action != NULL) { // Need "pre-" pseudo-actions // Create "pre-" notify pseudo-action for clone n_data->pre = new_notify_pseudo_action(rsc, action, RSC_NOTIFY, "pre"); pe__set_action_flags(n_data->pre, pe_action_runnable); add_hash_param(n_data->pre->meta, "notify_type", "pre"); add_hash_param(n_data->pre->meta, "notify_operation", n_data->action); // Create "pre-" notifications complete pseudo-action for clone n_data->pre_done = new_notify_pseudo_action(rsc, action, RSC_NOTIFIED, "confirmed-pre"); pe__set_action_flags(n_data->pre_done, pe_action_runnable); add_hash_param(n_data->pre_done->meta, "notify_type", "pre"); add_hash_param(n_data->pre_done->meta, "notify_operation", n_data->action); // Order "pre-" -> "pre-" complete -> original action order_actions(n_data->pre, n_data->pre_done, pe_order_optional); order_actions(n_data->pre_done, action, pe_order_optional); } if (complete != NULL) { // Need "post-" pseudo-actions // Create "post-" notify pseudo-action for clone n_data->post = new_notify_pseudo_action(rsc, complete, RSC_NOTIFY, "post"); n_data->post->priority = INFINITY; if (pcmk_is_set(complete->flags, pe_action_runnable)) { pe__set_action_flags(n_data->post, pe_action_runnable); } else { pe__clear_action_flags(n_data->post, pe_action_runnable); } add_hash_param(n_data->post->meta, "notify_type", "post"); add_hash_param(n_data->post->meta, "notify_operation", n_data->action); // Create "post-" notifications complete pseudo-action for clone n_data->post_done = new_notify_pseudo_action(rsc, complete, RSC_NOTIFIED, "confirmed-post"); n_data->post_done->priority = INFINITY; if (pcmk_is_set(complete->flags, pe_action_runnable)) { pe__set_action_flags(n_data->post_done, pe_action_runnable); } else { pe__clear_action_flags(n_data->post_done, pe_action_runnable); } add_hash_param(n_data->post_done->meta, "notify_type", "post"); add_hash_param(n_data->post_done->meta, "notify_operation", n_data->action); // Order original action complete -> "post-" -> "post-" complete order_actions(complete, n_data->post, pe_order_implies_then); order_actions(n_data->post, n_data->post_done, pe_order_implies_then); } // If we created both, order "pre-" complete -> "post-" if ((action != NULL) && (complete != NULL)) { order_actions(n_data->pre_done, n_data->post, pe_order_optional); } return n_data; } /*! * \internal * \brief Create a new notification entry * * \param[in] rsc Resource for notification * \param[in] node Node for notification * * \return Newly allocated notification entry * \note The caller is responsible for freeing the return value. */ static notify_entry_t * new_notify_entry(pe_resource_t *rsc, pe_node_t *node) { notify_entry_t *entry = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(entry != NULL); entry->rsc = rsc; entry->node = node; return entry; } /*! * \internal * \brief Add notification data for resource state and optionally actions * * \param[in] rsc Clone or clone instance being notified * \param[in] activity Whether to add notification entries for actions * \param[in] n_data Notification data for clone */ static void collect_resource_data(pe_resource_t *rsc, bool activity, notify_data_t *n_data) { GList *iter = NULL; notify_entry_t *entry = NULL; pe_node_t *node = NULL; if (n_data->allowed_nodes == NULL) { n_data->allowed_nodes = rsc->allowed_nodes; } // If this is a clone, call recursively for each instance if (rsc->children != NULL) { for (iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *child = (pe_resource_t *) iter->data; collect_resource_data(child, activity, n_data); } return; } // This is a notification for a single clone instance if (rsc->running_on != NULL) { node = rsc->running_on->data; // First is sufficient } entry = new_notify_entry(rsc, node); // Add notification indicating the resource state switch (rsc->role) { case RSC_ROLE_STOPPED: n_data->inactive = g_list_prepend(n_data->inactive, entry); break; case RSC_ROLE_STARTED: n_data->active = g_list_prepend(n_data->active, entry); break; case RSC_ROLE_UNPROMOTED: n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; case RSC_ROLE_PROMOTED: n_data->promoted = g_list_prepend(n_data->promoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; default: crm_err("Resource %s role on %s (%s) is not supported for " "notifications (bug?)", rsc->id, ((node == NULL)? "no node" : node->details->uname), role2text(rsc->role)); free(entry); break; } if (!activity) { return; } // Add notification entries for each of the resource's actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { pe_action_t *op = (pe_action_t *) iter->data; if (!pcmk_is_set(op->flags, pe_action_optional) && (op->node != NULL)) { enum action_tasks task = text2task(op->task); if ((task == stop_rsc) && op->node->details->unclean) { // Create anyway (additional noise if node can't be fenced) } else if (!pcmk_is_set(op->flags, pe_action_runnable)) { continue; } entry = new_notify_entry(rsc, op->node); switch (task) { case start_rsc: n_data->start = g_list_prepend(n_data->start, entry); break; case stop_rsc: n_data->stop = g_list_prepend(n_data->stop, entry); break; case action_promote: n_data->promote = g_list_prepend(n_data->promote, entry); break; case action_demote: n_data->demote = g_list_prepend(n_data->demote, entry); break; default: free(entry); break; } } } } #define add_notify_env(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \ } while (0) #define add_notify_env_free(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \ free(value); value = NULL; \ } while (0) /*! * \internal * \brief Create notification name/value pairs from structured data * * \param[in] rsc Resource that notification is for * \param[in,out] n_data Notification data * \param[in] data_set Cluster working set */ static void add_notif_keys(pe_resource_t *rsc, notify_data_t *n_data, pe_working_set_t *data_set) { bool required = false; // Whether to make notify actions required char *rsc_list = NULL; char *node_list = NULL; char *metal_list = NULL; const char *source = NULL; GList *nodes = NULL; n_data->stop = notify_entries_to_strings(n_data->stop, &rsc_list, &node_list); if (!pcmk__str_eq(" ", rsc_list, pcmk__str_null_matches) && pcmk__str_eq(n_data->action, RSC_STOP, pcmk__str_casei)) { required = true; } add_notify_env_free(n_data, "notify_stop_resource", rsc_list); add_notify_env_free(n_data, "notify_stop_uname", node_list); if ((n_data->start != NULL) && pcmk__str_eq(n_data->action, RSC_START, pcmk__str_none)) { required = true; } n_data->start = notify_entries_to_strings(n_data->start, &rsc_list, &node_list); add_notify_env_free(n_data, "notify_start_resource", rsc_list); add_notify_env_free(n_data, "notify_start_uname", node_list); if ((n_data->demote != NULL) && pcmk__str_eq(n_data->action, RSC_DEMOTE, pcmk__str_none)) { required = true; } n_data->demote = notify_entries_to_strings(n_data->demote, &rsc_list, &node_list); add_notify_env_free(n_data, "notify_demote_resource", rsc_list); add_notify_env_free(n_data, "notify_demote_uname", node_list); if ((n_data->promote != NULL) && pcmk__str_eq(n_data->action, RSC_PROMOTE, pcmk__str_none)) { required = true; } n_data->promote = notify_entries_to_strings(n_data->promote, &rsc_list, &node_list); add_notify_env_free(n_data, "notify_promote_resource", rsc_list); add_notify_env_free(n_data, "notify_promote_uname", node_list); n_data->active = notify_entries_to_strings(n_data->active, &rsc_list, &node_list); add_notify_env_free(n_data, "notify_active_resource", rsc_list); add_notify_env_free(n_data, "notify_active_uname", node_list); n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted, &rsc_list, &node_list); add_notify_env(n_data, "notify_unpromoted_resource", rsc_list); add_notify_env(n_data, "notify_unpromoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free(n_data, "notify_slave_resource", rsc_list); add_notify_env_free(n_data, "notify_slave_uname", node_list); n_data->promoted = notify_entries_to_strings(n_data->promoted, &rsc_list, &node_list); add_notify_env(n_data, "notify_promoted_resource", rsc_list); add_notify_env(n_data, "notify_promoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free(n_data, "notify_master_resource", rsc_list); add_notify_env_free(n_data, "notify_master_uname", node_list); n_data->inactive = notify_entries_to_strings(n_data->inactive, &rsc_list, NULL); add_notify_env_free(n_data, "notify_inactive_resource", rsc_list); nodes = g_hash_table_get_values(n_data->allowed_nodes); if (!pcmk__is_daemon) { /* For display purposes, sort the node list, for consistent * regression test output (while avoiding the performance hit * for the live cluster). */ nodes = g_list_sort(nodes, sort_node_uname); } get_node_names(nodes, &node_list, NULL); add_notify_env_free(n_data, "notify_available_uname", node_list); g_list_free(nodes); source = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET); if (pcmk__str_eq("host", source, pcmk__str_none)) { get_node_names(data_set->nodes, &node_list, &metal_list); add_notify_env_free(n_data, "notify_all_hosts", metal_list); } else { get_node_names(data_set->nodes, &node_list, NULL); } add_notify_env_free(n_data, "notify_all_uname", node_list); if (required && (n_data->pre != NULL)) { pe__clear_action_flags(n_data->pre, pe_action_optional); pe__clear_action_flags(n_data->pre_done, pe_action_optional); } if (required && (n_data->post != NULL)) { pe__clear_action_flags(n_data->post, pe_action_optional); pe__clear_action_flags(n_data->post_done, pe_action_optional); } } /* * \internal * \brief Find any remote connection start relevant to an action * * \param[in] action Action to check * * \return If action is behind a remote connection, connection's start */ static pe_action_t * find_remote_start(pe_action_t *action) { if ((action != NULL) && (action->node != NULL)) { pe_resource_t *remote_rsc = action->node->details->remote_rsc; if (remote_rsc != NULL) { return find_first_action(remote_rsc->actions, NULL, RSC_START, NULL); } } return NULL; } /*! * \internal * \brief Create notify actions, and add notify data to original actions * * \param[in] rsc Clone or clone instance that notification is for * \param[in] n_data Clone notification data for some action * \param[in] data_set Cluster working set */ static void create_notify_actions(pe_resource_t *rsc, notify_data_t *n_data, pe_working_set_t *data_set) { GList *iter = NULL; pe_action_t *stop = NULL; pe_action_t *start = NULL; enum action_tasks task = text2task(n_data->action); // If this is a clone, call recursively for each instance if (rsc->children != NULL) { for (iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *child = (pe_resource_t *) iter->data; create_notify_actions(child, n_data, data_set); } return; } // Add notification meta-attributes to original actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { pe_action_t *op = (pe_action_t *) iter->data; if (!pcmk_is_set(op->flags, pe_action_optional) && (op->node != NULL)) { switch (text2task(op->task)) { case start_rsc: case stop_rsc: case action_promote: case action_demote: add_notify_data_to_action_meta(n_data, op); break; default: break; } } } // Skip notify action itself if original action was not needed switch (task) { case start_rsc: if (n_data->start == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case action_promote: if (n_data->promote == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case action_demote: if (n_data->demote == NULL) { pe_rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; default: // We cannot do same for stop because it might be implied by fencing break; } pe_rsc_trace(rsc, "Creating notify actions for %s %s", rsc->id, n_data->action); // Create notify actions for stop or demote if ((rsc->role != RSC_ROLE_STOPPED) && ((task == stop_rsc) || (task == action_demote))) { stop = find_first_action(rsc->actions, NULL, RSC_STOP, NULL); for (iter = rsc->running_on; iter != NULL; iter = iter->next) { pe_node_t *current_node = (pe_node_t *) iter->data; /* If a stop is a pseudo-action implied by fencing, don't try to * notify the node getting fenced. */ if ((stop != NULL) && pcmk_is_set(stop->flags, pe_action_pseudo) && (current_node->details->unclean || current_node->details->remote_requires_reset)) { continue; } new_notify_action(rsc, current_node, n_data->pre, n_data->pre_done, n_data, data_set); if ((task == action_demote) || (stop == NULL) || pcmk_is_set(stop->flags, pe_action_optional)) { new_post_notify_action(rsc, current_node, n_data, data_set); } } } // Create notify actions for start or promote if ((rsc->next_role != RSC_ROLE_STOPPED) && ((task == start_rsc) || (task == action_promote))) { start = find_first_action(rsc->actions, NULL, RSC_START, NULL); if (start != NULL) { pe_action_t *remote_start = find_remote_start(start); if ((remote_start != NULL) && !pcmk_is_set(remote_start->flags, pe_action_runnable)) { /* Start and promote actions for a clone instance behind * a Pacemaker Remote connection happen after the * connection starts. If the connection start is blocked, do * not schedule notifications for these actions. */ return; } } if (rsc->allocated_to == NULL) { pe_proc_err("Next role '%s' but %s is not allocated", role2text(rsc->next_role), rsc->id); return; } if ((task != start_rsc) || (start == NULL) || pcmk_is_set(start->flags, pe_action_optional)) { new_notify_action(rsc, rsc->allocated_to, n_data->pre, n_data->pre_done, n_data, data_set); } new_post_notify_action(rsc, rsc->allocated_to, n_data, data_set); } } /*! * \internal * \brief Create notification data and actions for a clone * * \param[in] rsc Clone resource that notification is for * \param[in] n_data Clone notification data for some action */ void pcmk__create_notifications(pe_resource_t *rsc, notify_data_t *n_data) { if (n_data != NULL) { collect_resource_data(rsc, true, n_data); add_notif_keys(rsc, n_data, rsc->cluster); create_notify_actions(rsc, n_data, rsc->cluster); } } /*! * \internal * \brief Free notification data * * \param[in] n_data Notification data to free */ void pcmk__free_notification_data(notify_data_t *n_data) { if (n_data == NULL) { return; } g_list_free_full(n_data->stop, free); g_list_free_full(n_data->start, free); g_list_free_full(n_data->demote, free); g_list_free_full(n_data->promote, free); g_list_free_full(n_data->promoted, free); g_list_free_full(n_data->unpromoted, free); g_list_free_full(n_data->active, free); g_list_free_full(n_data->inactive, free); pcmk_free_nvpairs(n_data->keys); free(n_data); } +/*! + * \internal + * \brief Order clone "notifications complete" pseudo-action after fencing + * + * If a stop action is implied by fencing, the usual notification pseudo-actions + * will not be sufficient to order things properly, or even create all needed + * notifications if the clone is also stopping on another node, and another + * clone is ordered after it. This function creates new notification + * pseudo-actions relative to the fencing to ensure everything works properly. + * + * \param[in] stop Stop action implied by fencing + * \param[in] rsc Clone resource that notification is for + * \param[in] stonith_op Fencing action that implies \p stop + * \param[in] data_set Cluster working set + */ void -create_secondary_notification(pe_action_t *action, pe_resource_t *rsc, - pe_action_t *stonith_op, - pe_working_set_t *data_set) +pcmk__order_notifs_after_fencing(pe_action_t *stop, pe_resource_t *rsc, + pe_action_t *stonith_op, + pe_working_set_t *data_set) { notify_data_t *n_data; - crm_info("Creating secondary notification for %s", action->uuid); + crm_info("Ordering notifications for implied %s after fencing", stop->uuid); n_data = pcmk__clone_notif_pseudo_ops(rsc, RSC_STOP, NULL, stonith_op, data_set); collect_resource_data(rsc, false, n_data); add_notify_env(n_data, "notify_stop_resource", rsc->id); - add_notify_env(n_data, "notify_stop_uname", action->node->details->uname); + add_notify_env(n_data, "notify_stop_uname", stop->node->details->uname); create_notify_actions(uber_parent(rsc), n_data, data_set); pcmk__free_notification_data(n_data); }