diff --git a/lib/pacemaker/Makefile.am b/lib/pacemaker/Makefile.am index dfc8f80c95..354a1429a2 100644 --- a/lib/pacemaker/Makefile.am +++ b/lib/pacemaker/Makefile.am @@ -1,66 +1,67 @@ # # 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 $(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 5:0:4 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/fencing/libstonithd.la \ $(top_builddir)/lib/services/libcrmservice.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_acl.c 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_injections.c libpacemaker_la_SOURCES += pcmk_output.c libpacemaker_la_SOURCES += pcmk_resource.c libpacemaker_la_SOURCES += pcmk_result_code.c libpacemaker_la_SOURCES += pcmk_rule.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_nodes.c libpacemaker_la_SOURCES += pcmk_sched_ordering.c libpacemaker_la_SOURCES += pcmk_sched_primitive.c libpacemaker_la_SOURCES += pcmk_sched_probes.c libpacemaker_la_SOURCES += pcmk_sched_promotable.c +libpacemaker_la_SOURCES += pcmk_sched_recurring.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_utilization.c libpacemaker_la_SOURCES += pcmk_simulate.c libpacemaker_la_SOURCES += pcmk_status.c diff --git a/lib/pacemaker/libpacemaker_private.h b/lib/pacemaker/libpacemaker_private.h index bf1d71d01b..343917172d 100644 --- a/lib/pacemaker/libpacemaker_private.h +++ b/lib/pacemaker/libpacemaker_private.h @@ -1,701 +1,708 @@ /* * 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 // Flags to modify the behavior of the add_colocated_node_scores() method enum pcmk__coloc_select { // With no other flags, apply all "with this" colocations pcmk__coloc_select_default = 0, // Apply "this with" colocations instead of "with this" colocations pcmk__coloc_select_this_with = (1 << 0), // Apply only colocations with non-negative scores pcmk__coloc_select_nonnegative = (1 << 1), // Apply only colocations with at least one matching node pcmk__coloc_select_active = (1 << 2), }; // Flags the update_ordered_actions() method can return enum pcmk__updated { pcmk__updated_none = 0, // Nothing changed pcmk__updated_first = (1 << 0), // First action was updated pcmk__updated_then = (1 << 1), // Then action was updated }; #define pcmk__set_updated_flags(au_flags, action, flags_to_set) do { \ au_flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action update", \ (action)->uuid, au_flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_updated_flags(au_flags, action, flags_to_clear) do { \ au_flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action update", \ (action)->uuid, au_flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) // Resource allocation methods struct resource_alloc_functions_s { /*! * \internal * \brief Assign a resource to a node * * \param[in] rsc Resource to assign to a node * \param[in] prefer Node to prefer, if all else is equal * * \return Node that \p rsc is assigned to, if assigned entirely to one node */ pe_node_t *(*assign)(pe_resource_t *rsc, pe_node_t *prefer); void (*create_actions)(pe_resource_t *rsc); /*! * \internal * \brief Schedule any probes needed for a resource on a node * * \param[in] rsc Resource to create probe for * \param[in] node Node to create probe on * * \return true if any probe was created, otherwise false */ bool (*create_probe)(pe_resource_t *rsc, pe_node_t *node); void (*internal_constraints)(pe_resource_t *rsc); /*! * \internal * \brief Apply a colocation's score to node weights or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node weights (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void (*apply_coloc_score) (pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent); /*! * \internal * \brief Update nodes with scores of colocated resources' nodes * * Given a table of nodes and a resource, update the nodes' scores with the * scores of the best nodes matching the attribute used for each of the * resource's relevant colocations. * * \param[in,out] rsc Resource to check colocations for * \param[in] log_id Resource ID to use in logs (if NULL, use rsc ID) * \param[in,out] nodes Nodes to update * \param[in] attr Colocation attribute (NULL to use default) * \param[in] factor Incorporate scores multiplied by this factor * \param[in] flags Bitmask of enum pcmk__coloc_select values * * \note The caller remains responsible for freeing \p *nodes. */ void (*add_colocated_node_scores)(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, const char *attr, float factor, enum pcmk__coloc_select flags); /*! * \internal * \brief Create list of all resources in colocations with a given resource * * Given a resource, create a list of all resources involved in mandatory * colocations with it, whether directly or indirectly via chained colocations. * * \param[in] rsc Resource to add to colocated list * \param[in] orig_rsc Resource originally requested * \param[in] colocated_rscs Existing list * * \return List of given resource and all resources involved in colocations * * \note This function is recursive; top-level callers should pass NULL as * \p colocated_rscs and \p orig_rsc, and the desired resource as * \p rsc. The recursive calls will use other values. */ GList *(*colocated_resources)(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *colocated_rscs); void (*rsc_location) (pe_resource_t *, pe__location_t *); enum pe_action_flags (*action_flags) (pe_action_t *, pe_node_t *); /*! * \internal * \brief Update two actions according to an ordering between them * * Given information about an ordering of two actions, update the actions' * flags (and runnable_before members if appropriate) as appropriate for the * ordering. In some cases, the ordering could be disabled as well. * * \param[in] first 'First' action in an ordering * \param[in] then 'Then' action in an ordering * \param[in] node If not NULL, limit scope of ordering to this node * (only used when interleaving instances) * \param[in] flags Action flags for \p first for ordering purposes * \param[in] filter Action flags to limit scope of certain updates (may * include pe_action_optional to affect only mandatory * actions, and pe_action_runnable to affect only * runnable actions) * \param[in] type Group of enum pe_ordering flags to apply * \param[in] data_set Cluster working set * * \return Group of enum pcmk__updated flags indicating what was updated */ uint32_t (*update_ordered_actions)(pe_action_t *first, pe_action_t *then, pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pe_working_set_t *data_set); void (*output_actions)(pe_resource_t *rsc); /*! * \internal * \brief Add a resource's actions to the transition graph * * \param[in] rsc Resource whose actions should be added */ void (*add_actions_to_graph)(pe_resource_t *rsc); void (*append_meta) (pe_resource_t * rsc, xmlNode * xml); /*! * \internal * \brief Add a resource's utilization to a table of utilization values * * This function is used when summing the utilization of a resource and all * resources colocated with it, to determine whether a node has sufficient * capacity. Given a resource and a table of utilization values, it will add * the resource's utilization to the existing values, if the resource has * not yet been allocated to a node. * * \param[in] rsc Resource with utilization to add * \param[in] orig_rsc Resource being allocated (for logging only) * \param[in] all_rscs List of all resources that will be summed * \param[in] utilization Table of utilization values to add to */ void (*add_utilization)(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization); /*! * \internal * \brief Apply a shutdown lock for a resource, if appropriate * * \param[in] rsc Resource to check for shutdown lock */ void (*shutdown_lock)(pe_resource_t *rsc); }; // Actions (pcmk_sched_actions.c) G_GNUC_INTERNAL void pcmk__update_action_for_orderings(pe_action_t *action, pe_working_set_t *data_set); G_GNUC_INTERNAL uint32_t pcmk__update_ordered_actions(pe_action_t *first, pe_action_t *then, pe_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, 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_cancel_action(pe_resource_t *rsc, const char *name, guint interval_ms, const pe_node_t *node); G_GNUC_INTERNAL pe_action_t *pcmk__new_shutdown_action(pe_node_t *node); 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); G_GNUC_INTERNAL bool pcmk__check_action_config(pe_resource_t *rsc, pe_node_t *node, xmlNode *xml_op); G_GNUC_INTERNAL void pcmk__handle_rsc_config_changes(pe_working_set_t *data_set); + +// Recurring actions (pcmk_sched_recurring.c) + +G_GNUC_INTERNAL +void pcmk__create_recurring_actions(pe_resource_t *rsc); + + // 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_rsc_actions_to_graph(pe_resource_t *rsc); 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); G_GNUC_INTERNAL void pcmk__fence_guest(pe_node_t *node); G_GNUC_INTERNAL bool pcmk__node_unfenced(pe_node_t *node); // 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 (pcmk_sched_colocation.c) 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__add_colocated_node_scores(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, const char *attr, float factor, uint32_t flags); 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); /*! * \internal * \brief Check whether colocation's dependent preferences should be considered * * \param[in] colocation Colocation constraint * \param[in] rsc Primary instance (normally this will be * colocation->primary, which NULL will be treated as, * but for clones or bundles with multiple instances * this can be a particular instance) * * \return true if colocation influence should be effective, otherwise false */ static inline bool pcmk__colocation_has_influence(const pcmk__colocation_t *colocation, const pe_resource_t *rsc) { if (rsc == NULL) { rsc = colocation->primary; } /* A bundle replica colocates its remote connection with its container, * using a finite score so that the container can run on Pacemaker Remote * nodes. * * Moving a connection is lightweight and does not interrupt the service, * while moving a container is heavyweight and does interrupt the service, * so don't move a clean, active container based solely on the preferences * of its connection. * * This also avoids problematic scenarios where two containers want to * perpetually swap places. */ if (pcmk_is_set(colocation->dependent->flags, pe_rsc_allow_remote_remotes) && !pcmk_is_set(rsc->flags, pe_rsc_failed) && pcmk__list_of_1(rsc->running_on)) { return false; } /* The dependent in a colocation influences the primary's location * if the influence option is true or the primary is not yet active. */ return colocation->influence || (rsc->running_on == NULL); } // Ordering constraints (pcmk_sched_ordering.c) G_GNUC_INTERNAL void pcmk__new_ordering(pe_resource_t *first_rsc, char *first_task, pe_action_t *first_action, pe_resource_t *then_rsc, char *then_task, pe_action_t *then_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); G_GNUC_INTERNAL void pcmk__apply_orderings(pe_working_set_t *data_set); G_GNUC_INTERNAL void pcmk__order_after_each(pe_action_t *after, GList *list); /*! * \internal * \brief Create a new ordering between two resource actions * * \param[in] first_rsc Resource for 'first' action * \param[in] then_rsc Resource for 'then' action * \param[in] first_task Action key for 'first' action * \param[in] then_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(first_rsc, first_task, \ then_rsc, then_task, flags) \ pcmk__new_ordering((first_rsc), \ pcmk__op_key((first_rsc)->id, (first_task), 0), \ NULL, \ (then_rsc), \ pcmk__op_key((then_rsc)->id, (then_task), 0), \ NULL, (flags), (first_rsc)->cluster) #define pcmk__order_starts(rsc1, rsc2, type) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_START, \ (rsc2), CRMD_ACTION_START, (type)) #define pcmk__order_stops(rsc1, rsc2, type) \ pcmk__order_resource_actions((rsc1), CRMD_ACTION_STOP, \ (rsc2), CRMD_ACTION_STOP, (type)) // Ticket constraints (pcmk_sched_tickets.c) G_GNUC_INTERNAL void pcmk__unpack_rsc_ticket(xmlNode *xml_obj, pe_working_set_t *data_set); // Promotable clone resources (pcmk_sched_promotable.c) G_GNUC_INTERNAL void pcmk__require_promotion_tickets(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__set_instance_roles(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__create_promotable_actions(pe_resource_t *clone); G_GNUC_INTERNAL void pcmk__promotable_restart_ordering(pe_resource_t *rsc); G_GNUC_INTERNAL void pcmk__order_promotable_instances(pe_resource_t *clone); G_GNUC_INTERNAL void pcmk__update_dependent_with_promotable(pe_resource_t *primary, pe_resource_t *dependent, pcmk__colocation_t *colocation); G_GNUC_INTERNAL void pcmk__update_promotable_dependent_priority(pe_resource_t *primary, pe_resource_t *dependent, pcmk__colocation_t *colocation); // Pacemaker Remote nodes (pcmk_sched_remote.c) 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); G_GNUC_INTERNAL void pcmk__add_bundle_meta_to_xml(xmlNode *args_xml, pe_action_t *action); // Primitives (pcmk_sched_primitive.c) G_GNUC_INTERNAL pe_node_t *pcmk__primitive_assign(pe_resource_t *rsc, pe_node_t *prefer); G_GNUC_INTERNAL void pcmk__primitive_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent); // Groups (pcmk_sched_group.c) G_GNUC_INTERNAL void pcmk__group_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent); G_GNUC_INTERNAL void pcmk__group_add_colocated_node_scores(pe_resource_t *rsc, const char *log_id, GHashTable **nodes, const char *attr, float factor, uint32_t flags); G_GNUC_INTERNAL GList *pcmk__group_colocated_resources(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *colocated_rscs); // Clones (pcmk_sched_clone.c) G_GNUC_INTERNAL void pcmk__clone_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent); // Bundles (pcmk_sched_bundle.c) G_GNUC_INTERNAL void pcmk__bundle_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent); 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); // Nodes (pcmk_sched_nodes.c) G_GNUC_INTERNAL bool pcmk__node_available(const pe_node_t *node, bool consider_score, bool consider_guest); G_GNUC_INTERNAL bool pcmk__any_node_available(GHashTable *nodes); G_GNUC_INTERNAL GHashTable *pcmk__copy_node_table(GHashTable *nodes); G_GNUC_INTERNAL GList *pcmk__sort_nodes(GList *nodes, pe_node_t *active_node); G_GNUC_INTERNAL void pcmk__apply_node_health(pe_working_set_t *data_set); G_GNUC_INTERNAL pe_node_t *pcmk__top_allowed_node(const pe_resource_t *rsc, const pe_node_t *node); // Functions applying to more than one variant (pcmk_sched_resource.c) G_GNUC_INTERNAL void pcmk__set_allocation_methods(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__rsc_agent_changed(pe_resource_t *rsc, pe_node_t *node, const xmlNode *rsc_entry, bool active_on_node); G_GNUC_INTERNAL GList *pcmk__rscs_matching_id(const char *id, pe_working_set_t *data_set); 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_resource_t **failed); G_GNUC_INTERNAL void pcmk__sort_resources(pe_working_set_t *data_set); G_GNUC_INTERNAL gint pcmk__cmp_instance(gconstpointer a, gconstpointer b); G_GNUC_INTERNAL gint pcmk__cmp_instance_number(gconstpointer a, gconstpointer b); // Functions related to probes (pcmk_sched_probes.c) G_GNUC_INTERNAL bool pcmk__probe_rsc_on_node(pe_resource_t *rsc, pe_node_t *node); G_GNUC_INTERNAL void pcmk__order_probes(pe_working_set_t *data_set); G_GNUC_INTERNAL bool pcmk__probe_resource_list(GList *rscs, pe_node_t *node); 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); G_GNUC_INTERNAL void pcmk__create_utilization_constraints(pe_resource_t *rsc, GList *allowed_nodes); G_GNUC_INTERNAL void pcmk__show_node_capacities(const char *desc, pe_working_set_t *data_set); #endif // PCMK__LIBPACEMAKER_PRIVATE__H diff --git a/lib/pacemaker/pcmk_sched_primitive.c b/lib/pacemaker/pcmk_sched_primitive.c index 6d0119be26..baa7696ad2 100644 --- a/lib/pacemaker/pcmk_sched_primitive.c +++ b/lib/pacemaker/pcmk_sched_primitive.c @@ -1,2039 +1,1469 @@ /* * 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" gboolean DeleteRsc(pe_resource_t * rsc, pe_node_t * node, gboolean optional, pe_working_set_t * data_set); static bool StopRsc(pe_resource_t *rsc, pe_node_t *next, bool optional); static bool StartRsc(pe_resource_t *rsc, pe_node_t *next, bool optional); static bool DemoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional); static bool PromoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional); static bool RoleError(pe_resource_t *rsc, pe_node_t *next, bool optional); static bool NullOp(pe_resource_t *rsc, pe_node_t *next, bool optional); /* This array says what the *next* role should be when transitioning from one * role to another. For example going from Stopped to Promoted, the next role is * RSC_ROLE_UNPROMOTED, because the resource must be started before being promoted. * The current state then becomes Started, which is fed into this array again, * giving a next role of RSC_ROLE_PROMOTED. */ static enum rsc_role_e rsc_state_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = { /* Current state Next state*/ /* Unknown Stopped Started Unpromoted Promoted */ /* Unknown */ { RSC_ROLE_UNKNOWN, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED }, /* Stopped */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STARTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED }, /* Started */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STARTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED }, /* Unpromoted */ { RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_STOPPED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED }, /* Promoted */ { RSC_ROLE_STOPPED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_UNPROMOTED, RSC_ROLE_PROMOTED }, }; typedef bool (*rsc_transition_fn)(pe_resource_t *rsc, pe_node_t *next, bool optional); // This array picks the function needed to transition from one role to another static rsc_transition_fn rsc_action_matrix[RSC_ROLE_MAX][RSC_ROLE_MAX] = { /* Current state Next state */ /* Unknown Stopped Started Unpromoted Promoted */ /* Unknown */ { RoleError, StopRsc, RoleError, RoleError, RoleError, }, /* Stopped */ { RoleError, NullOp, StartRsc, StartRsc, RoleError, }, /* Started */ { RoleError, StopRsc, NullOp, NullOp, PromoteRsc, }, /* Unpromoted */ { RoleError, StopRsc, StopRsc, NullOp, PromoteRsc, }, /* Promoted */ { RoleError, DemoteRsc, DemoteRsc, DemoteRsc, NullOp, }, }; /*! * \internal * \brief Get a list of a resource's allowed nodes sorted by node weight * * \param[in] rsc Resource to check * * \return List of allowed nodes sorted by node weight */ static GList * sorted_allowed_nodes(const pe_resource_t *rsc) { if (rsc->allowed_nodes != NULL) { GList *nodes = g_hash_table_get_values(rsc->allowed_nodes); if (nodes != NULL) { return pcmk__sort_nodes(nodes, pe__current_node(rsc)); } } return NULL; } /*! * \internal * \brief Assign a resource to its best allowed node, if possible * * \param[in] rsc Resource to choose a node for * \param[in] prefer If not NULL, prefer this node when all else equal * * \return true if \p rsc could be assigned to a node, otherwise false */ static bool assign_best_node(pe_resource_t *rsc, pe_node_t *prefer) { GList *nodes = NULL; pe_node_t *chosen = NULL; pe_node_t *best = NULL; bool result = false; pcmk__ban_insufficient_capacity(rsc, &prefer); if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { // We've already finished assignment of resources to nodes return rsc->allocated_to != NULL; } // Sort allowed nodes by weight nodes = sorted_allowed_nodes(rsc); if (nodes != NULL) { best = (pe_node_t *) nodes->data; // First node has best score } if ((prefer != NULL) && (nodes != NULL)) { // Get the allowed node version of prefer chosen = g_hash_table_lookup(rsc->allowed_nodes, prefer->details->id); if (chosen == NULL) { pe_rsc_trace(rsc, "Preferred node %s for %s was unknown", pe__node_name(prefer), rsc->id); /* Favor the preferred node as long as its weight is at least as good as * the best allowed node's. * * An alternative would be to favor the preferred node even if the best * node is better, when the best node's weight is less than INFINITY. */ } else if (chosen->weight < best->weight) { pe_rsc_trace(rsc, "Preferred node %s for %s was unsuitable", pe__node_name(chosen), rsc->id); chosen = NULL; } else if (!pcmk__node_available(chosen, true, false)) { pe_rsc_trace(rsc, "Preferred node %s for %s was unavailable", pe__node_name(chosen), rsc->id); chosen = NULL; } else { pe_rsc_trace(rsc, "Chose preferred node %s for %s (ignoring %d candidates)", pe__node_name(chosen), rsc->id, g_list_length(nodes)); } } if ((chosen == NULL) && (best != NULL)) { /* Either there is no preferred node, or the preferred node is not * suitable, but another node is allowed to run the resource. */ chosen = best; if (!pe_rsc_is_unique_clone(rsc->parent) && (chosen->weight > 0) // Zero not acceptable && pcmk__node_available(chosen, false, false)) { /* If the resource is already running on a node, prefer that node if * it is just as good as the chosen node. * * We don't do this for unique clone instances, because * distribute_children() has already assigned instances to their * running nodes when appropriate, and if we get here, we don't want * remaining unassigned instances to prefer a node that's already * running another instance. */ pe_node_t *running = pe__current_node(rsc); if (running == NULL) { // Nothing to do } else if (!pcmk__node_available(running, true, false)) { pe_rsc_trace(rsc, "Current node for %s (%s) can't run resources", rsc->id, pe__node_name(running)); } else { int nodes_with_best_score = 1; for (GList *iter = nodes->next; iter; iter = iter->next) { pe_node_t *allowed = (pe_node_t *) iter->data; if (allowed->weight != chosen->weight) { // The nodes are sorted by weight, so no more are equal break; } if (allowed->details == running->details) { // Scores are equal, so prefer the current node chosen = allowed; } nodes_with_best_score++; } if (nodes_with_best_score > 1) { do_crm_log(((chosen->weight >= INFINITY)? LOG_WARNING : LOG_INFO), "Chose %s for %s from %d nodes with score %s", pe__node_name(chosen), rsc->id, nodes_with_best_score, pcmk_readable_score(chosen->weight)); } } } pe_rsc_trace(rsc, "Chose %s for %s from %d candidates", pe__node_name(chosen), rsc->id, g_list_length(nodes)); } result = pcmk__assign_primitive(rsc, chosen, false); g_list_free(nodes); return result; } /*! * \internal * \brief Apply a "this with" colocation to a node's allowed node scores * * \param[in] data Colocation to apply * \param[in] user_data Resource being assigned */ static void apply_this_with(void *data, void *user_data) { pcmk__colocation_t *colocation = (pcmk__colocation_t *) data; pe_resource_t *rsc = (pe_resource_t *) user_data; GHashTable *archive = NULL; pe_resource_t *other = colocation->primary; // In certain cases, we will need to revert the node scores if ((colocation->dependent_role >= RSC_ROLE_PROMOTED) || ((colocation->score < 0) && (colocation->score > -INFINITY))) { archive = pcmk__copy_node_table(rsc->allowed_nodes); } pe_rsc_trace(rsc, "%s: Assigning colocation %s primary %s first" "(score=%d role=%s)", rsc->id, colocation->id, other->id, colocation->score, role2text(colocation->dependent_role)); other->cmds->assign(other, NULL); // Apply the colocation score to this resource's allowed node scores rsc->cmds->apply_coloc_score(rsc, other, colocation, true); if ((archive != NULL) && !pcmk__any_node_available(rsc->allowed_nodes)) { pe_rsc_info(rsc, "%s: Reverting scores from colocation with %s " "because no nodes allowed", rsc->id, other->id); g_hash_table_destroy(rsc->allowed_nodes); rsc->allowed_nodes = archive; archive = NULL; } if (archive != NULL) { g_hash_table_destroy(archive); } } /*! * \internal * \brief Apply a "with this" colocation to a node's allowed node scores * * \param[in] data Colocation to apply * \param[in] user_data Resource being assigned */ static void apply_with_this(void *data, void *user_data) { pcmk__colocation_t *colocation = (pcmk__colocation_t *) data; pe_resource_t *rsc = (pe_resource_t *) user_data; pe_resource_t *other = colocation->dependent; const float factor = colocation->score / (float) INFINITY; if (!pcmk__colocation_has_influence(colocation, NULL)) { return; } pe_rsc_trace(rsc, "%s: Incorporating attenuated %s assignment scores due " "to colocation %s", rsc->id, other->id, colocation->id); other->cmds->add_colocated_node_scores(other, rsc->id, &rsc->allowed_nodes, colocation->node_attribute, factor, pcmk__coloc_select_active); } /*! * \internal * \brief Update a Pacemaker Remote node once its connection has been assigned * * \param[in] connection Connection resource that has been assigned */ static void remote_connection_assigned(pe_resource_t *connection) { pe_node_t *remote_node = pe_find_node(connection->cluster->nodes, connection->id); CRM_CHECK(remote_node != NULL, return); if ((connection->allocated_to != NULL) && (connection->next_role != RSC_ROLE_STOPPED)) { crm_trace("Pacemaker Remote node %s will be online", remote_node->details->id); remote_node->details->online = TRUE; if (remote_node->details->unseen) { // Avoid unnecessary fence, since we will attempt connection remote_node->details->unclean = FALSE; } } else { crm_trace("Pacemaker Remote node %s will be shut down " "(%sassigned connection's next role is %s)", remote_node->details->id, ((connection->allocated_to == NULL)? "un" : ""), role2text(connection->next_role)); remote_node->details->shutdown = TRUE; } } /*! * \internal * \brief Assign a primitive resource to a node * * \param[in] rsc Resource to assign to a node * \param[in] prefer Node to prefer, if all else is equal * * \return Node that \p rsc is assigned to, if assigned entirely to one node */ pe_node_t * pcmk__primitive_assign(pe_resource_t *rsc, pe_node_t *prefer) { CRM_ASSERT(rsc != NULL); // Never assign a child without parent being assigned first if ((rsc->parent != NULL) && !pcmk_is_set(rsc->parent->flags, pe_rsc_allocating)) { pe_rsc_debug(rsc, "%s: Assigning parent %s first", rsc->id, rsc->parent->id); rsc->parent->cmds->assign(rsc->parent, prefer); } if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { return rsc->allocated_to; // Assignment has already been done } // Ensure we detect assignment loops if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { pe_rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id); return NULL; } pe__set_resource_flags(rsc, pe_rsc_allocating); pe__show_node_weights(true, rsc, "Pre-assignment", rsc->allowed_nodes, rsc->cluster); g_list_foreach(rsc->rsc_cons, apply_this_with, rsc); pe__show_node_weights(true, rsc, "Post-this-with", rsc->allowed_nodes, rsc->cluster); g_list_foreach(rsc->rsc_cons_lhs, apply_with_this, rsc); if (rsc->next_role == RSC_ROLE_STOPPED) { pe_rsc_trace(rsc, "Banning %s from all nodes because it will be stopped", rsc->id); resource_location(rsc, NULL, -INFINITY, XML_RSC_ATTR_TARGET_ROLE, rsc->cluster); } else if ((rsc->next_role > rsc->role) && !pcmk_is_set(rsc->cluster->flags, pe_flag_have_quorum) && (rsc->cluster->no_quorum_policy == no_quorum_freeze)) { crm_notice("Resource %s cannot be elevated from %s to %s due to " "no-quorum-policy=freeze", rsc->id, role2text(rsc->role), role2text(rsc->next_role)); pe__set_next_role(rsc, rsc->role, "no-quorum-policy=freeze"); } pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_scores), rsc, __func__, rsc->allowed_nodes, rsc->cluster); // Unmanage resource if fencing is enabled but no device is configured if (pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_enabled) && !pcmk_is_set(rsc->cluster->flags, pe_flag_have_stonith_resource)) { pe__clear_resource_flags(rsc, pe_rsc_managed); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { // Unmanaged resources stay on their current node const char *reason = NULL; pe_node_t *assign_to = NULL; pe__set_next_role(rsc, rsc->role, "unmanaged"); assign_to = pe__current_node(rsc); if (assign_to == NULL) { reason = "inactive"; } else if (rsc->role == RSC_ROLE_PROMOTED) { reason = "promoted"; } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { reason = "failed"; } else { reason = "active"; } pe_rsc_info(rsc, "Unmanaged resource %s assigned to %s: %s", rsc->id, (assign_to? assign_to->details->uname : "no node"), reason); pcmk__assign_primitive(rsc, assign_to, true); } else if (pcmk_is_set(rsc->cluster->flags, pe_flag_stop_everything)) { pe_rsc_debug(rsc, "Forcing %s to stop: stop-all-resources", rsc->id); pcmk__assign_primitive(rsc, NULL, true); } else if (pcmk_is_set(rsc->flags, pe_rsc_provisional) && assign_best_node(rsc, prefer)) { // Assignment successful } else if (rsc->allocated_to == NULL) { if (!pcmk_is_set(rsc->flags, pe_rsc_orphan)) { pe_rsc_info(rsc, "Resource %s cannot run anywhere", rsc->id); } else if (rsc->running_on != NULL) { pe_rsc_info(rsc, "Stopping orphan resource %s", rsc->id); } } else { pe_rsc_debug(rsc, "%s: pre-assigned to %s", rsc->id, pe__node_name(rsc->allocated_to)); } pe__clear_resource_flags(rsc, pe_rsc_allocating); if (rsc->is_remote_node) { remote_connection_assigned(rsc); } return rsc->allocated_to; } -/*! - * \internal - * \brief Parse an interval from XML - * - * \param[in] xml XML containing an interval attribute - * - * \return Interval parsed from XML (or 0 as default) - */ -static guint -xe_interval(const xmlNode *xml) -{ - return crm_parse_interval_spec(crm_element_value(xml, - XML_LRM_ATTR_INTERVAL)); -} - -/*! - * \internal - * \brief Check whether an operation exists multiple times in resource history - * - * \param[in] rsc Resource with history to search - * \param[in] name Name of action to search for - * \param[in] interval_ms Interval (in milliseconds) of action to search for - * - * \return true if an operation with \p name and \p interval_ms exists more than - * once in the operation history of \p rsc, otherwise false - */ -static bool -is_op_dup(const pe_resource_t *rsc, const char *name, guint interval_ms) -{ - const char *id = NULL; - - for (xmlNode *op = first_named_child(rsc->ops_xml, "op"); - op != NULL; op = crm_next_same_xml(op)) { - - // Check whether action name and interval match - if (!pcmk__str_eq(crm_element_value(op, "name"), - name, pcmk__str_none) - || (xe_interval(op) != interval_ms)) { - continue; - } - - if (ID(op) == NULL) { - continue; // Shouldn't be possible - } - - if (id == NULL) { - id = ID(op); // First matching op - } else { - pcmk__config_err("Operation %s is duplicate of %s (do not use " - "same name and interval combination more " - "than once per resource)", ID(op), id); - return true; - } - } - return false; -} - -/*! - * \internal - * \brief Check whether an action name is one that can be recurring - * - * \param[in] name Action name to check - * - * \return true if \p name is an action known to be unsuitable as a recurring - * operation, otherwise false - * - * \note Pacemaker's current philosophy is to allow users to configure recurring - * operations except for a short list of actions known not to be suitable - * for that (as opposed to allowing only actions known to be suitable, - * which includes only monitor). Among other things, this approach allows - * users to define their own custom operations and make them recurring, - * though that use case is not well tested. - */ -static bool -op_cannot_recur(const char *name) -{ - return pcmk__str_any_of(name, RSC_STOP, RSC_START, RSC_DEMOTE, RSC_PROMOTE, - CRMD_ACTION_RELOAD_AGENT, CRMD_ACTION_MIGRATE, - CRMD_ACTION_MIGRATED, NULL); -} - -/*! - * \internal - * \brief Check whether a resource history entry is for a recurring action - * - * \param[in] rsc Resource that history entry is for - * \param[in] op Resource history entry to check - * \param[out] key Will be set to operation key if recurring - * \param[out] interval_ms Will be set to interval from history entry - */ -static bool -is_recurring_history(const pe_resource_t *rsc, const xmlNode *op, char **key, - guint *interval_ms) -{ - const char *name = NULL; - - *interval_ms = xe_interval(op); - if (*interval_ms == 0) { - return false; // Not recurring - } - - if (pcmk__str_empty(ID(op))) { - pcmk__config_err("Ignoring resource history entry without ID"); - return false; // Shouldn't be possible (unless CIB was manually edited) - } - - name = crm_element_value(op, "name"); - if (op_cannot_recur(name)) { - pcmk__config_err("Ignoring %s because action '%s' cannot be recurring", - ID(op), name); - return false; - } - - // There should only be one recurring operation per action/interval - if (is_op_dup(rsc, name, *interval_ms)) { - return false; - } - - // Disabled resources don't get monitored - *key = pcmk__op_key(rsc->id, name, *interval_ms); - if (find_rsc_op_entry(rsc, *key) == NULL) { - crm_trace("Not creating recurring action %s for disabled resource %s", - ID(op), rsc->id); - free(*key); - return false; - } - - return true; -} - -/*! - * \internal - * \brief Check whether a recurring action for an active role should be optional - * - * \param[in] rsc Resource that recurring action is for - * \param[in] node Node that \p rsc will be active on (if any) - * \param[in] key Operation key for recurring action to check - * \param[in] start Start action for \p rsc - * - * \return true if recurring action should be optional, otherwise false - */ -static bool -active_recurring_should_be_optional(const pe_resource_t *rsc, - const pe_node_t *node, const char *key, - pe_action_t *start) -{ - GList *possible_matches = NULL; - - if (node == NULL) { // Should only be possible if unmanaged and stopped - pe_rsc_trace(rsc, "%s will be mandatory because resource is unmanaged", - key); - return false; - } - - if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL), - pe_action_optional)) { - pe_rsc_trace(rsc, "%s will be mandatory because %s is", - key, start->uuid); - return false; - } - - possible_matches = find_actions_exact(rsc->actions, key, node); - if (possible_matches == NULL) { - pe_rsc_trace(rsc, "%s will be mandatory because it is not active on %s", - key, pe__node_name(node)); - return false; - } - - for (GList *iter = possible_matches; iter != NULL; iter = iter->next) { - pe_action_t *op = (pe_action_t *) iter->data; - - if (pcmk_is_set(op->flags, pe_action_reschedule)) { - pe_rsc_trace(rsc, - "%s will be mandatory because " - "it needs to be rescheduled", key); - g_list_free(possible_matches); - return false; - } - } - - g_list_free(possible_matches); - return true; -} - -/*! - * \internal - * \brief Create recurring action from resource history entry for an active role - * - * \param[in,out] rsc Resource that resource history is for - * \param[in] start Start action for \p rsc on \p node - * \param[in] node Node that resource will be active on (if any) - * \param[in] op Resource history entry - */ -static void -recurring_op_for_active(pe_resource_t *rsc, pe_action_t *start, - const pe_node_t *node, const xmlNode *op) -{ - char *key = NULL; - const char *name = NULL; - const char *role = NULL; - - guint interval_ms = 0; - pe_action_t *mon = NULL; - bool is_optional = true; - - // We're only interested in recurring actions for active roles - role = crm_element_value(op, "role"); - if ((role != NULL) && (text2role(role) == RSC_ROLE_STOPPED)) { - return; - } - - if (!is_recurring_history(rsc, op, &key, &interval_ms)) { - return; - } - - name = crm_element_value(op, "name"); - is_optional = active_recurring_should_be_optional(rsc, node, key, start); - - if (((role != NULL) && (rsc->next_role != text2role(role))) - || ((role == NULL) && (rsc->next_role == RSC_ROLE_PROMOTED))) { - // Configured monitor role doesn't match role resource will have - - if (is_optional) { // It's running, so cancel it - char *after_key = NULL; - pe_action_t *cancel_op = pcmk__new_cancel_action(rsc, name, - interval_ms, node); - - switch (rsc->role) { - case RSC_ROLE_UNPROMOTED: - case RSC_ROLE_STARTED: - if (rsc->next_role == RSC_ROLE_PROMOTED) { - after_key = promote_key(rsc); - - } else if (rsc->next_role == RSC_ROLE_STOPPED) { - after_key = stop_key(rsc); - } - - break; - case RSC_ROLE_PROMOTED: - after_key = demote_key(rsc); - break; - default: - break; - } - - if (after_key) { - pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL, - pe_order_runnable_left, rsc->cluster); - } - } - - do_crm_log((is_optional? LOG_INFO : LOG_TRACE), - "%s recurring action %s because %s configured for %s role " - "(not %s)", - (is_optional? "Cancelling" : "Ignoring"), key, ID(op), - ((role == NULL)? role2text(RSC_ROLE_UNPROMOTED) : role), - role2text(rsc->next_role)); - free(key); - return; - } - - pe_rsc_trace(rsc, - "Creating %s recurring action %s for %s (%s %s on %s)", - (is_optional? "optional" : "mandatory"), key, - ID(op), rsc->id, role2text(rsc->next_role), - pe__node_name(node)); - - mon = custom_action(rsc, key, name, node, is_optional, TRUE, rsc->cluster); - - if (!pcmk_is_set(start->flags, pe_action_runnable)) { - pe_rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid); - pe__clear_action_flags(mon, pe_action_runnable); - - } else if ((node == NULL) || !node->details->online - || node->details->unclean) { - pe_rsc_trace(rsc, "%s is unrunnable because no node is available", - mon->uuid); - pe__clear_action_flags(mon, pe_action_runnable); - - } else if (!pcmk_is_set(mon->flags, pe_action_optional)) { - pe_rsc_info(rsc, "Start %s-interval %s for %s on %s", - pcmk__readable_interval(interval_ms), mon->task, rsc->id, - pe__node_name(node)); - } - - if (rsc->next_role == RSC_ROLE_PROMOTED) { - pe__add_action_expected_result(mon, CRM_EX_PROMOTED); - } - - // Order monitor relative to other actions - if ((node == NULL) || pcmk_is_set(rsc->flags, pe_rsc_managed)) { - pcmk__new_ordering(rsc, start_key(rsc), NULL, - NULL, strdup(mon->uuid), mon, - pe_order_implies_then|pe_order_runnable_left, - rsc->cluster); - - pcmk__new_ordering(rsc, reload_key(rsc), NULL, - NULL, strdup(mon->uuid), mon, - pe_order_implies_then|pe_order_runnable_left, - rsc->cluster); - - if (rsc->next_role == RSC_ROLE_PROMOTED) { - pcmk__new_ordering(rsc, promote_key(rsc), NULL, - rsc, NULL, mon, - pe_order_optional|pe_order_runnable_left, - rsc->cluster); - - } else if (rsc->role == RSC_ROLE_PROMOTED) { - pcmk__new_ordering(rsc, demote_key(rsc), NULL, - rsc, NULL, mon, - pe_order_optional|pe_order_runnable_left, - rsc->cluster); - } - } -} - -/*! - * \internal - * \brief Cancel a recurring action if running on a node - * - * \param[in,out] rsc Resource that action is for - * \param[in] node Node to cancel action on - * \param[in] key Operation key for action - * \param[in] name Action name - * \param[in] interval_ms Action interval (in milliseconds) - */ -static void -cancel_if_running(pe_resource_t *rsc, const pe_node_t *node, const char *key, - const char *name, guint interval_ms) -{ - GList *possible_matches = find_actions_exact(rsc->actions, key, node); - pe_action_t *cancel_op = NULL; - - if (possible_matches == NULL) { - return; // Recurring action isn't running on this node - } - g_list_free(possible_matches); - - cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node); - - switch (rsc->next_role) { - case RSC_ROLE_STARTED: - case RSC_ROLE_UNPROMOTED: - /* Order starts after cancel. If the current role is - * stopped, this cancels the monitor before the resource - * starts; if the current role is started, then this cancels - * the monitor on a migration target before starting there. - */ - pcmk__new_ordering(rsc, NULL, cancel_op, - rsc, start_key(rsc), NULL, - pe_order_runnable_left, rsc->cluster); - break; - default: - break; - } - pe_rsc_info(rsc, - "Cancelling %s-interval %s action for %s on %s because " - "configured for " RSC_ROLE_STOPPED_S " role (not %s)", - pcmk__readable_interval(interval_ms), name, rsc->id, - pe__node_name(node), role2text(rsc->next_role)); -} - -/*! - * \internal - * \brief Order an action after all probes of a resource on a node - * - * \param[in,out] rsc Resource to check for probes - * \param[in] node Node to check for probes of \p rsc - * \param[in,out] action Action to order after probes of \p rsc on \p node - */ -static void -order_after_probes(pe_resource_t *rsc, const pe_node_t *node, - pe_action_t *action) -{ - GList *probes = pe__resource_actions(rsc, node, RSC_STATUS, FALSE); - - for (GList *iter = probes; iter != NULL; iter = iter->next) { - order_actions((pe_action_t *) iter->data, action, - pe_order_runnable_left); - } - g_list_free(probes); -} - -/*! - * \internal - * \brief Order an action after all stops of a resource on a node - * - * \param[in,out] rsc Resource to check for stops - * \param[in] node Node to check for stops of \p rsc - * \param[in,out] action Action to order after stops of \p rsc on \p node - */ -static void -order_after_stops(pe_resource_t *rsc, const pe_node_t *node, - pe_action_t *action) -{ - GList *stop_ops = pe__resource_actions(rsc, node, RSC_STOP, TRUE); - - for (GList *iter = stop_ops; iter != NULL; iter = iter->next) { - pe_action_t *stop = (pe_action_t *) iter->data; - - if (!pcmk_is_set(stop->flags, pe_action_optional) - && !pcmk_is_set(action->flags, pe_action_optional) - && !pcmk_is_set(rsc->flags, pe_rsc_managed)) { - pe_rsc_trace(rsc, "%s optional on %s: unmanaged", - action->uuid, pe__node_name(node)); - pe__set_action_flags(action, pe_action_optional); - } - - if (!pcmk_is_set(stop->flags, pe_action_runnable)) { - crm_debug("%s unrunnable on %s: stop is unrunnable", - action->uuid, pe__node_name(node)); - pe__clear_action_flags(action, pe_action_runnable); - } - - if (pcmk_is_set(rsc->flags, pe_rsc_managed)) { - pcmk__new_ordering(rsc, stop_key(rsc), stop, - NULL, NULL, action, - pe_order_implies_then|pe_order_runnable_left, - rsc->cluster); - } - } - g_list_free(stop_ops); -} - -/*! - * \internal - * \brief Create recurring action from resource history entry for inactive role - * - * \param[in,out] rsc Resource that resource history is for - * \param[in] node Node that resource will be active on (if any) - * \param[in] op Resource history entry - */ -static void -recurring_op_for_inactive(pe_resource_t *rsc, const pe_node_t *node, - const xmlNode *op) -{ - char *key = NULL; - const char *name = NULL; - const char *role = NULL; - guint interval_ms = 0; - GList *possible_matches = NULL; - - // We're only interested in recurring actions for the inactive role - role = crm_element_value(op, "role"); - if ((role == NULL) || (text2role(role) != RSC_ROLE_STOPPED)) { - return; - } - - if (!is_recurring_history(rsc, op, &key, &interval_ms)) { - return; - } - - if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) { - crm_notice("Ignoring %s (recurring monitors for " RSC_ROLE_STOPPED_S - " role are not supported for anonymous clones)", ID(op)); - return; // @TODO add support - } - - name = crm_element_value(op, "name"); - - pe_rsc_trace(rsc, "Creating recurring action %s for %s on nodes " - "where it should not be running", ID(op), rsc->id); - - for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) { - pe_node_t *stop_node = (pe_node_t *) iter->data; - - bool is_optional = true; - pe_action_t *stopped_mon = NULL; - - // Cancel action on node where resource will be active - if ((node != NULL) - && pcmk__str_eq(stop_node->details->uname, node->details->uname, - pcmk__str_casei)) { - cancel_if_running(rsc, node, key, name, interval_ms); - continue; - } - - // Recurring action on this node is optional if it's already active here - possible_matches = find_actions_exact(rsc->actions, key, stop_node); - is_optional = (possible_matches != NULL); - g_list_free(possible_matches); - - pe_rsc_trace(rsc, - "Creating %s recurring action %s for %s (%s " - RSC_ROLE_STOPPED_S " on %s)", - (is_optional? "optional" : "mandatory"), - key, ID(op), rsc->id, pe__node_name(stop_node)); - - stopped_mon = custom_action(rsc, strdup(key), name, stop_node, - is_optional, TRUE, rsc->cluster); - - pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING); - - if (pcmk_is_set(rsc->flags, pe_rsc_managed)) { - order_after_probes(rsc, stop_node, stopped_mon); - } - - /* The recurring action is for the inactive role, so it shouldn't be - * performed until the resource is inactive. - */ - order_after_stops(rsc, stop_node, stopped_mon); - - if (!stop_node->details->online || stop_node->details->unclean) { - pe_rsc_debug(rsc, "%s unrunnable on %s: node unavailable)", - stopped_mon->uuid, pe__node_name(stop_node)); - pe__clear_action_flags(stopped_mon, pe_action_runnable); - } - - if (pcmk_is_set(stopped_mon->flags, pe_action_runnable) - && !pcmk_is_set(stopped_mon->flags, pe_action_optional)) { - crm_notice("Start recurring %s-interval %s for " - RSC_ROLE_STOPPED_S " %s on %s", - pcmk__readable_interval(interval_ms), stopped_mon->task, - rsc->id, pe__node_name(stop_node)); - } - } - free(key); -} - -/*! - * \internal - * \brief Create recurring actions for a resource - * - * \param[in,out] rsc Resource to create recurring actions for - */ -static void -create_recurring_actions(pe_resource_t *rsc) -{ - pe_action_t *start = NULL; - - if (pcmk_is_set(rsc->flags, pe_rsc_block)) { - pe_rsc_trace(rsc, "Skipping recurring actions for blocked resource %s", - rsc->id); - return; - } - - if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { - pe_rsc_trace(rsc, "Skipping recurring actions for %s " - "in maintenance mode", rsc->id); - return; - } - - if (rsc->allocated_to == NULL) { - // Recurring actions for active roles not needed - - } else if (rsc->allocated_to->details->maintenance) { - pe_rsc_trace(rsc, - "Skipping recurring actions for %s on %s " - "in maintenance mode", - rsc->id, pe__node_name(rsc->allocated_to)); - - } else if ((rsc->next_role != RSC_ROLE_STOPPED) - || !pcmk_is_set(rsc->flags, pe_rsc_managed)) { - // Recurring actions for active roles needed - start = start_action(rsc, rsc->allocated_to, TRUE); - } - - pe_rsc_trace(rsc, "Creating any recurring actions needed for %s", rsc->id); - - for (xmlNode *op = first_named_child(rsc->ops_xml, "op"); - op != NULL; op = crm_next_same_xml(op)) { - - if (start != NULL) { - recurring_op_for_active(rsc, start, rsc->allocated_to, op); - } - recurring_op_for_inactive(rsc, rsc->allocated_to, op); - } -} - static void handle_migration_actions(pe_resource_t * rsc, pe_node_t *current, pe_node_t *chosen, pe_working_set_t * data_set) { pe_action_t *migrate_to = NULL; pe_action_t *migrate_from = NULL; pe_action_t *start = NULL; pe_action_t *stop = NULL; gboolean partial = rsc->partial_migration_target ? TRUE : FALSE; pe_rsc_trace(rsc, "Processing migration actions %s moving from %s to %s . partial migration = %s", rsc->id, current->details->id, chosen->details->id, partial ? "TRUE" : "FALSE"); start = start_action(rsc, chosen, TRUE); stop = stop_action(rsc, current, TRUE); if (partial == FALSE) { migrate_to = custom_action(rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0), RSC_MIGRATE, current, TRUE, TRUE, data_set); } migrate_from = custom_action(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), RSC_MIGRATED, chosen, TRUE, TRUE, data_set); if ((migrate_to && migrate_from) || (migrate_from && partial)) { pe__set_action_flags(start, pe_action_migrate_runnable); pe__set_action_flags(stop, pe_action_migrate_runnable); // This is easier than trying to delete it from the graph pe__set_action_flags(start, pe_action_pseudo); /* order probes before migrations */ if (partial) { pe__set_action_flags(migrate_from, pe_action_migrate_runnable); migrate_from->needs = start->needs; pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL, pe_order_optional, data_set); } else { pe__set_action_flags(migrate_from, pe_action_migrate_runnable); pe__set_action_flags(migrate_to, pe_action_migrate_runnable); migrate_to->needs = start->needs; pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0), NULL, pe_order_optional, data_set); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATE, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL, pe_order_optional|pe_order_implies_first_migratable, data_set); } pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL, pe_order_optional|pe_order_implies_first_migratable, data_set); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_MIGRATED, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL, pe_order_optional|pe_order_implies_first_migratable|pe_order_pseudo_left, data_set); } if (migrate_to) { add_hash_param(migrate_to->meta, XML_LRM_ATTR_MIGRATE_SOURCE, current->details->uname); add_hash_param(migrate_to->meta, XML_LRM_ATTR_MIGRATE_TARGET, chosen->details->uname); /* Pacemaker Remote connections don't require pending to be recorded in * the CIB. We can reduce CIB writes by not setting PENDING for them. */ if (rsc->is_remote_node == FALSE) { /* migrate_to takes place on the source node, but can * have an effect on the target node depending on how * the agent is written. Because of this, we have to maintain * a record that the migrate_to occurred, in case the source node * loses membership while the migrate_to action is still in-flight. */ add_hash_param(migrate_to->meta, XML_OP_ATTR_PENDING, "true"); } } if (migrate_from) { add_hash_param(migrate_from->meta, XML_LRM_ATTR_MIGRATE_SOURCE, current->details->uname); add_hash_param(migrate_from->meta, XML_LRM_ATTR_MIGRATE_TARGET, chosen->details->uname); } } /*! * \internal * \brief Schedule actions to bring resource down and back to current role * * \param[in] rsc Resource to restart * \param[in] current Node that resource should be brought down on * \param[in] chosen Node that resource should be brought up on * \param[in] need_stop Whether the resource must be stopped * \param[in] need_promote Whether the resource must be promoted * * \return Role that resource would have after scheduled actions are taken */ static void schedule_restart_actions(pe_resource_t *rsc, pe_node_t *current, pe_node_t *chosen, bool need_stop, bool need_promote) { enum rsc_role_e role = rsc->role; enum rsc_role_e next_role; pe__set_resource_flags(rsc, pe_rsc_restarting); // Bring resource down to a stop on its current node while (role != RSC_ROLE_STOPPED) { next_role = rsc_state_matrix[role][RSC_ROLE_STOPPED]; pe_rsc_trace(rsc, "Creating %s action to take %s down from %s to %s", (need_stop? "required" : "optional"), rsc->id, role2text(role), role2text(next_role)); if (!rsc_action_matrix[role][next_role](rsc, current, !need_stop)) { break; } role = next_role; } // Bring resource up to its next role on its next node while ((rsc->role <= rsc->next_role) && (role != rsc->role) && !pcmk_is_set(rsc->flags, pe_rsc_block)) { bool required = need_stop; next_role = rsc_state_matrix[role][rsc->role]; if ((next_role == RSC_ROLE_PROMOTED) && need_promote) { required = true; } pe_rsc_trace(rsc, "Creating %s action to take %s up from %s to %s", (required? "required" : "optional"), rsc->id, role2text(role), role2text(next_role)); if (!rsc_action_matrix[role][next_role](rsc, chosen, !required)) { break; } role = next_role; } pe__clear_resource_flags(rsc, pe_rsc_restarting); } void native_create_actions(pe_resource_t *rsc) { pe_action_t *start = NULL; pe_node_t *chosen = NULL; pe_node_t *current = NULL; gboolean need_stop = FALSE; bool need_promote = FALSE; gboolean is_moving = FALSE; gboolean allow_migrate = FALSE; GList *gIter = NULL; unsigned int num_all_active = 0; unsigned int num_clean_active = 0; bool multiply_active = FALSE; enum rsc_role_e role = RSC_ROLE_UNKNOWN; enum rsc_role_e next_role = RSC_ROLE_UNKNOWN; CRM_ASSERT(rsc != NULL); allow_migrate = pcmk_is_set(rsc->flags, pe_rsc_allow_migrate)? TRUE : FALSE; chosen = rsc->allocated_to; next_role = rsc->next_role; if (next_role == RSC_ROLE_UNKNOWN) { pe__set_next_role(rsc, (chosen == NULL)? RSC_ROLE_STOPPED : RSC_ROLE_STARTED, "allocation"); } pe_rsc_trace(rsc, "Creating all actions for %s transition from %s to %s (%s) on %s", rsc->id, role2text(rsc->role), role2text(rsc->next_role), ((next_role == RSC_ROLE_UNKNOWN)? "implicit" : "explicit"), pe__node_name(chosen)); current = pe__find_active_on(rsc, &num_all_active, &num_clean_active); for (gIter = rsc->dangling_migrations; gIter != NULL; gIter = gIter->next) { pe_node_t *dangling_source = (pe_node_t *) gIter->data; pe_action_t *stop = NULL; pe_rsc_trace(rsc, "Creating stop action %sfor %s on %s due to dangling migration", pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)? "and cleanup " : "", rsc->id, pe__node_name(dangling_source)); stop = stop_action(rsc, dangling_source, FALSE); pe__set_action_flags(stop, pe_action_dangle); if (pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)) { DeleteRsc(rsc, dangling_source, FALSE, rsc->cluster); } } if ((num_all_active == 2) && (num_clean_active == 2) && chosen && rsc->partial_migration_source && rsc->partial_migration_target && (current->details == rsc->partial_migration_source->details) && (chosen->details == rsc->partial_migration_target->details)) { /* The chosen node is still the migration target from a partial * migration. Attempt to continue the migration instead of recovering * by stopping the resource everywhere and starting it on a single node. */ pe_rsc_trace(rsc, "Will attempt to continue with partial migration " "to target %s from %s", rsc->partial_migration_target->details->id, rsc->partial_migration_source->details->id); } else if (!pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) { /* If a resource has "requires" set to nothing or quorum, don't consider * it active on unclean nodes (similar to how all resources behave when * stonith-enabled is false). We can start such resources elsewhere * before fencing completes, and if we considered the resource active on * the failed node, we would attempt recovery for being active on * multiple nodes. */ multiply_active = (num_clean_active > 1); } else { multiply_active = (num_all_active > 1); } if (multiply_active) { if (rsc->partial_migration_target && rsc->partial_migration_source) { // Migration was in progress, but we've chosen a different target crm_notice("Resource %s can no longer migrate from %s to %s " "(will stop on both nodes)", rsc->id, pe__node_name(rsc->partial_migration_source), pe__node_name(rsc->partial_migration_target)); multiply_active = false; } else { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); // Resource was (possibly) incorrectly multiply active pe_proc_err("%s resource %s might be active on %u nodes (%s)", pcmk__s(class, "Untyped"), rsc->id, num_all_active, recovery2text(rsc->recovery_type)); crm_notice("See https://wiki.clusterlabs.org/wiki/FAQ#Resource_is_Too_Active for more information"); } switch (rsc->recovery_type) { case recovery_stop_start: need_stop = TRUE; break; case recovery_stop_unexpected: need_stop = TRUE; // StopRsc() will skip expected node pe__set_resource_flags(rsc, pe_rsc_stop_unexpected); break; default: break; } /* If by chance a partial migration is in process, but the migration * target is not chosen still, clear all partial migration data. */ rsc->partial_migration_source = rsc->partial_migration_target = NULL; allow_migrate = FALSE; } if (!multiply_active) { pe__clear_resource_flags(rsc, pe_rsc_stop_unexpected); } if (pcmk_is_set(rsc->flags, pe_rsc_start_pending)) { pe_rsc_trace(rsc, "Creating start action for %s to represent already pending start", rsc->id); start = start_action(rsc, chosen, TRUE); pe__set_action_flags(start, pe_action_print_always); } if (current && chosen && current->details != chosen->details) { pe_rsc_trace(rsc, "Moving %s from %s to %s", rsc->id, pe__node_name(current), pe__node_name(chosen)); is_moving = TRUE; need_stop = TRUE; } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { if (pcmk_is_set(rsc->flags, pe_rsc_stop)) { need_stop = TRUE; pe_rsc_trace(rsc, "Recovering %s", rsc->id); } else { pe_rsc_trace(rsc, "Recovering %s by demotion", rsc->id); if (rsc->next_role == RSC_ROLE_PROMOTED) { need_promote = TRUE; } } } else if (pcmk_is_set(rsc->flags, pe_rsc_block)) { pe_rsc_trace(rsc, "Blocking further actions on %s", rsc->id); need_stop = TRUE; } else if (rsc->role > RSC_ROLE_STARTED && current != NULL && chosen != NULL) { pe_rsc_trace(rsc, "Creating start action for promoted resource %s", rsc->id); start = start_action(rsc, chosen, TRUE); if (!pcmk_is_set(start->flags, pe_action_optional)) { // Recovery of a promoted resource pe_rsc_trace(rsc, "%s restart is required for recovery", rsc->id); need_stop = TRUE; } } /* Create any additional actions required when bringing resource down and * back up to same level. */ schedule_restart_actions(rsc, current, chosen, need_stop, need_promote); /* Required steps from this role to the next */ role = rsc->role; while (role != rsc->next_role) { next_role = rsc_state_matrix[role][rsc->next_role]; pe_rsc_trace(rsc, "Creating action to take %s from %s to %s (ending at %s)", rsc->id, role2text(role), role2text(next_role), role2text(rsc->next_role)); if (!rsc_action_matrix[role][next_role](rsc, chosen, false)) { break; } role = next_role; } - create_recurring_actions(rsc); + pcmk__create_recurring_actions(rsc); /* if we are stuck in a partial migration, where the target * of the partial migration no longer matches the chosen target. * A full stop/start is required */ if (rsc->partial_migration_target && (chosen == NULL || rsc->partial_migration_target->details != chosen->details)) { pe_rsc_trace(rsc, "Not allowing partial migration of %s to continue", rsc->id); allow_migrate = FALSE; } else if (!is_moving || !pcmk_is_set(rsc->flags, pe_rsc_managed) || pcmk_any_flags_set(rsc->flags, pe_rsc_failed|pe_rsc_start_pending) || (current && current->details->unclean) || rsc->next_role < RSC_ROLE_STARTED) { allow_migrate = FALSE; } if (allow_migrate) { handle_migration_actions(rsc, current, chosen, rsc->cluster); } } static void rsc_avoids_remote_nodes(pe_resource_t *rsc) { GHashTableIter iter; pe_node_t *node = NULL; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (node->details->remote_rsc) { node->weight = -INFINITY; } } } /*! * \internal * \brief Return allowed nodes as (possibly sorted) list * * Convert a resource's hash table of allowed nodes to a list. If printing to * stdout, sort the list, to keep action ID numbers consistent for regression * test output (while avoiding the performance hit on a live cluster). * * \param[in] rsc Resource to check for allowed nodes * \param[in] data_set Cluster working set * * \return List of resource's allowed nodes * \note Callers should take care not to rely on the list being sorted. */ static GList * allowed_nodes_as_list(pe_resource_t *rsc, pe_working_set_t *data_set) { GList *allowed_nodes = NULL; if (rsc->allowed_nodes) { allowed_nodes = g_hash_table_get_values(rsc->allowed_nodes); } if (!pcmk__is_daemon) { allowed_nodes = g_list_sort(allowed_nodes, pe__cmp_node_name); } return allowed_nodes; } void native_internal_constraints(pe_resource_t *rsc) { /* This function is on the critical path and worth optimizing as much as possible */ pe_resource_t *top = NULL; GList *allowed_nodes = NULL; bool check_unfencing = FALSE; bool check_utilization = false; if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_rsc_trace(rsc, "Skipping native constraints for unmanaged resource: %s", rsc->id); return; } top = uber_parent(rsc); // Whether resource requires unfencing check_unfencing = !pcmk_is_set(rsc->flags, pe_rsc_fence_device) && pcmk_is_set(rsc->cluster->flags, pe_flag_enable_unfencing) && pcmk_is_set(rsc->flags, pe_rsc_needs_unfencing); // Whether a non-default placement strategy is used check_utilization = (g_hash_table_size(rsc->utilization) > 0) && !pcmk__str_eq(rsc->cluster->placement_strategy, "default", pcmk__str_casei); // Order stops before starts (i.e. restart) pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL, pe_order_optional|pe_order_implies_then|pe_order_restart, rsc->cluster); // Promotable ordering: demote before stop, start before promote if (pcmk_is_set(top->flags, pe_rsc_promotable) || (rsc->role > RSC_ROLE_UNPROMOTED)) { pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_DEMOTE, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL, pe_order_promoted_implies_first, rsc->cluster); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_PROMOTE, 0), NULL, pe_order_runnable_left, rsc->cluster); } // Don't clear resource history if probing on same node pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, CRM_OP_LRM_DELETE, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_STATUS, 0), NULL, pe_order_same_node|pe_order_then_cancels_first, rsc->cluster); // Certain checks need allowed nodes if (check_unfencing || check_utilization || rsc->container) { allowed_nodes = allowed_nodes_as_list(rsc, rsc->cluster); } if (check_unfencing) { /* Check if the node needs to be unfenced first */ for (GList *item = allowed_nodes; item; item = item->next) { pe_node_t *node = item->data; pe_action_t *unfence = pe_fence_op(node, "on", TRUE, NULL, FALSE, rsc->cluster); crm_debug("Ordering any stops of %s before %s, and any starts after", rsc->id, unfence->uuid); /* * It would be more efficient to order clone resources once, * rather than order each instance, but ordering the instance * allows us to avoid unnecessary dependencies that might conflict * with user constraints. * * @TODO: This constraint can still produce a transition loop if the * resource has a stop scheduled on the node being unfenced, and * there is a user ordering constraint to start some other resource * (which will be ordered after the unfence) before stopping this * resource. An example is "start some slow-starting cloned service * before stopping an associated virtual IP that may be moving to * it": * stop this -> unfencing -> start that -> stop this */ pcmk__new_ordering(rsc, stop_key(rsc), NULL, NULL, strdup(unfence->uuid), unfence, pe_order_optional|pe_order_same_node, rsc->cluster); pcmk__new_ordering(NULL, strdup(unfence->uuid), unfence, rsc, start_key(rsc), NULL, pe_order_implies_then_on_node|pe_order_same_node, rsc->cluster); } } if (check_utilization) { pcmk__create_utilization_constraints(rsc, allowed_nodes); } if (rsc->container) { pe_resource_t *remote_rsc = NULL; if (rsc->is_remote_node) { // rsc is the implicit remote connection for a guest or bundle node /* Do not allow a guest resource to live on a Pacemaker Remote node, * to avoid nesting remotes. However, allow bundles to run on remote * nodes. */ if (!pcmk_is_set(rsc->flags, pe_rsc_allow_remote_remotes)) { rsc_avoids_remote_nodes(rsc->container); } /* If someone cleans up a guest or bundle node's container, we will * likely schedule a (re-)probe of the container and recovery of the * connection. Order the connection stop after the container probe, * so that if we detect the container running, we will trigger a new * transition and avoid the unnecessary recovery. */ pcmk__order_resource_actions(rsc->container, RSC_STATUS, rsc, RSC_STOP, pe_order_optional); /* A user can specify that a resource must start on a Pacemaker Remote * node by explicitly configuring it with the container=NODENAME * meta-attribute. This is of questionable merit, since location * constraints can accomplish the same thing. But we support it, so here * we check whether a resource (that is not itself a remote connection) * has container set to a remote node or guest node resource. */ } else if (rsc->container->is_remote_node) { remote_rsc = rsc->container; } else { remote_rsc = pe__resource_contains_guest_node(rsc->cluster, rsc->container); } if (remote_rsc) { /* Force the resource on the Pacemaker Remote node instead of * colocating the resource with the container resource. */ for (GList *item = allowed_nodes; item; item = item->next) { pe_node_t *node = item->data; if (node->details->remote_rsc != remote_rsc) { node->weight = -INFINITY; } } } else { /* This resource is either a filler for a container that does NOT * represent a Pacemaker Remote node, or a Pacemaker Remote * connection resource for a guest node or bundle. */ int score; crm_trace("Order and colocate %s relative to its container %s", rsc->id, rsc->container->id); pcmk__new_ordering(rsc->container, pcmk__op_key(rsc->container->id, RSC_START, 0), NULL, rsc, pcmk__op_key(rsc->id, RSC_START, 0), NULL, pe_order_implies_then|pe_order_runnable_left, rsc->cluster); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, RSC_STOP, 0), NULL, rsc->container, pcmk__op_key(rsc->container->id, RSC_STOP, 0), NULL, pe_order_implies_first, rsc->cluster); if (pcmk_is_set(rsc->flags, pe_rsc_allow_remote_remotes)) { score = 10000; /* Highly preferred but not essential */ } else { score = INFINITY; /* Force them to run on the same host */ } pcmk__new_colocation("resource-with-container", NULL, score, rsc, rsc->container, NULL, NULL, true, rsc->cluster); } } if (rsc->is_remote_node || pcmk_is_set(rsc->flags, pe_rsc_fence_device)) { /* don't allow remote nodes to run stonith devices * or remote connection resources.*/ rsc_avoids_remote_nodes(rsc); } g_list_free(allowed_nodes); } /*! * \internal * \brief Apply a colocation's score to node weights or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node weights (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void pcmk__primitive_apply_coloc_score(pe_resource_t *dependent, pe_resource_t *primary, pcmk__colocation_t *colocation, bool for_dependent) { enum pcmk__coloc_affects filter_results; CRM_CHECK((colocation != NULL) && (dependent != NULL) && (primary != NULL), return); if (for_dependent) { // Always process on behalf of primary resource primary->cmds->apply_coloc_score(dependent, primary, colocation, false); return; } filter_results = pcmk__colocation_affects(dependent, primary, colocation, false); pe_rsc_trace(dependent, "%s %s with %s (%s, score=%d, filter=%d)", ((colocation->score > 0)? "Colocating" : "Anti-colocating"), dependent->id, primary->id, colocation->id, colocation->score, filter_results); switch (filter_results) { case pcmk__coloc_affects_role: pcmk__apply_coloc_to_priority(dependent, primary, colocation); break; case pcmk__coloc_affects_location: pcmk__apply_coloc_to_weights(dependent, primary, colocation); break; default: // pcmk__coloc_affects_nothing return; } } enum pe_action_flags native_action_flags(pe_action_t * action, pe_node_t * node) { return action->flags; } void native_rsc_location(pe_resource_t *rsc, pe__location_t *constraint) { pcmk__apply_location(constraint, rsc); } /*! * \internal * \brief Check whether a node is a multiply active resource's expected node * * \param[in] rsc Resource to check * \param[in] node Node to check * * \return true if \p rsc is multiply active with multiple-active set to * stop_unexpected, and \p node is the node where it will remain active * \note This assumes that the resource's next role cannot be changed to stopped * after this is called, which should be reasonable if status has already * been unpacked and resources have been assigned to nodes. */ static bool is_expected_node(const pe_resource_t *rsc, const pe_node_t *node) { return pcmk_all_flags_set(rsc->flags, pe_rsc_stop_unexpected|pe_rsc_restarting) && (rsc->next_role > RSC_ROLE_STOPPED) && (rsc->allocated_to != NULL) && (node != NULL) && (rsc->allocated_to->details == node->details); } static bool StopRsc(pe_resource_t *rsc, pe_node_t *next, bool optional) { GList *gIter = NULL; CRM_ASSERT(rsc); for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) { pe_node_t *current = (pe_node_t *) gIter->data; pe_action_t *stop; if (is_expected_node(rsc, current)) { /* We are scheduling restart actions for a multiply active resource * with multiple-active=stop_unexpected, and this is where it should * not be stopped. */ pe_rsc_trace(rsc, "Skipping stop of multiply active resource %s " "on expected node %s", rsc->id, pe__node_name(current)); continue; } if (rsc->partial_migration_target) { if (rsc->partial_migration_target->details == current->details // Only if the allocated node still is the migration target. && rsc->allocated_to && rsc->allocated_to->details == rsc->partial_migration_target->details) { pe_rsc_trace(rsc, "Skipping stop of %s on %s " "because migration to %s in progress", rsc->id, pe__node_name(current), pe__node_name(next)); continue; } else { pe_rsc_trace(rsc, "Forcing stop of %s on %s " "because migration target changed", rsc->id, pe__node_name(current)); optional = false; } } pe_rsc_trace(rsc, "Scheduling stop of %s on %s", rsc->id, pe__node_name(current)); stop = stop_action(rsc, current, optional); if(rsc->allocated_to == NULL) { pe_action_set_reason(stop, "node availability", TRUE); } else if (pcmk_all_flags_set(rsc->flags, pe_rsc_restarting |pe_rsc_stop_unexpected)) { /* We are stopping a multiply active resource on a node that is * not its expected node, and we are still scheduling restart * actions, so the stop is for being multiply active. */ pe_action_set_reason(stop, "being multiply active", TRUE); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe__clear_action_flags(stop, pe_action_runnable); } if (pcmk_is_set(rsc->cluster->flags, pe_flag_remove_after_stop)) { DeleteRsc(rsc, current, optional, rsc->cluster); } if (pcmk_is_set(rsc->flags, pe_rsc_needs_unfencing)) { pe_action_t *unfence = pe_fence_op(current, "on", TRUE, NULL, FALSE, rsc->cluster); order_actions(stop, unfence, pe_order_implies_first); if (!pcmk__node_unfenced(current)) { pe_proc_err("Stopping %s until %s can be unfenced", rsc->id, pe__node_name(current)); } } } return true; } static bool StartRsc(pe_resource_t *rsc, pe_node_t *next, bool optional) { pe_action_t *start = NULL; CRM_ASSERT(rsc); pe_rsc_trace(rsc, "Scheduling %s start of %s on %s (weight=%d)", (optional? "optional" : "required"), rsc->id, pe__node_name(next), ((next == NULL)? 0 : next->weight)); start = start_action(rsc, next, TRUE); pcmk__order_vs_unfence(rsc, next, start, pe_order_implies_then); if (pcmk_is_set(start->flags, pe_action_runnable) && !optional) { pe__clear_action_flags(start, pe_action_optional); } if (is_expected_node(rsc, next)) { /* This could be a problem if the start becomes necessary for other * reasons later. */ pe_rsc_trace(rsc, "Start of multiply active resouce %s " "on expected node %s will be a pseudo-action", rsc->id, pe__node_name(next)); pe__set_action_flags(start, pe_action_pseudo); } return true; } static bool PromoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional) { GList *gIter = NULL; gboolean runnable = TRUE; GList *action_list = NULL; CRM_ASSERT(rsc); CRM_CHECK(next != NULL, return false); pe_rsc_trace(rsc, "%s on %s", rsc->id, pe__node_name(next)); action_list = pe__resource_actions(rsc, next, RSC_START, TRUE); for (gIter = action_list; gIter != NULL; gIter = gIter->next) { pe_action_t *start = (pe_action_t *) gIter->data; if (!pcmk_is_set(start->flags, pe_action_runnable)) { runnable = FALSE; } } g_list_free(action_list); if (runnable) { pe_action_t *promote = promote_action(rsc, next, optional); if (is_expected_node(rsc, next)) { /* This could be a problem if the promote becomes necessary for * other reasons later. */ pe_rsc_trace(rsc, "Promotion of multiply active resouce %s " "on expected node %s will be a pseudo-action", rsc->id, pe__node_name(next)); pe__set_action_flags(promote, pe_action_pseudo); } return true; } pe_rsc_debug(rsc, "%s\tPromote %s (canceled)", pe__node_name(next), rsc->id); action_list = pe__resource_actions(rsc, next, RSC_PROMOTE, TRUE); for (gIter = action_list; gIter != NULL; gIter = gIter->next) { pe_action_t *promote = (pe_action_t *) gIter->data; pe__clear_action_flags(promote, pe_action_runnable); } g_list_free(action_list); return true; } static bool DemoteRsc(pe_resource_t *rsc, pe_node_t *next, bool optional) { GList *gIter = NULL; CRM_ASSERT(rsc); if (is_expected_node(rsc, next)) { pe_rsc_trace(rsc, "Skipping demote of multiply active resource %s " "on expected node %s", rsc->id, pe__node_name(next)); return true; } pe_rsc_trace(rsc, "%s", rsc->id); /* CRM_CHECK(rsc->next_role == RSC_ROLE_UNPROMOTED, return FALSE); */ for (gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) { pe_node_t *current = (pe_node_t *) gIter->data; pe_rsc_trace(rsc, "%s on %s", rsc->id, pe__node_name(next)); demote_action(rsc, current, optional); } return true; } static bool RoleError(pe_resource_t *rsc, pe_node_t *next, bool optional) { CRM_ASSERT(rsc); crm_err("%s on %s", rsc->id, pe__node_name(next)); CRM_CHECK(false, return false); return false; } static bool NullOp(pe_resource_t *rsc, pe_node_t *next, bool optional) { CRM_ASSERT(rsc); pe_rsc_trace(rsc, "%s", rsc->id); return FALSE; } gboolean DeleteRsc(pe_resource_t * rsc, pe_node_t * node, gboolean optional, pe_working_set_t * data_set) { if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { pe_rsc_trace(rsc, "Resource %s not deleted from %s: failed", rsc->id, pe__node_name(node)); return FALSE; } else if (node == NULL) { pe_rsc_trace(rsc, "Resource %s not deleted: NULL node", rsc->id); return FALSE; } else if (node->details->unclean || node->details->online == FALSE) { pe_rsc_trace(rsc, "Resource %s not deleted from %s: unrunnable", rsc->id, pe__node_name(node)); return FALSE; } crm_notice("Removing %s from %s", rsc->id, pe__node_name(node)); delete_action(rsc, node, optional); pcmk__order_resource_actions(rsc, RSC_STOP, rsc, RSC_DELETE, optional? pe_order_implies_then : pe_order_optional); pcmk__order_resource_actions(rsc, RSC_DELETE, rsc, RSC_START, optional? pe_order_implies_then : pe_order_optional); return TRUE; } void native_append_meta(pe_resource_t * rsc, xmlNode * xml) { char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION); pe_resource_t *parent; if (value) { char *name = NULL; name = crm_meta_name(XML_RSC_ATTR_INCARNATION); crm_xml_add(xml, name, value); free(name); } value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_REMOTE_NODE); if (value) { char *name = NULL; name = crm_meta_name(XML_RSC_ATTR_REMOTE_NODE); crm_xml_add(xml, name, value); free(name); } for (parent = rsc; parent != NULL; parent = parent->parent) { if (parent->container) { crm_xml_add(xml, CRM_META"_"XML_RSC_ATTR_CONTAINER, parent->container->id); } } } // Primitive implementation of resource_alloc_functions_t:add_utilization() void pcmk__primitive_add_utilization(pe_resource_t *rsc, pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization) { if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { return; } pe_rsc_trace(orig_rsc, "%s: Adding primitive %s as colocated utilization", orig_rsc->id, rsc->id); pcmk__release_node_capacity(utilization, rsc); } /*! * \internal * \brief Get epoch time of node's shutdown attribute (or now if none) * * \param[in] node Node to check * \param[in] data_set Cluster working set * * \return Epoch time corresponding to shutdown attribute if set or now if not */ static time_t shutdown_time(pe_node_t *node, pe_working_set_t *data_set) { const char *shutdown = pe_node_attribute_raw(node, XML_CIB_ATTR_SHUTDOWN); time_t result = 0; if (shutdown != NULL) { long long result_ll; if (pcmk__scan_ll(shutdown, &result_ll, 0LL) == pcmk_rc_ok) { result = (time_t) result_ll; } } return (result == 0)? get_effective_time(data_set) : result; } // Primitive implementation of resource_alloc_functions_t:shutdown_lock() void pcmk__primitive_shutdown_lock(pe_resource_t *rsc) { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); // Fence devices and remote connections can't be locked if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_null_matches) || pe__resource_is_remote_conn(rsc, rsc->cluster)) { return; } if (rsc->lock_node != NULL) { // The lock was obtained from resource history if (rsc->running_on != NULL) { /* The resource was started elsewhere even though it is now * considered locked. This shouldn't be possible, but as a * failsafe, we don't want to disturb the resource now. */ pe_rsc_info(rsc, "Cancelling shutdown lock because %s is already active", rsc->id); pe__clear_resource_history(rsc, rsc->lock_node, rsc->cluster); rsc->lock_node = NULL; rsc->lock_time = 0; } // Only a resource active on exactly one node can be locked } else if (pcmk__list_of_1(rsc->running_on)) { pe_node_t *node = rsc->running_on->data; if (node->details->shutdown) { if (node->details->unclean) { pe_rsc_debug(rsc, "Not locking %s to unclean %s for shutdown", rsc->id, pe__node_name(node)); } else { rsc->lock_node = node; rsc->lock_time = shutdown_time(node, rsc->cluster); } } } if (rsc->lock_node == NULL) { // No lock needed return; } if (rsc->cluster->shutdown_lock > 0) { time_t lock_expiration = rsc->lock_time + rsc->cluster->shutdown_lock; pe_rsc_info(rsc, "Locking %s to %s due to shutdown (expires @%lld)", rsc->id, pe__node_name(rsc->lock_node), (long long) lock_expiration); pe__update_recheck_time(++lock_expiration, rsc->cluster); } else { pe_rsc_info(rsc, "Locking %s to %s due to shutdown", rsc->id, pe__node_name(rsc->lock_node)); } // If resource is locked to one node, ban it from all other nodes for (GList *item = rsc->cluster->nodes; item != NULL; item = item->next) { pe_node_t *node = item->data; if (strcmp(node->details->uname, rsc->lock_node->details->uname)) { resource_location(rsc, node, -CRM_SCORE_INFINITY, XML_CONFIG_ATTR_SHUTDOWN_LOCK, rsc->cluster); } } } diff --git a/lib/pacemaker/pcmk_sched_recurring.c b/lib/pacemaker/pcmk_sched_recurring.c new file mode 100644 index 0000000000..2d4a9687b8 --- /dev/null +++ b/lib/pacemaker/pcmk_sched_recurring.c @@ -0,0 +1,586 @@ +/* + * 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 "libpacemaker_private.h" + +/*! + * \internal + * \brief Parse an interval from XML + * + * \param[in] xml XML containing an interval attribute + * + * \return Interval parsed from XML (or 0 as default) + */ +static guint +xe_interval(const xmlNode *xml) +{ + return crm_parse_interval_spec(crm_element_value(xml, + XML_LRM_ATTR_INTERVAL)); +} + +/*! + * \internal + * \brief Check whether an operation exists multiple times in resource history + * + * \param[in] rsc Resource with history to search + * \param[in] name Name of action to search for + * \param[in] interval_ms Interval (in milliseconds) of action to search for + * + * \return true if an operation with \p name and \p interval_ms exists more than + * once in the operation history of \p rsc, otherwise false + */ +static bool +is_op_dup(const pe_resource_t *rsc, const char *name, guint interval_ms) +{ + const char *id = NULL; + + for (xmlNode *op = first_named_child(rsc->ops_xml, "op"); + op != NULL; op = crm_next_same_xml(op)) { + + // Check whether action name and interval match + if (!pcmk__str_eq(crm_element_value(op, "name"), + name, pcmk__str_none) + || (xe_interval(op) != interval_ms)) { + continue; + } + + if (ID(op) == NULL) { + continue; // Shouldn't be possible + } + + if (id == NULL) { + id = ID(op); // First matching op + } else { + pcmk__config_err("Operation %s is duplicate of %s (do not use " + "same name and interval combination more " + "than once per resource)", ID(op), id); + return true; + } + } + return false; +} + +/*! + * \internal + * \brief Check whether an action name is one that can be recurring + * + * \param[in] name Action name to check + * + * \return true if \p name is an action known to be unsuitable as a recurring + * operation, otherwise false + * + * \note Pacemaker's current philosophy is to allow users to configure recurring + * operations except for a short list of actions known not to be suitable + * for that (as opposed to allowing only actions known to be suitable, + * which includes only monitor). Among other things, this approach allows + * users to define their own custom operations and make them recurring, + * though that use case is not well tested. + */ +static bool +op_cannot_recur(const char *name) +{ + return pcmk__str_any_of(name, RSC_STOP, RSC_START, RSC_DEMOTE, RSC_PROMOTE, + CRMD_ACTION_RELOAD_AGENT, CRMD_ACTION_MIGRATE, + CRMD_ACTION_MIGRATED, NULL); +} + +/*! + * \internal + * \brief Check whether a resource history entry is for a recurring action + * + * \param[in] rsc Resource that history entry is for + * \param[in] op Resource history entry to check + * \param[out] key Will be set to operation key if recurring + * \param[out] interval_ms Will be set to interval from history entry + */ +static bool +is_recurring_history(const pe_resource_t *rsc, const xmlNode *op, char **key, + guint *interval_ms) +{ + const char *name = NULL; + + *interval_ms = xe_interval(op); + if (*interval_ms == 0) { + return false; // Not recurring + } + + if (pcmk__str_empty(ID(op))) { + pcmk__config_err("Ignoring resource history entry without ID"); + return false; // Shouldn't be possible (unless CIB was manually edited) + } + + name = crm_element_value(op, "name"); + if (op_cannot_recur(name)) { + pcmk__config_err("Ignoring %s because action '%s' cannot be recurring", + ID(op), name); + return false; + } + + // There should only be one recurring operation per action/interval + if (is_op_dup(rsc, name, *interval_ms)) { + return false; + } + + // Disabled resources don't get monitored + *key = pcmk__op_key(rsc->id, name, *interval_ms); + if (find_rsc_op_entry(rsc, *key) == NULL) { + crm_trace("Not creating recurring action %s for disabled resource %s", + ID(op), rsc->id); + free(*key); + return false; + } + + return true; +} + +/*! + * \internal + * \brief Check whether a recurring action for an active role should be optional + * + * \param[in] rsc Resource that recurring action is for + * \param[in] node Node that \p rsc will be active on (if any) + * \param[in] key Operation key for recurring action to check + * \param[in] start Start action for \p rsc + * + * \return true if recurring action should be optional, otherwise false + */ +static bool +active_recurring_should_be_optional(const pe_resource_t *rsc, + const pe_node_t *node, const char *key, + pe_action_t *start) +{ + GList *possible_matches = NULL; + + if (node == NULL) { // Should only be possible if unmanaged and stopped + pe_rsc_trace(rsc, "%s will be mandatory because resource is unmanaged", + key); + return false; + } + + if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL), + pe_action_optional)) { + pe_rsc_trace(rsc, "%s will be mandatory because %s is", + key, start->uuid); + return false; + } + + possible_matches = find_actions_exact(rsc->actions, key, node); + if (possible_matches == NULL) { + pe_rsc_trace(rsc, "%s will be mandatory because it is not active on %s", + key, pe__node_name(node)); + return false; + } + + for (GList *iter = possible_matches; iter != NULL; iter = iter->next) { + pe_action_t *op = (pe_action_t *) iter->data; + + if (pcmk_is_set(op->flags, pe_action_reschedule)) { + pe_rsc_trace(rsc, + "%s will be mandatory because " + "it needs to be rescheduled", key); + g_list_free(possible_matches); + return false; + } + } + + g_list_free(possible_matches); + return true; +} + +/*! + * \internal + * \brief Create recurring action from resource history entry for an active role + * + * \param[in,out] rsc Resource that resource history is for + * \param[in] start Start action for \p rsc on \p node + * \param[in] node Node that resource will be active on (if any) + * \param[in] op Resource history entry + */ +static void +recurring_op_for_active(pe_resource_t *rsc, pe_action_t *start, + const pe_node_t *node, const xmlNode *op) +{ + char *key = NULL; + const char *name = NULL; + const char *role = NULL; + + guint interval_ms = 0; + pe_action_t *mon = NULL; + bool is_optional = true; + + // We're only interested in recurring actions for active roles + role = crm_element_value(op, "role"); + if ((role != NULL) && (text2role(role) == RSC_ROLE_STOPPED)) { + return; + } + + if (!is_recurring_history(rsc, op, &key, &interval_ms)) { + return; + } + + name = crm_element_value(op, "name"); + is_optional = active_recurring_should_be_optional(rsc, node, key, start); + + if (((role != NULL) && (rsc->next_role != text2role(role))) + || ((role == NULL) && (rsc->next_role == RSC_ROLE_PROMOTED))) { + // Configured monitor role doesn't match role resource will have + + if (is_optional) { // It's running, so cancel it + char *after_key = NULL; + pe_action_t *cancel_op = pcmk__new_cancel_action(rsc, name, + interval_ms, node); + + switch (rsc->role) { + case RSC_ROLE_UNPROMOTED: + case RSC_ROLE_STARTED: + if (rsc->next_role == RSC_ROLE_PROMOTED) { + after_key = promote_key(rsc); + + } else if (rsc->next_role == RSC_ROLE_STOPPED) { + after_key = stop_key(rsc); + } + + break; + case RSC_ROLE_PROMOTED: + after_key = demote_key(rsc); + break; + default: + break; + } + + if (after_key) { + pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL, + pe_order_runnable_left, rsc->cluster); + } + } + + do_crm_log((is_optional? LOG_INFO : LOG_TRACE), + "%s recurring action %s because %s configured for %s role " + "(not %s)", + (is_optional? "Cancelling" : "Ignoring"), key, ID(op), + ((role == NULL)? role2text(RSC_ROLE_UNPROMOTED) : role), + role2text(rsc->next_role)); + free(key); + return; + } + + pe_rsc_trace(rsc, + "Creating %s recurring action %s for %s (%s %s on %s)", + (is_optional? "optional" : "mandatory"), key, + ID(op), rsc->id, role2text(rsc->next_role), + pe__node_name(node)); + + mon = custom_action(rsc, key, name, node, is_optional, TRUE, rsc->cluster); + + if (!pcmk_is_set(start->flags, pe_action_runnable)) { + pe_rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid); + pe__clear_action_flags(mon, pe_action_runnable); + + } else if ((node == NULL) || !node->details->online + || node->details->unclean) { + pe_rsc_trace(rsc, "%s is unrunnable because no node is available", + mon->uuid); + pe__clear_action_flags(mon, pe_action_runnable); + + } else if (!pcmk_is_set(mon->flags, pe_action_optional)) { + pe_rsc_info(rsc, "Start %s-interval %s for %s on %s", + pcmk__readable_interval(interval_ms), mon->task, rsc->id, + pe__node_name(node)); + } + + if (rsc->next_role == RSC_ROLE_PROMOTED) { + pe__add_action_expected_result(mon, CRM_EX_PROMOTED); + } + + // Order monitor relative to other actions + if ((node == NULL) || pcmk_is_set(rsc->flags, pe_rsc_managed)) { + pcmk__new_ordering(rsc, start_key(rsc), NULL, + NULL, strdup(mon->uuid), mon, + pe_order_implies_then|pe_order_runnable_left, + rsc->cluster); + + pcmk__new_ordering(rsc, reload_key(rsc), NULL, + NULL, strdup(mon->uuid), mon, + pe_order_implies_then|pe_order_runnable_left, + rsc->cluster); + + if (rsc->next_role == RSC_ROLE_PROMOTED) { + pcmk__new_ordering(rsc, promote_key(rsc), NULL, + rsc, NULL, mon, + pe_order_optional|pe_order_runnable_left, + rsc->cluster); + + } else if (rsc->role == RSC_ROLE_PROMOTED) { + pcmk__new_ordering(rsc, demote_key(rsc), NULL, + rsc, NULL, mon, + pe_order_optional|pe_order_runnable_left, + rsc->cluster); + } + } +} + +/*! + * \internal + * \brief Cancel a recurring action if running on a node + * + * \param[in,out] rsc Resource that action is for + * \param[in] node Node to cancel action on + * \param[in] key Operation key for action + * \param[in] name Action name + * \param[in] interval_ms Action interval (in milliseconds) + */ +static void +cancel_if_running(pe_resource_t *rsc, const pe_node_t *node, const char *key, + const char *name, guint interval_ms) +{ + GList *possible_matches = find_actions_exact(rsc->actions, key, node); + pe_action_t *cancel_op = NULL; + + if (possible_matches == NULL) { + return; // Recurring action isn't running on this node + } + g_list_free(possible_matches); + + cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node); + + switch (rsc->next_role) { + case RSC_ROLE_STARTED: + case RSC_ROLE_UNPROMOTED: + /* Order starts after cancel. If the current role is + * stopped, this cancels the monitor before the resource + * starts; if the current role is started, then this cancels + * the monitor on a migration target before starting there. + */ + pcmk__new_ordering(rsc, NULL, cancel_op, + rsc, start_key(rsc), NULL, + pe_order_runnable_left, rsc->cluster); + break; + default: + break; + } + pe_rsc_info(rsc, + "Cancelling %s-interval %s action for %s on %s because " + "configured for " RSC_ROLE_STOPPED_S " role (not %s)", + pcmk__readable_interval(interval_ms), name, rsc->id, + pe__node_name(node), role2text(rsc->next_role)); +} + +/*! + * \internal + * \brief Order an action after all probes of a resource on a node + * + * \param[in,out] rsc Resource to check for probes + * \param[in] node Node to check for probes of \p rsc + * \param[in,out] action Action to order after probes of \p rsc on \p node + */ +static void +order_after_probes(pe_resource_t *rsc, const pe_node_t *node, + pe_action_t *action) +{ + GList *probes = pe__resource_actions(rsc, node, RSC_STATUS, FALSE); + + for (GList *iter = probes; iter != NULL; iter = iter->next) { + order_actions((pe_action_t *) iter->data, action, + pe_order_runnable_left); + } + g_list_free(probes); +} + +/*! + * \internal + * \brief Order an action after all stops of a resource on a node + * + * \param[in,out] rsc Resource to check for stops + * \param[in] node Node to check for stops of \p rsc + * \param[in,out] action Action to order after stops of \p rsc on \p node + */ +static void +order_after_stops(pe_resource_t *rsc, const pe_node_t *node, + pe_action_t *action) +{ + GList *stop_ops = pe__resource_actions(rsc, node, RSC_STOP, TRUE); + + for (GList *iter = stop_ops; iter != NULL; iter = iter->next) { + pe_action_t *stop = (pe_action_t *) iter->data; + + if (!pcmk_is_set(stop->flags, pe_action_optional) + && !pcmk_is_set(action->flags, pe_action_optional) + && !pcmk_is_set(rsc->flags, pe_rsc_managed)) { + pe_rsc_trace(rsc, "%s optional on %s: unmanaged", + action->uuid, pe__node_name(node)); + pe__set_action_flags(action, pe_action_optional); + } + + if (!pcmk_is_set(stop->flags, pe_action_runnable)) { + crm_debug("%s unrunnable on %s: stop is unrunnable", + action->uuid, pe__node_name(node)); + pe__clear_action_flags(action, pe_action_runnable); + } + + if (pcmk_is_set(rsc->flags, pe_rsc_managed)) { + pcmk__new_ordering(rsc, stop_key(rsc), stop, + NULL, NULL, action, + pe_order_implies_then|pe_order_runnable_left, + rsc->cluster); + } + } + g_list_free(stop_ops); +} + +/*! + * \internal + * \brief Create recurring action from resource history entry for inactive role + * + * \param[in,out] rsc Resource that resource history is for + * \param[in] node Node that resource will be active on (if any) + * \param[in] op Resource history entry + */ +static void +recurring_op_for_inactive(pe_resource_t *rsc, const pe_node_t *node, + const xmlNode *op) +{ + char *key = NULL; + const char *name = NULL; + const char *role = NULL; + guint interval_ms = 0; + GList *possible_matches = NULL; + + // We're only interested in recurring actions for the inactive role + role = crm_element_value(op, "role"); + if ((role == NULL) || (text2role(role) != RSC_ROLE_STOPPED)) { + return; + } + + if (!is_recurring_history(rsc, op, &key, &interval_ms)) { + return; + } + + if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) { + crm_notice("Ignoring %s (recurring monitors for " RSC_ROLE_STOPPED_S + " role are not supported for anonymous clones)", ID(op)); + return; // @TODO add support + } + + name = crm_element_value(op, "name"); + + pe_rsc_trace(rsc, "Creating recurring action %s for %s on nodes " + "where it should not be running", ID(op), rsc->id); + + for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) { + pe_node_t *stop_node = (pe_node_t *) iter->data; + + bool is_optional = true; + pe_action_t *stopped_mon = NULL; + + // Cancel action on node where resource will be active + if ((node != NULL) + && pcmk__str_eq(stop_node->details->uname, node->details->uname, + pcmk__str_casei)) { + cancel_if_running(rsc, node, key, name, interval_ms); + continue; + } + + // Recurring action on this node is optional if it's already active here + possible_matches = find_actions_exact(rsc->actions, key, stop_node); + is_optional = (possible_matches != NULL); + g_list_free(possible_matches); + + pe_rsc_trace(rsc, + "Creating %s recurring action %s for %s (%s " + RSC_ROLE_STOPPED_S " on %s)", + (is_optional? "optional" : "mandatory"), + key, ID(op), rsc->id, pe__node_name(stop_node)); + + stopped_mon = custom_action(rsc, strdup(key), name, stop_node, + is_optional, TRUE, rsc->cluster); + + pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING); + + if (pcmk_is_set(rsc->flags, pe_rsc_managed)) { + order_after_probes(rsc, stop_node, stopped_mon); + } + + /* The recurring action is for the inactive role, so it shouldn't be + * performed until the resource is inactive. + */ + order_after_stops(rsc, stop_node, stopped_mon); + + if (!stop_node->details->online || stop_node->details->unclean) { + pe_rsc_debug(rsc, "%s unrunnable on %s: node unavailable)", + stopped_mon->uuid, pe__node_name(stop_node)); + pe__clear_action_flags(stopped_mon, pe_action_runnable); + } + + if (pcmk_is_set(stopped_mon->flags, pe_action_runnable) + && !pcmk_is_set(stopped_mon->flags, pe_action_optional)) { + crm_notice("Start recurring %s-interval %s for " + RSC_ROLE_STOPPED_S " %s on %s", + pcmk__readable_interval(interval_ms), stopped_mon->task, + rsc->id, pe__node_name(stop_node)); + } + } + free(key); +} + +/*! + * \internal + * \brief Create recurring actions for a resource + * + * \param[in,out] rsc Resource to create recurring actions for + */ +void +pcmk__create_recurring_actions(pe_resource_t *rsc) +{ + pe_action_t *start = NULL; + + if (pcmk_is_set(rsc->flags, pe_rsc_block)) { + pe_rsc_trace(rsc, "Skipping recurring actions for blocked resource %s", + rsc->id); + return; + } + + if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { + pe_rsc_trace(rsc, "Skipping recurring actions for %s " + "in maintenance mode", rsc->id); + return; + } + + if (rsc->allocated_to == NULL) { + // Recurring actions for active roles not needed + + } else if (rsc->allocated_to->details->maintenance) { + pe_rsc_trace(rsc, + "Skipping recurring actions for %s on %s " + "in maintenance mode", + rsc->id, pe__node_name(rsc->allocated_to)); + + } else if ((rsc->next_role != RSC_ROLE_STOPPED) + || !pcmk_is_set(rsc->flags, pe_rsc_managed)) { + // Recurring actions for active roles needed + start = start_action(rsc, rsc->allocated_to, TRUE); + } + + pe_rsc_trace(rsc, "Creating any recurring actions needed for %s", rsc->id); + + for (xmlNode *op = first_named_child(rsc->ops_xml, "op"); + op != NULL; op = crm_next_same_xml(op)) { + + if (start != NULL) { + recurring_op_for_active(rsc, start, rsc->allocated_to, op); + } + recurring_op_for_inactive(rsc, rsc->allocated_to, op); + } +}