diff --git a/include/crm/common/scheduler.h b/include/crm/common/scheduler.h index 434e382f40..c4fbfc34c0 100644 --- a/include/crm/common/scheduler.h +++ b/include/crm/common/scheduler.h @@ -1,151 +1,150 @@ /* * Copyright 2004-2024 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__CRM_COMMON_SCHEDULER__H #define PCMK__CRM_COMMON_SCHEDULER__H #include // time_t #include // xmlNode #include // guint, GList, GHashTable #include // crm_time_t #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Scheduler API * \ingroup core */ // NOTE: sbd (as of at least 1.5.2) uses this enum //! Possible responses to loss of quorum enum pe_quorum_policy { pcmk_no_quorum_freeze, //!< Do not recover resources from outside partition pcmk_no_quorum_stop, //!< Stop all resources in partition pcmk_no_quorum_ignore, //!< Act as if partition still holds quorum pcmk_no_quorum_fence, //!< Fence all nodes in partition pcmk_no_quorum_demote, //!< Demote promotable resources and stop all others #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) // NOTE: sbd (as of at least 1.5.2) uses this value //! \deprecated Use pcmk_no_quorum_freeze instead no_quorum_freeze = pcmk_no_quorum_freeze, // NOTE: sbd (as of at least 1.5.2) uses this value //! \deprecated Use pcmk_no_quorum_stop instead no_quorum_stop = pcmk_no_quorum_stop, // NOTE: sbd (as of at least 1.5.2) uses this value //! \deprecated Use pcmk_no_quorum_ignore instead no_quorum_ignore = pcmk_no_quorum_ignore, //! \deprecated Use pcmk_no_quorum_fence instead no_quorum_suicide = pcmk_no_quorum_fence, // NOTE: sbd (as of at least 1.5.2) uses this value //! \deprecated Use pcmk_no_quorum_demote instead no_quorum_demote = pcmk_no_quorum_demote, #endif }; //! \internal Do not use typedef struct pcmk__scheduler_private pcmk__scheduler_private_t; /* Implementation of pcmk_scheduler_t * * @COMPAT Drop this struct once all members are moved to * pcmk__scheduler_private_t, and repoint pcmk_scheduler_t to that */ //!@{ //! \deprecated Do not use (public access will be removed in a future release) struct pcmk__scheduler { // Be careful about when each piece of information is available and final // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Set scheduler input with pcmk_set_scheduler_cib() instead xmlNode *input; // CIB XML // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_get_dc() instead pcmk_node_t *dc_node; // Node object for DC // NOTE: sbd (as of at least 1.5.2) uses this // @COMPAT Change to uint64_t at a compatibility break //! \deprecated Call pcmk_has_quorum() to check quorum unsigned long long flags; // Group of enum pcmk__scheduler_flags // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_get_no_quorum_policy() to get no-quorum policy enum pe_quorum_policy no_quorum_policy; // Response to loss of quorum // NOTE: sbd (as of at least 1.5.2) uses this //! \deprecated Call pcmk_find_node() to find a node instead GList *nodes; // Nodes in cluster - GList *placement_constraints; // Location constraints GList *ordering_constraints; // Ordering constraints GList *colocation_constraints; // Colocation constraints // Ticket constraints unpacked by libpacemaker GList *ticket_constraints; xmlNode *failed; // History entries of failed actions xmlNode *op_defaults; // Configured operation defaults xmlNode *rsc_defaults; // Configured resource defaults int num_synapse; // Number of transition graph synapses int max_valid_nodes; // \deprecated Do not use int order_id; // ID to use for next created ordering int action_id; // ID to use for next created action xmlNode *graph; // Transition graph GHashTable *template_rsc_sets; // Mappings of template ID to resource ID // @COMPAT Replace this with a fencer variable (only place it's used) const char *localhost; // \deprecated Do not use GHashTable *tags; // Configuration tags (ID -> pcmk__idref_t*) int blocked_resources; // Number of blocked resources in cluster int disabled_resources; // Number of disabled resources in cluster GList *param_check; // History entries that need to be checked GList *stop_needed; // Containers that need stop actions time_t recheck_by; // Hint to controller when to reschedule int ninstances; // Total number of resource instances guint shutdown_lock; // How long to lock resources (seconds) int priority_fencing_delay; // Priority fencing delay pcmk__scheduler_private_t *priv; // For Pacemaker use only guint node_pending_timeout; // Pending join times out after this (ms) }; //!@} pcmk_node_t *pcmk_get_dc(const pcmk_scheduler_t *scheduler); enum pe_quorum_policy pcmk_get_no_quorum_policy(const pcmk_scheduler_t *scheduler); int pcmk_set_scheduler_cib(pcmk_scheduler_t *scheduler, xmlNode *cib); bool pcmk_has_quorum(const pcmk_scheduler_t *scheduler); pcmk_node_t *pcmk_find_node(const pcmk_scheduler_t *scheduler, const char *node_name); #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_SCHEDULER__H diff --git a/include/crm/common/scheduler_internal.h b/include/crm/common/scheduler_internal.h index 6969c3b5e1..2a0778716d 100644 --- a/include/crm/common/scheduler_internal.h +++ b/include/crm/common/scheduler_internal.h @@ -1,273 +1,274 @@ /* * Copyright 2004-2024 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__CRM_COMMON_SCHEDULER_INTERNAL__H #define PCMK__CRM_COMMON_SCHEDULER_INTERNAL__H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif enum pcmk__check_parameters { /* Clear fail count if parameters changed for un-expired start or monitor * last_failure. */ pcmk__check_last_failure, /* Clear fail count if parameters changed for start, monitor, promote, or * migrate_from actions for active resources. */ pcmk__check_active, }; // Scheduling options and conditions enum pcmk__scheduler_flags { // No scheduler flags set (compare with equality rather than bit set) pcmk__sched_none = 0ULL, /* These flags are dynamically determined conditions */ // Whether partition has quorum (via \c PCMK_XA_HAVE_QUORUM attribute) //! \deprecated Call pcmk_has_quorum() to check quorum instead pcmk__sched_quorate = (1ULL << 0), // Whether cluster is symmetric (via symmetric-cluster property) pcmk__sched_symmetric_cluster = (1ULL << 1), // Whether scheduling encountered a non-configuration error pcmk__sched_processing_error = (1ULL << 2), // Whether cluster is in maintenance mode (via maintenance-mode property) pcmk__sched_in_maintenance = (1ULL << 3), // Whether fencing is enabled (via stonith-enabled property) pcmk__sched_fencing_enabled = (1ULL << 4), // Whether cluster has a fencing resource (via CIB resources) /*! \deprecated To indicate the cluster has a fencing resource, add either a * fencing resource configuration or the have-watchdog cluster option to the * input CIB */ pcmk__sched_have_fencing = (1ULL << 5), // Whether any resource provides or requires unfencing (via CIB resources) pcmk__sched_enable_unfencing = (1ULL << 6), // Whether concurrent fencing is allowed (via concurrent-fencing property) pcmk__sched_concurrent_fencing = (1ULL << 7), /* * Whether resources removed from the configuration should be stopped (via * stop-orphan-resources property) */ pcmk__sched_stop_removed_resources = (1ULL << 8), /* * Whether recurring actions removed from the configuration should be * cancelled (via stop-orphan-actions property) */ pcmk__sched_cancel_removed_actions = (1ULL << 9), // Whether to stop all resources (via stop-all-resources property) pcmk__sched_stop_all = (1ULL << 10), // Whether scheduler processing encountered a warning pcmk__sched_processing_warning = (1ULL << 11), /* * Whether start failure should be treated as if * \c PCMK_META_MIGRATION_THRESHOLD is 1 (via * \c PCMK_OPT_START_FAILURE_IS_FATAL property) */ pcmk__sched_start_failure_fatal = (1ULL << 12), // Unused pcmk__sched_remove_after_stop = (1ULL << 13), // Whether unseen nodes should be fenced (via startup-fencing property) pcmk__sched_startup_fencing = (1ULL << 14), /* * Whether resources should be left stopped when their node shuts down * cleanly (via shutdown-lock property) */ pcmk__sched_shutdown_lock = (1ULL << 15), /* * Whether resources' current state should be probed (when unknown) before * scheduling any other actions (via the enable-startup-probes property) */ pcmk__sched_probe_resources = (1ULL << 16), // Whether the CIB status section has been parsed yet pcmk__sched_have_status = (1ULL << 17), // Whether the cluster includes any Pacemaker Remote nodes (via CIB) pcmk__sched_have_remote_nodes = (1ULL << 18), /* The remaining flags are scheduling options that must be set explicitly */ /* * Whether to skip unpacking the CIB status section and stop the scheduling * sequence after applying node-specific location criteria (skipping * assignment, ordering, actions, etc.). */ pcmk__sched_location_only = (1ULL << 20), // Whether sensitive resource attributes have been masked pcmk__sched_sanitized = (1ULL << 21), // Skip counting of total, disabled, and blocked resource instances pcmk__sched_no_counts = (1ULL << 23), /* * Skip deprecated code kept solely for backward API compatibility * (internal code should always set this) */ pcmk__sched_no_compat = (1ULL << 24), // Whether node scores should be output instead of logged pcmk__sched_output_scores = (1ULL << 25), // Whether to show node and resource utilization (in log or output) pcmk__sched_show_utilization = (1ULL << 26), /* * Whether to stop the scheduling sequence after unpacking the CIB, * calculating cluster status, and applying node health (skipping * applying node-specific location criteria, assignment, etc.) */ pcmk__sched_validate_only = (1ULL << 27), }; // Implementation of pcmk__scheduler_private_t struct pcmk__scheduler_private { // Be careful about when each piece of information is available and final crm_time_t *now; // Time to use when evaluating rules pcmk__output_t *out; // Output object for displaying messages GHashTable *options; // Cluster options const char *fence_action; // Default fencing action int fence_timeout_ms; // Value of stonith-timeout property in ms const char *placement_strategy; // Value of placement-strategy property GList *resources; // Resources in cluster GList *actions; // All scheduled actions GHashTable *singletons; // Scheduled non-resource actions + GList *location_constraints; // Location constraints GHashTable *ticket_constraints; // Key = ticket ID, value = pcmk__ticket_t }; // Group of enum pcmk__warnings flags for warnings we want to log once extern uint32_t pcmk__warnings; /*! * \internal * \brief Log a resource-tagged message at info severity * * \param[in] rsc Tag message with this resource's ID * \param[in] fmt... printf(3)-style format and arguments */ #define pcmk__rsc_info(rsc, fmt, args...) \ crm_log_tag(LOG_INFO, ((rsc) == NULL)? "" : (rsc)->id, (fmt), ##args) /*! * \internal * \brief Log a resource-tagged message at debug severity * * \param[in] rsc Tag message with this resource's ID * \param[in] fmt... printf(3)-style format and arguments */ #define pcmk__rsc_debug(rsc, fmt, args...) \ crm_log_tag(LOG_DEBUG, ((rsc) == NULL)? "" : (rsc)->id, (fmt), ##args) /*! * \internal * \brief Log a resource-tagged message at trace severity * * \param[in] rsc Tag message with this resource's ID * \param[in] fmt... printf(3)-style format and arguments */ #define pcmk__rsc_trace(rsc, fmt, args...) \ crm_log_tag(LOG_TRACE, ((rsc) == NULL)? "" : (rsc)->id, (fmt), ##args) /*! * \internal * \brief Log an error and remember that current scheduler input has errors * * \param[in,out] scheduler Scheduler data * \param[in] fmt... printf(3)-style format and arguments */ #define pcmk__sched_err(scheduler, fmt...) do { \ pcmk__set_scheduler_flags((scheduler), \ pcmk__sched_processing_error); \ crm_err(fmt); \ } while (0) /*! * \internal * \brief Log a warning and remember that current scheduler input has warnings * * \param[in,out] scheduler Scheduler data * \param[in] fmt... printf(3)-style format and arguments */ #define pcmk__sched_warn(scheduler, fmt...) do { \ pcmk__set_scheduler_flags((scheduler), \ pcmk__sched_processing_warning); \ crm_warn(fmt); \ } while (0) /*! * \internal * \brief Set scheduler flags * * \param[in,out] scheduler Scheduler data * \param[in] flags_to_set Group of enum pcmk__scheduler_flags to set */ #define pcmk__set_scheduler_flags(scheduler, flags_to_set) do { \ (scheduler)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Scheduler", crm_system_name, \ (scheduler)->flags, (flags_to_set), #flags_to_set); \ } while (0) /*! * \internal * \brief Clear scheduler flags * * \param[in,out] scheduler Scheduler data * \param[in] flags_to_clear Group of enum pcmk__scheduler_flags to clear */ #define pcmk__clear_scheduler_flags(scheduler, flags_to_clear) do { \ (scheduler)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Scheduler", crm_system_name, \ (scheduler)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_SCHEDULER_INTERNAL__H diff --git a/lib/pacemaker/pcmk_sched_colocation.c b/lib/pacemaker/pcmk_sched_colocation.c index fbfcdd19bf..8e3f38da07 100644 --- a/lib/pacemaker/pcmk_sched_colocation.c +++ b/lib/pacemaker/pcmk_sched_colocation.c @@ -1,1997 +1,1997 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include "crm/common/util.h" #include "crm/common/xml_internal.h" #include "crm/common/xml.h" #include "libpacemaker_private.h" // Used to temporarily mark a node as unusable #define INFINITY_HACK (PCMK_SCORE_INFINITY * -100) /*! * \internal * \brief Get the value of a colocation's node attribute * * \param[in] node Node on which to look up the attribute * \param[in] attr Name of attribute to look up * \param[in] rsc Resource on whose behalf to look up the attribute * * \return Value of \p attr on \p node or on the host of \p node, as appropriate */ const char * pcmk__colocation_node_attr(const pcmk_node_t *node, const char *attr, const pcmk_resource_t *rsc) { const char *target = NULL; /* A resource colocated with a bundle or its primitive can't run on the * bundle node itself (where only the primitive, if any, can run). Instead, * we treat it as a colocation with the bundle's containers, so always look * up colocation node attributes on the container host. */ if (pcmk__is_bundle_node(node) && pcmk__is_bundled(rsc) && (pe__const_top_resource(rsc, false) == pe__bundled_resource(rsc))) { target = PCMK_VALUE_HOST; } else if (rsc != NULL) { target = g_hash_table_lookup(rsc->priv->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET); } return pcmk__node_attr(node, attr, target, pcmk__rsc_node_assigned); } /*! * \internal * \brief Compare two colocations according to priority * * Compare two colocations according to the order in which they should be * considered, based on either their dependent resources or their primary * resources -- preferring (in order): * * Colocation that is not \c NULL * * Colocation whose resource has higher priority * * Colocation whose resource is of a higher-level variant * (bundle > clone > group > primitive) * * Colocation whose resource is promotable, if both are clones * * Colocation whose resource has lower ID in lexicographic order * * \param[in] colocation1 First colocation to compare * \param[in] colocation2 Second colocation to compare * \param[in] dependent If \c true, compare colocations by dependent * priority; otherwise compare them by primary priority * * \return A negative number if \p colocation1 should be considered first, * a positive number if \p colocation2 should be considered first, * or 0 if order doesn't matter */ static gint cmp_colocation_priority(const pcmk__colocation_t *colocation1, const pcmk__colocation_t *colocation2, bool dependent) { const pcmk_resource_t *rsc1 = NULL; const pcmk_resource_t *rsc2 = NULL; if (colocation1 == NULL) { return 1; } if (colocation2 == NULL) { return -1; } if (dependent) { rsc1 = colocation1->dependent; rsc2 = colocation2->dependent; CRM_ASSERT(colocation1->primary != NULL); } else { rsc1 = colocation1->primary; rsc2 = colocation2->primary; CRM_ASSERT(colocation1->dependent != NULL); } CRM_ASSERT((rsc1 != NULL) && (rsc2 != NULL)); if (rsc1->priv->priority > rsc2->priv->priority) { return -1; } if (rsc1->priv->priority < rsc2->priv->priority) { return 1; } // Process clones before primitives and groups if (rsc1->priv->variant > rsc2->priv->variant) { return -1; } if (rsc1->priv->variant < rsc2->priv->variant) { return 1; } /* @COMPAT scheduler <2.0.0: Process promotable clones before nonpromotable * clones (probably unnecessary, but avoids having to update regression * tests) */ if (pcmk__is_clone(rsc1)) { if (pcmk_is_set(rsc1->flags, pcmk__rsc_promotable) && !pcmk_is_set(rsc2->flags, pcmk__rsc_promotable)) { return -1; } if (!pcmk_is_set(rsc1->flags, pcmk__rsc_promotable) && pcmk_is_set(rsc2->flags, pcmk__rsc_promotable)) { return 1; } } return strcmp(rsc1->id, rsc2->id); } /*! * \internal * \brief Compare two colocations according to priority based on dependents * * Compare two colocations according to the order in which they should be * considered, based on their dependent resources -- preferring (in order): * * Colocation that is not \c NULL * * Colocation whose resource has higher priority * * Colocation whose resource is of a higher-level variant * (bundle > clone > group > primitive) * * Colocation whose resource is promotable, if both are clones * * Colocation whose resource has lower ID in lexicographic order * * \param[in] a First colocation to compare * \param[in] b Second colocation to compare * * \return A negative number if \p a should be considered first, * a positive number if \p b should be considered first, * or 0 if order doesn't matter */ static gint cmp_dependent_priority(gconstpointer a, gconstpointer b) { return cmp_colocation_priority(a, b, true); } /*! * \internal * \brief Compare two colocations according to priority based on primaries * * Compare two colocations according to the order in which they should be * considered, based on their primary resources -- preferring (in order): * * Colocation that is not \c NULL * * Colocation whose primary has higher priority * * Colocation whose primary is of a higher-level variant * (bundle > clone > group > primitive) * * Colocation whose primary is promotable, if both are clones * * Colocation whose primary has lower ID in lexicographic order * * \param[in] a First colocation to compare * \param[in] b Second colocation to compare * * \return A negative number if \p a should be considered first, * a positive number if \p b should be considered first, * or 0 if order doesn't matter */ static gint cmp_primary_priority(gconstpointer a, gconstpointer b) { return cmp_colocation_priority(a, b, false); } /*! * \internal * \brief Add a "this with" colocation constraint to a sorted list * * \param[in,out] list List of constraints to add \p colocation to * \param[in] colocation Colocation constraint to add to \p list * \param[in] rsc Resource whose colocations we're getting (for * logging only) * * \note The list will be sorted using cmp_primary_priority(). */ void pcmk__add_this_with(GList **list, const pcmk__colocation_t *colocation, const pcmk_resource_t *rsc) { CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL)); pcmk__rsc_trace(rsc, "Adding colocation %s (%s with %s using %s @%s) to " "'this with' list for %s", colocation->id, colocation->dependent->id, colocation->primary->id, colocation->node_attribute, pcmk_readable_score(colocation->score), rsc->id); *list = g_list_insert_sorted(*list, (gpointer) colocation, cmp_primary_priority); } /*! * \internal * \brief Add a list of "this with" colocation constraints to a list * * \param[in,out] list List of constraints to add \p addition to * \param[in] addition List of colocation constraints to add to \p list * \param[in] rsc Resource whose colocations we're getting (for * logging only) * * \note The lists must be pre-sorted by cmp_primary_priority(). */ void pcmk__add_this_with_list(GList **list, GList *addition, const pcmk_resource_t *rsc) { CRM_ASSERT((list != NULL) && (rsc != NULL)); pcmk__if_tracing( {}, // Always add each colocation individually if tracing { if (*list == NULL) { // Trivial case for efficiency if not tracing *list = g_list_copy(addition); return; } } ); for (const GList *iter = addition; iter != NULL; iter = iter->next) { pcmk__add_this_with(list, addition->data, rsc); } } /*! * \internal * \brief Add a "with this" colocation constraint to a sorted list * * \param[in,out] list List of constraints to add \p colocation to * \param[in] colocation Colocation constraint to add to \p list * \param[in] rsc Resource whose colocations we're getting (for * logging only) * * \note The list will be sorted using cmp_dependent_priority(). */ void pcmk__add_with_this(GList **list, const pcmk__colocation_t *colocation, const pcmk_resource_t *rsc) { CRM_ASSERT((list != NULL) && (colocation != NULL) && (rsc != NULL)); pcmk__rsc_trace(rsc, "Adding colocation %s (%s with %s using %s @%s) to " "'with this' list for %s", colocation->id, colocation->dependent->id, colocation->primary->id, colocation->node_attribute, pcmk_readable_score(colocation->score), rsc->id); *list = g_list_insert_sorted(*list, (gpointer) colocation, cmp_dependent_priority); } /*! * \internal * \brief Add a list of "with this" colocation constraints to a list * * \param[in,out] list List of constraints to add \p addition to * \param[in] addition List of colocation constraints to add to \p list * \param[in] rsc Resource whose colocations we're getting (for * logging only) * * \note The lists must be pre-sorted by cmp_dependent_priority(). */ void pcmk__add_with_this_list(GList **list, GList *addition, const pcmk_resource_t *rsc) { CRM_ASSERT((list != NULL) && (rsc != NULL)); pcmk__if_tracing( {}, // Always add each colocation individually if tracing { if (*list == NULL) { // Trivial case for efficiency if not tracing *list = g_list_copy(addition); return; } } ); for (const GList *iter = addition; iter != NULL; iter = iter->next) { pcmk__add_with_this(list, addition->data, rsc); } } /*! * \internal * \brief Add orderings necessary for an anti-colocation constraint * * \param[in,out] first_rsc One resource in an anti-colocation * \param[in] first_role Anti-colocation role of \p first_rsc * \param[in] then_rsc Other resource in the anti-colocation * \param[in] then_role Anti-colocation role of \p then_rsc */ static void anti_colocation_order(pcmk_resource_t *first_rsc, int first_role, pcmk_resource_t *then_rsc, int then_role) { const char *first_tasks[] = { NULL, NULL }; const char *then_tasks[] = { NULL, NULL }; /* Actions to make first_rsc lose first_role */ if (first_role == pcmk_role_promoted) { first_tasks[0] = PCMK_ACTION_DEMOTE; } else { first_tasks[0] = PCMK_ACTION_STOP; if (first_role == pcmk_role_unpromoted) { first_tasks[1] = PCMK_ACTION_PROMOTE; } } /* Actions to make then_rsc gain then_role */ if (then_role == pcmk_role_promoted) { then_tasks[0] = PCMK_ACTION_PROMOTE; } else { then_tasks[0] = PCMK_ACTION_START; if (then_role == pcmk_role_unpromoted) { then_tasks[1] = PCMK_ACTION_DEMOTE; } } for (int first_lpc = 0; (first_lpc <= 1) && (first_tasks[first_lpc] != NULL); first_lpc++) { for (int then_lpc = 0; (then_lpc <= 1) && (then_tasks[then_lpc] != NULL); then_lpc++) { pcmk__order_resource_actions(first_rsc, first_tasks[first_lpc], then_rsc, then_tasks[then_lpc], pcmk__ar_if_required_on_same_node); } } } /*! * \internal * \brief Add a new colocation constraint to scheduler data * * \param[in] id XML ID for this constraint * \param[in] node_attr Colocate by this attribute (NULL for #uname) * \param[in] score Constraint score * \param[in,out] dependent Resource to be colocated * \param[in,out] primary Resource to colocate \p dependent with * \param[in] dependent_role Current role of \p dependent * \param[in] primary_role Current role of \p primary * \param[in] flags Group of enum pcmk__coloc_flags */ void pcmk__new_colocation(const char *id, const char *node_attr, int score, pcmk_resource_t *dependent, pcmk_resource_t *primary, const char *dependent_role, const char *primary_role, uint32_t flags) { pcmk__colocation_t *new_con = NULL; CRM_CHECK(id != NULL, return); if ((dependent == NULL) || (primary == NULL)) { pcmk__config_err("Ignoring colocation '%s' because resource " "does not exist", id); return; } if (score == 0) { pcmk__rsc_trace(dependent, "Ignoring colocation '%s' (%s with %s) because score is 0", id, dependent->id, primary->id); return; } new_con = pcmk__assert_alloc(1, sizeof(pcmk__colocation_t)); if (pcmk__str_eq(dependent_role, PCMK_ROLE_STARTED, pcmk__str_null_matches|pcmk__str_casei)) { dependent_role = PCMK__ROLE_UNKNOWN; } if (pcmk__str_eq(primary_role, PCMK_ROLE_STARTED, pcmk__str_null_matches|pcmk__str_casei)) { primary_role = PCMK__ROLE_UNKNOWN; } new_con->id = id; new_con->dependent = dependent; new_con->primary = primary; new_con->score = score; new_con->dependent_role = pcmk_parse_role(dependent_role); new_con->primary_role = pcmk_parse_role(primary_role); new_con->node_attribute = pcmk__s(node_attr, CRM_ATTR_UNAME); new_con->flags = flags; pcmk__add_this_with(&(dependent->priv->this_with_colocations), new_con, dependent); pcmk__add_with_this(&(primary->priv->with_this_colocations), new_con, primary); dependent->priv->scheduler->colocation_constraints = g_list_prepend(dependent->priv->scheduler->colocation_constraints, new_con); if (score <= -PCMK_SCORE_INFINITY) { anti_colocation_order(dependent, new_con->dependent_role, primary, new_con->primary_role); anti_colocation_order(primary, new_con->primary_role, dependent, new_con->dependent_role); } } /*! * \internal * \brief Return the boolean influence corresponding to configuration * * \param[in] coloc_id Colocation XML ID (for error logging) * \param[in] rsc Resource involved in constraint (for default) * \param[in] influence_s String value of \c PCMK_XA_INFLUENCE option * * \return \c pcmk__coloc_influence if string evaluates true, or string is * \c NULL or invalid and resource's \c PCMK_META_CRITICAL option * evaluates true, otherwise \c pcmk__coloc_none */ static uint32_t unpack_influence(const char *coloc_id, const pcmk_resource_t *rsc, const char *influence_s) { if (influence_s != NULL) { int influence_i = 0; if (crm_str_to_boolean(influence_s, &influence_i) < 0) { pcmk__config_err("Constraint '%s' has invalid value for " PCMK_XA_INFLUENCE " (using default)", coloc_id); } else { return (influence_i == 0)? pcmk__coloc_none : pcmk__coloc_influence; } } if (pcmk_is_set(rsc->flags, pcmk__rsc_critical)) { return pcmk__coloc_influence; } return pcmk__coloc_none; } static void unpack_colocation_set(xmlNode *set, int score, const char *coloc_id, const char *influence_s, pcmk_scheduler_t *scheduler) { xmlNode *xml_rsc = NULL; pcmk_resource_t *other = NULL; pcmk_resource_t *resource = NULL; const char *set_id = pcmk__xe_id(set); const char *role = crm_element_value(set, PCMK_XA_ROLE); bool with_previous = false; int local_score = score; bool sequential = false; uint32_t flags = pcmk__coloc_none; const char *xml_rsc_id = NULL; const char *score_s = crm_element_value(set, PCMK_XA_SCORE); if (score_s) { local_score = char2score(score_s); } if (local_score == 0) { crm_trace("Ignoring colocation '%s' for set '%s' because score is 0", coloc_id, set_id); return; } /* @COMPAT The deprecated PCMK__XA_ORDERING attribute specifies whether * resources in a positive-score set are colocated with the previous or next * resource. */ if (pcmk__str_eq(crm_element_value(set, PCMK__XA_ORDERING), PCMK__VALUE_GROUP, pcmk__str_null_matches|pcmk__str_casei)) { with_previous = true; } else { pcmk__warn_once(pcmk__wo_set_ordering, "Support for '" PCMK__XA_ORDERING "' other than" " '" PCMK__VALUE_GROUP "' in " PCMK_XE_RESOURCE_SET " (such as %s) is deprecated and will be removed in a" " future release", set_id); } if ((pcmk__xe_get_bool_attr(set, PCMK_XA_SEQUENTIAL, &sequential) == pcmk_rc_ok) && !sequential) { return; } if (local_score > 0) { for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xml_rsc_id = pcmk__xe_id(xml_rsc); resource = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (resource == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring %s and later resources in set %s: " "No such resource", xml_rsc_id, set_id); return; } if (other != NULL) { flags = pcmk__coloc_explicit | unpack_influence(coloc_id, resource, influence_s); if (with_previous) { pcmk__rsc_trace(resource, "Colocating %s with %s in set %s", resource->id, other->id, set_id); pcmk__new_colocation(set_id, NULL, local_score, resource, other, role, role, flags); } else { pcmk__rsc_trace(resource, "Colocating %s with %s in set %s", other->id, resource->id, set_id); pcmk__new_colocation(set_id, NULL, local_score, other, resource, role, role, flags); } } other = resource; } } else { /* Anti-colocating with every prior resource is * the only way to ensure the intuitive result * (i.e. that no one in the set can run with anyone else in the set) */ for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xmlNode *xml_rsc_with = NULL; xml_rsc_id = pcmk__xe_id(xml_rsc); resource = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (resource == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring %s and later resources in set %s: " "No such resource", xml_rsc_id, set_id); return; } flags = pcmk__coloc_explicit | unpack_influence(coloc_id, resource, influence_s); for (xml_rsc_with = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc_with != NULL; xml_rsc_with = pcmk__xe_next_same(xml_rsc_with)) { xml_rsc_id = pcmk__xe_id(xml_rsc_with); if (pcmk__str_eq(resource->id, xml_rsc_id, pcmk__str_none)) { break; } other = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); CRM_ASSERT(other != NULL); // We already processed it pcmk__new_colocation(set_id, NULL, local_score, resource, other, role, role, flags); } } } } /*! * \internal * \brief Colocate two resource sets relative to each other * * \param[in] id Colocation XML ID * \param[in] set1 Dependent set * \param[in] set2 Primary set * \param[in] score Colocation score * \param[in] influence_s Value of colocation's \c PCMK_XA_INFLUENCE * attribute * \param[in,out] scheduler Scheduler data */ static void colocate_rsc_sets(const char *id, const xmlNode *set1, const xmlNode *set2, int score, const char *influence_s, pcmk_scheduler_t *scheduler) { xmlNode *xml_rsc = NULL; pcmk_resource_t *rsc_1 = NULL; pcmk_resource_t *rsc_2 = NULL; const char *xml_rsc_id = NULL; const char *role_1 = crm_element_value(set1, PCMK_XA_ROLE); const char *role_2 = crm_element_value(set2, PCMK_XA_ROLE); int rc = pcmk_rc_ok; bool sequential = false; uint32_t flags = pcmk__coloc_none; if (score == 0) { crm_trace("Ignoring colocation '%s' between sets %s and %s " "because score is 0", id, pcmk__xe_id(set1), pcmk__xe_id(set2)); return; } rc = pcmk__xe_get_bool_attr(set1, PCMK_XA_SEQUENTIAL, &sequential); if ((rc != pcmk_rc_ok) || sequential) { // Get the first one xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL); if (xml_rsc != NULL) { xml_rsc_id = pcmk__xe_id(xml_rsc); rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_1 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring colocation of set %s with set %s " "because first resource %s not found", pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id); return; } } } rc = pcmk__xe_get_bool_attr(set2, PCMK_XA_SEQUENTIAL, &sequential); if ((rc != pcmk_rc_ok) || sequential) { // Get the last one for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xml_rsc_id = pcmk__xe_id(xml_rsc); } rsc_2 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_2 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring colocation of set %s with set %s " "because last resource %s not found", pcmk__xe_id(set1), pcmk__xe_id(set2), xml_rsc_id); return; } } if ((rsc_1 != NULL) && (rsc_2 != NULL)) { // Both sets are sequential flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s); pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2, flags); } else if (rsc_1 != NULL) { // Only set1 is sequential flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s); for (xml_rsc = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xml_rsc_id = pcmk__xe_id(xml_rsc); rsc_2 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_2 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring set %s colocation with resource %s " "in set %s: No such resource", pcmk__xe_id(set1), xml_rsc_id, pcmk__xe_id(set2)); continue; } pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2, flags); } } else if (rsc_2 != NULL) { // Only set2 is sequential for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xml_rsc_id = pcmk__xe_id(xml_rsc); rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_1 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring colocation of set %s resource %s " "with set %s: No such resource", pcmk__xe_id(set1), xml_rsc_id, pcmk__xe_id(set2)); continue; } flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s); pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2, flags); } } else { // Neither set is sequential for (xml_rsc = pcmk__xe_first_child(set1, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { xmlNode *xml_rsc_2 = NULL; xml_rsc_id = pcmk__xe_id(xml_rsc); rsc_1 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_1 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring colocation of set %s resource %s " "with set %s: No such resource", pcmk__xe_id(set1), xml_rsc_id, pcmk__xe_id(set2)); continue; } flags = pcmk__coloc_explicit | unpack_influence(id, rsc_1, influence_s); for (xml_rsc_2 = pcmk__xe_first_child(set2, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc_2 != NULL; xml_rsc_2 = pcmk__xe_next_same(xml_rsc_2)) { xml_rsc_id = pcmk__xe_id(xml_rsc_2); rsc_2 = pcmk__find_constraint_resource(scheduler->priv->resources, xml_rsc_id); if (rsc_2 == NULL) { // Should be possible only with validation disabled pcmk__config_err("Ignoring colocation of set %s resource " "%s with set %s resource %s: No such " "resource", pcmk__xe_id(set1), pcmk__xe_id(xml_rsc), pcmk__xe_id(set2), xml_rsc_id); continue; } pcmk__new_colocation(id, NULL, score, rsc_1, rsc_2, role_1, role_2, flags); } } } } static void unpack_simple_colocation(xmlNode *xml_obj, const char *id, const char *influence_s, pcmk_scheduler_t *scheduler) { int score_i = 0; uint32_t flags = pcmk__coloc_none; const char *score = crm_element_value(xml_obj, PCMK_XA_SCORE); const char *dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC); const char *primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC); const char *dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE); const char *primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE); const char *attr = crm_element_value(xml_obj, PCMK_XA_NODE_ATTRIBUTE); const char *primary_instance = NULL; const char *dependent_instance = NULL; pcmk_resource_t *primary = NULL; pcmk_resource_t *dependent = NULL; primary = pcmk__find_constraint_resource(scheduler->priv->resources, primary_id); dependent = pcmk__find_constraint_resource(scheduler->priv->resources, dependent_id); // @COMPAT: Deprecated since 2.1.5 primary_instance = crm_element_value(xml_obj, PCMK__XA_WITH_RSC_INSTANCE); dependent_instance = crm_element_value(xml_obj, PCMK__XA_RSC_INSTANCE); if (dependent_instance != NULL) { pcmk__warn_once(pcmk__wo_coloc_inst, "Support for " PCMK__XA_RSC_INSTANCE " is deprecated " "and will be removed in a future release"); } if (primary_instance != NULL) { pcmk__warn_once(pcmk__wo_coloc_inst, "Support for " PCMK__XA_WITH_RSC_INSTANCE " is " "deprecated and will be removed in a future release"); } if (dependent == NULL) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "does not exist", id, dependent_id); return; } else if (primary == NULL) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "does not exist", id, primary_id); return; } else if ((dependent_instance != NULL) && !pcmk__is_clone(dependent)) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "is not a clone but instance '%s' was requested", id, dependent_id, dependent_instance); return; } else if ((primary_instance != NULL) && !pcmk__is_clone(primary)) { pcmk__config_err("Ignoring constraint '%s' because resource '%s' " "is not a clone but instance '%s' was requested", id, primary_id, primary_instance); return; } if (dependent_instance != NULL) { dependent = find_clone_instance(dependent, dependent_instance); if (dependent == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not have an instance '%s'", id, dependent_id, dependent_instance); return; } } if (primary_instance != NULL) { primary = find_clone_instance(primary, primary_instance); if (primary == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not have an instance '%s'", "'%s'", id, primary_id, primary_instance); return; } } if (pcmk__xe_attr_is_true(xml_obj, PCMK_XA_SYMMETRICAL)) { pcmk__config_warn("The colocation constraint " "'" PCMK_XA_SYMMETRICAL "' attribute has been " "removed"); } if (score) { score_i = char2score(score); } flags = pcmk__coloc_explicit | unpack_influence(id, dependent, influence_s); pcmk__new_colocation(id, attr, score_i, dependent, primary, dependent_role, primary_role, flags); } // \return Standard Pacemaker return code static int unpack_colocation_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pcmk_scheduler_t *scheduler) { const char *id = NULL; const char *dependent_id = NULL; const char *primary_id = NULL; const char *dependent_role = NULL; const char *primary_role = NULL; pcmk_resource_t *dependent = NULL; pcmk_resource_t *primary = NULL; pcmk__idref_t *dependent_tag = NULL; pcmk__idref_t *primary_tag = NULL; xmlNode *dependent_set = NULL; xmlNode *primary_set = NULL; bool any_sets = false; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return pcmk_rc_unpack_error; } // Check whether there are any resource sets with template or tag references *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION); return pcmk_rc_ok; } dependent_id = crm_element_value(xml_obj, PCMK_XA_RSC); primary_id = crm_element_value(xml_obj, PCMK_XA_WITH_RSC); if ((dependent_id == NULL) || (primary_id == NULL)) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(scheduler, dependent_id, &dependent, &dependent_tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, dependent_id); return pcmk_rc_unpack_error; } if (!pcmk__valid_resource_or_tag(scheduler, primary_id, &primary, &primary_tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, primary_id); return pcmk_rc_unpack_error; } if ((dependent != NULL) && (primary != NULL)) { /* Neither side references any template/tag. */ return pcmk_rc_ok; } if ((dependent_tag != NULL) && (primary_tag != NULL)) { // A colocation constraint between two templates/tags makes no sense pcmk__config_err("Ignoring constraint '%s' because two templates or " "tags cannot be colocated", id); return pcmk_rc_unpack_error; } dependent_role = crm_element_value(xml_obj, PCMK_XA_RSC_ROLE); primary_role = crm_element_value(xml_obj, PCMK_XA_WITH_RSC_ROLE); *expanded_xml = pcmk__xml_copy(NULL, xml_obj); /* Convert dependent's template/tag reference into constraint * PCMK_XE_RESOURCE_SET */ if (!pcmk__tag_to_set(*expanded_xml, &dependent_set, PCMK_XA_RSC, true, scheduler)) { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (dependent_set != NULL) { if (dependent_role != NULL) { /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as * PCMK_XA_ROLE */ crm_xml_add(dependent_set, PCMK_XA_ROLE, dependent_role); pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_RSC_ROLE); } any_sets = true; } /* Convert primary's template/tag reference into constraint * PCMK_XE_RESOURCE_SET */ if (!pcmk__tag_to_set(*expanded_xml, &primary_set, PCMK_XA_WITH_RSC, true, scheduler)) { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (primary_set != NULL) { if (primary_role != NULL) { /* Move PCMK_XA_WITH_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as * PCMK_XA_ROLE */ crm_xml_add(primary_set, PCMK_XA_ROLE, primary_role); pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_WITH_RSC_ROLE); } any_sets = true; } if (any_sets) { crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_COLOCATION); } else { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } /*! * \internal * \brief Parse a colocation constraint from XML into scheduler data * * \param[in,out] xml_obj Colocation constraint XML to unpack * \param[in,out] scheduler Scheduler data to add constraint to */ void pcmk__unpack_colocation(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { int score_i = 0; xmlNode *set = NULL; xmlNode *last = NULL; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; const char *id = crm_element_value(xml_obj, PCMK_XA_ID); const char *score = NULL; const char *influence_s = NULL; if (pcmk__str_empty(id)) { pcmk__config_err("Ignoring " PCMK_XE_RSC_COLOCATION " without " CRM_ATTR_ID); return; } if (unpack_colocation_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) { return; } if (expanded_xml != NULL) { orig_xml = xml_obj; xml_obj = expanded_xml; } score = crm_element_value(xml_obj, PCMK_XA_SCORE); if (score != NULL) { score_i = char2score(score); } influence_s = crm_element_value(xml_obj, PCMK_XA_INFLUENCE); for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL); set != NULL; set = pcmk__xe_next_same(set)) { set = pcmk__xe_resolve_idref(set, scheduler->input); if (set == NULL) { // Configuration error, message already logged if (expanded_xml != NULL) { pcmk__xml_free(expanded_xml); } return; } if (pcmk__str_empty(pcmk__xe_id(set))) { pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET " without " CRM_ATTR_ID); continue; } unpack_colocation_set(set, score_i, id, influence_s, scheduler); if (last != NULL) { colocate_rsc_sets(id, last, set, score_i, influence_s, scheduler); } last = set; } if (expanded_xml) { pcmk__xml_free(expanded_xml); xml_obj = orig_xml; } if (last == NULL) { unpack_simple_colocation(xml_obj, id, influence_s, scheduler); } } /*! * \internal * \brief Make actions of a given type unrunnable for a given resource * * \param[in,out] rsc Resource whose actions should be blocked * \param[in] task Name of action to block * \param[in] reason Unrunnable start action causing the block */ static void mark_action_blocked(pcmk_resource_t *rsc, const char *task, const pcmk_resource_t *reason) { GList *iter = NULL; char *reason_text = crm_strdup_printf("colocation with %s", reason->id); for (iter = rsc->priv->actions; iter != NULL; iter = iter->next) { pcmk_action_t *action = iter->data; if (pcmk_is_set(action->flags, pcmk__action_runnable) && pcmk__str_eq(action->task, task, pcmk__str_none)) { pcmk__clear_action_flags(action, pcmk__action_runnable); pe_action_set_reason(action, reason_text, false); pcmk__block_colocation_dependents(action); pcmk__update_action_for_orderings(action, rsc->priv->scheduler); } } // If parent resource can't perform an action, neither can any children for (iter = rsc->priv->children; iter != NULL; iter = iter->next) { mark_action_blocked((pcmk_resource_t *) (iter->data), task, reason); } free(reason_text); } /*! * \internal * \brief If an action is unrunnable, block any relevant dependent actions * * If a given action is an unrunnable start or promote, block the start or * promote actions of resources colocated with it, as appropriate to the * colocations' configured roles. * * \param[in,out] action Action to check */ void pcmk__block_colocation_dependents(pcmk_action_t *action) { GList *iter = NULL; GList *colocations = NULL; pcmk_resource_t *rsc = NULL; bool is_start = false; if (pcmk_is_set(action->flags, pcmk__action_runnable)) { return; // Only unrunnable actions block dependents } is_start = pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_none); if (!is_start && !pcmk__str_eq(action->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) { return; // Only unrunnable starts and promotes block dependents } CRM_ASSERT(action->rsc != NULL); // Start and promote are resource actions /* If this resource is part of a collective resource, dependents are blocked * only if all instances of the collective are unrunnable, so check the * collective resource. */ rsc = uber_parent(action->rsc); if (rsc->priv->parent != NULL) { rsc = rsc->priv->parent; // Bundle } // Colocation fails only if entire primary can't reach desired role for (iter = rsc->priv->children; iter != NULL; iter = iter->next) { pcmk_resource_t *child = iter->data; pcmk_action_t *child_action = NULL; child_action = find_first_action(child->priv->actions, NULL, action->task, NULL); if ((child_action == NULL) || pcmk_is_set(child_action->flags, pcmk__action_runnable)) { crm_trace("Not blocking %s colocation dependents because " "at least %s has runnable %s", rsc->id, child->id, action->task); return; // At least one child can reach desired role } } crm_trace("Blocking %s colocation dependents due to unrunnable %s %s", rsc->id, action->rsc->id, action->task); // Check each colocation where this resource is primary colocations = pcmk__with_this_colocations(rsc); for (iter = colocations; iter != NULL; iter = iter->next) { pcmk__colocation_t *colocation = iter->data; if (colocation->score < PCMK_SCORE_INFINITY) { continue; // Only mandatory colocations block dependent } /* If the primary can't start, the dependent can't reach its colocated * role, regardless of what the primary or dependent colocation role is. * * If the primary can't be promoted, the dependent can't reach its * colocated role if the primary's colocation role is promoted. */ if (!is_start && (colocation->primary_role != pcmk_role_promoted)) { continue; } // Block the dependent from reaching its colocated role if (colocation->dependent_role == pcmk_role_promoted) { mark_action_blocked(colocation->dependent, PCMK_ACTION_PROMOTE, action->rsc); } else { mark_action_blocked(colocation->dependent, PCMK_ACTION_START, action->rsc); } } g_list_free(colocations); } /*! * \internal * \brief Get the resource to use for role comparisons * * A bundle replica includes a container and possibly an instance of the bundled * resource. The dependent in a "with bundle" colocation is colocated with a * particular bundle container. However, if the colocation includes a role, then * the role must be checked on the bundled resource instance inside the * container. The container itself will never be promoted; the bundled resource * may be. * * If the given resource is a bundle replica container, return the resource * inside it, if any. Otherwise, return the resource itself. * * \param[in] rsc Resource to check * * \return Resource to use for role comparisons */ static const pcmk_resource_t * get_resource_for_role(const pcmk_resource_t *rsc) { if (pcmk_is_set(rsc->flags, pcmk__rsc_replica_container)) { const pcmk_resource_t *child = pe__get_rsc_in_container(rsc); if (child != NULL) { return child; } } return rsc; } /*! * \internal * \brief Determine how a colocation constraint should affect a resource * * Colocation constraints have different effects at different points in the * scheduler sequence. Initially, they affect a resource's location; once that * is determined, then for promotable clones they can affect a resource * instance's role; after both are determined, the constraints no longer matter. * Given a specific colocation constraint, check what has been done so far to * determine what should be affected at the current point in the scheduler. * * \param[in] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint * \param[in] preview If true, pretend resources have already been assigned * * \return How colocation constraint should be applied at this point */ enum pcmk__coloc_affects pcmk__colocation_affects(const pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation, bool preview) { const pcmk_resource_t *dependent_role_rsc = NULL; const pcmk_resource_t *primary_role_rsc = NULL; CRM_ASSERT((dependent != NULL) && (primary != NULL) && (colocation != NULL)); if (!preview && pcmk_is_set(primary->flags, pcmk__rsc_unassigned)) { // Primary resource has not been assigned yet, so we can't do anything return pcmk__coloc_affects_nothing; } dependent_role_rsc = get_resource_for_role(dependent); primary_role_rsc = get_resource_for_role(primary); if ((colocation->dependent_role >= pcmk_role_unpromoted) && (dependent_role_rsc->priv->parent != NULL) && pcmk_is_set(dependent_role_rsc->priv->parent->flags, pcmk__rsc_promotable) && !pcmk_is_set(dependent_role_rsc->flags, pcmk__rsc_unassigned)) { /* This is a colocation by role, and the dependent is a promotable clone * that has already been assigned, so the colocation should now affect * the role. */ return pcmk__coloc_affects_role; } if (!preview && !pcmk_is_set(dependent->flags, pcmk__rsc_unassigned)) { /* The dependent resource has already been through assignment, so the * constraint no longer matters. */ return pcmk__coloc_affects_nothing; } if ((colocation->dependent_role != pcmk_role_unknown) && (colocation->dependent_role != dependent_role_rsc->priv->next_role)) { crm_trace("Skipping %scolocation '%s': dependent limited to %s role " "but %s next role is %s", ((colocation->score < 0)? "anti-" : ""), colocation->id, pcmk_role_text(colocation->dependent_role), dependent_role_rsc->id, pcmk_role_text(dependent_role_rsc->priv->next_role)); return pcmk__coloc_affects_nothing; } if ((colocation->primary_role != pcmk_role_unknown) && (colocation->primary_role != primary_role_rsc->priv->next_role)) { crm_trace("Skipping %scolocation '%s': primary limited to %s role " "but %s next role is %s", ((colocation->score < 0)? "anti-" : ""), colocation->id, pcmk_role_text(colocation->primary_role), primary_role_rsc->id, pcmk_role_text(primary_role_rsc->priv->next_role)); return pcmk__coloc_affects_nothing; } return pcmk__coloc_affects_location; } /*! * \internal * \brief Apply colocation to dependent for assignment purposes * * Update the allowed node scores of the dependent resource in a colocation, * for the purposes of assigning it to a node. * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint */ void pcmk__apply_coloc_to_scores(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { const char *attr = colocation->node_attribute; const char *value = NULL; GHashTable *work = NULL; GHashTableIter iter; pcmk_node_t *node = NULL; if (primary->priv->assigned_node != NULL) { value = pcmk__colocation_node_attr(primary->priv->assigned_node, attr, primary); } else if (colocation->score < 0) { // Nothing to do (anti-colocation with something that is not running) return; } work = pcmk__copy_node_table(dependent->priv->allowed_nodes); g_hash_table_iter_init(&iter, work); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (primary->priv->assigned_node == NULL) { node->assign->score = pcmk__add_scores(-colocation->score, node->assign->score); pcmk__rsc_trace(dependent, "Applied %s to %s score on %s (now %s after " "subtracting %s because primary %s inactive)", colocation->id, dependent->id, pcmk__node_name(node), pcmk_readable_score(node->assign->score), pcmk_readable_score(colocation->score), primary->id); continue; } if (pcmk__str_eq(pcmk__colocation_node_attr(node, attr, dependent), value, pcmk__str_casei)) { /* Add colocation score only if optional (or minus infinity). A * mandatory colocation is a requirement rather than a preference, * so we don't need to consider it for relative assignment purposes. * The resource will simply be forbidden from running on the node if * the primary isn't active there (via the condition above). */ if (colocation->score < PCMK_SCORE_INFINITY) { node->assign->score = pcmk__add_scores(colocation->score, node->assign->score); pcmk__rsc_trace(dependent, "Applied %s to %s score on %s (now %s after " "adding %s)", colocation->id, dependent->id, pcmk__node_name(node), pcmk_readable_score(node->assign->score), pcmk_readable_score(colocation->score)); } continue; } if (colocation->score >= PCMK_SCORE_INFINITY) { /* Only mandatory colocations are relevant when the colocation * attribute doesn't match, because an attribute not matching is not * a negative preference -- the colocation is simply relevant only * where it matches. */ node->assign->score = -PCMK_SCORE_INFINITY; pcmk__rsc_trace(dependent, "Banned %s from %s because colocation %s attribute %s " "does not match", dependent->id, pcmk__node_name(node), colocation->id, attr); } } if ((colocation->score <= -PCMK_SCORE_INFINITY) || (colocation->score >= PCMK_SCORE_INFINITY) || pcmk__any_node_available(work)) { g_hash_table_destroy(dependent->priv->allowed_nodes); dependent->priv->allowed_nodes = work; work = NULL; } else { pcmk__rsc_info(dependent, "%s: Rolling back scores from %s (no available nodes)", dependent->id, primary->id); } if (work != NULL) { g_hash_table_destroy(work); } } /*! * \internal * \brief Apply colocation to dependent for role purposes * * Update the priority of the dependent resource in a colocation, for the * purposes of selecting its role * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint */ void pcmk__apply_coloc_to_priority(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation) { const char *dependent_value = NULL; const char *primary_value = NULL; const char *attr = colocation->node_attribute; int score_multiplier = 1; const pcmk_node_t *primary_node = NULL; const pcmk_node_t *dependent_node = NULL; const pcmk_resource_t *primary_role_rsc = NULL; CRM_ASSERT((dependent != NULL) && (primary != NULL) && (colocation != NULL)); primary_node = primary->priv->assigned_node; dependent_node = dependent->priv->assigned_node; if ((primary_node == NULL) || (dependent_node == NULL)) { return; } dependent_value = pcmk__colocation_node_attr(dependent_node, attr, dependent); primary_value = pcmk__colocation_node_attr(primary_node, attr, primary); primary_role_rsc = get_resource_for_role(primary); if (!pcmk__str_eq(dependent_value, primary_value, pcmk__str_casei)) { if ((colocation->score == PCMK_SCORE_INFINITY) && (colocation->dependent_role == pcmk_role_promoted)) { dependent->priv->priority = -PCMK_SCORE_INFINITY; } return; } if ((colocation->primary_role != pcmk_role_unknown) && (colocation->primary_role != primary_role_rsc->priv->next_role)) { return; } if (colocation->dependent_role == pcmk_role_unpromoted) { score_multiplier = -1; } dependent->priv->priority = pcmk__add_scores(score_multiplier * colocation->score, dependent->priv->priority); pcmk__rsc_trace(dependent, "Applied %s to %s promotion priority (now %s after %s %s)", colocation->id, dependent->id, pcmk_readable_score(dependent->priv->priority), ((score_multiplier == 1)? "adding" : "subtracting"), pcmk_readable_score(colocation->score)); } /*! * \internal * \brief Find score of highest-scored node that matches colocation attribute * * \param[in] colocation Colocation constraint being applied * \param[in,out] rsc Resource whose allowed nodes should be searched * \param[in] attr Colocation attribute name (must not be NULL) * \param[in] value Colocation attribute value to require */ static int best_node_score_matching_attr(const pcmk__colocation_t *colocation, pcmk_resource_t *rsc, const char *attr, const char *value) { GHashTable *allowed_nodes_orig = NULL; GHashTableIter iter; pcmk_node_t *node = NULL; int best_score = -PCMK_SCORE_INFINITY; const char *best_node = NULL; if ((colocation != NULL) && (rsc == colocation->dependent) && pcmk_is_set(colocation->flags, pcmk__coloc_explicit) && pcmk__is_group(rsc->priv->parent) && (rsc != rsc->priv->parent->priv->children->data)) { /* The resource is a user-configured colocation's explicit dependent, * and a group member other than the first, which means the group's * location constraint scores were not applied to it (see * pcmk__group_apply_location()). Explicitly consider those scores now. * * @TODO This does leave one suboptimal case: if the group itself or * another member other than the first is explicitly colocated with * the same primary, the primary will count the group's location scores * multiple times. This is much less likely than a single member being * explicitly colocated, so it's an acceptable tradeoff for now. */ allowed_nodes_orig = rsc->priv->allowed_nodes; rsc->priv->allowed_nodes = pcmk__copy_node_table(allowed_nodes_orig); - for (GList *loc_iter = rsc->priv->scheduler->placement_constraints; + for (GList *loc_iter = rsc->priv->scheduler->priv->location_constraints; loc_iter != NULL; loc_iter = loc_iter->next) { pcmk__location_t *location = loc_iter->data; if (location->rsc == rsc->priv->parent) { rsc->priv->cmds->apply_location(rsc, location); } } } // Find best allowed node with matching attribute g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) { if ((node->assign->score > best_score) && pcmk__node_available(node, false, false) && pcmk__str_eq(value, pcmk__colocation_node_attr(node, attr, rsc), pcmk__str_casei)) { best_score = node->assign->score; best_node = node->priv->name; } } if (!pcmk__str_eq(attr, CRM_ATTR_UNAME, pcmk__str_none)) { if (best_node == NULL) { crm_info("No allowed node for %s matches node attribute %s=%s", rsc->id, attr, value); } else { crm_info("Allowed node %s for %s had best score (%d) " "of those matching node attribute %s=%s", best_node, rsc->id, best_score, attr, value); } } if (allowed_nodes_orig != NULL) { g_hash_table_destroy(rsc->priv->allowed_nodes); rsc->priv->allowed_nodes = allowed_nodes_orig; } return best_score; } /*! * \internal * \brief Check whether a resource is allowed only on a single node * * \param[in] rsc Resource to check * * \return \c true if \p rsc is allowed only on one node, otherwise \c false */ static bool allowed_on_one(const pcmk_resource_t *rsc) { GHashTableIter iter; pcmk_node_t *allowed_node = NULL; int allowed_nodes = 0; g_hash_table_iter_init(&iter, rsc->priv->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &allowed_node)) { if ((allowed_node->assign->score >= 0) && (++allowed_nodes > 1)) { pcmk__rsc_trace(rsc, "%s is allowed on multiple nodes", rsc->id); return false; } } pcmk__rsc_trace(rsc, "%s is allowed %s", rsc->id, ((allowed_nodes == 1)? "on a single node" : "nowhere")); return (allowed_nodes == 1); } /*! * \internal * \brief Add resource's colocation matches to current node assignment scores * * For each node in a given table, if any of a given resource's allowed nodes * have a matching value for the colocation attribute, add the highest of those * nodes' scores to the node's score. * * \param[in,out] nodes Table of nodes with assignment scores so far * \param[in,out] source_rsc Resource whose node scores to add * \param[in] target_rsc Resource on whose behalf to update \p nodes * \param[in] colocation Original colocation constraint (used to get * configured primary resource's stickiness, and * to get colocation node attribute; pass NULL to * ignore stickiness and use default attribute) * \param[in] factor Factor by which to multiply scores being added * \param[in] only_positive Whether to add only positive scores */ static void add_node_scores_matching_attr(GHashTable *nodes, pcmk_resource_t *source_rsc, const pcmk_resource_t *target_rsc, const pcmk__colocation_t *colocation, float factor, bool only_positive) { GHashTableIter iter; pcmk_node_t *node = NULL; const char *attr = colocation->node_attribute; // Iterate through each node g_hash_table_iter_init(&iter, nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { float delta_f = 0; int delta = 0; int score = 0; int new_score = 0; const char *value = pcmk__colocation_node_attr(node, attr, target_rsc); score = best_node_score_matching_attr(colocation, source_rsc, attr, value); if ((factor < 0) && (score < 0)) { /* If the dependent is anti-colocated, we generally don't want the * primary to prefer nodes that the dependent avoids. That could * lead to unnecessary shuffling of the primary when the dependent * hits its migration threshold somewhere, for example. * * However, there are cases when it is desirable. If the dependent * can't run anywhere but where the primary is, it would be * worthwhile to move the primary for the sake of keeping the * dependent active. * * We can't know that exactly at this point since we don't know * where the primary will be assigned, but we can limit considering * the preference to when the dependent is allowed only on one node. * This is less than ideal for multiple reasons: * * - the dependent could be allowed on more than one node but have * anti-colocation primaries on each; * - the dependent could be a clone or bundle with multiple * instances, and the dependent as a whole is allowed on multiple * nodes but some instance still can't run * - the dependent has considered node-specific criteria such as * location constraints and stickiness by this point, but might * have other factors that end up disallowing a node * * but the alternative is making the primary move when it doesn't * need to. * * We also consider the primary's stickiness and influence, so the * user has some say in the matter. (This is the configured primary, * not a particular instance of the primary, but that doesn't matter * unless stickiness uses a rule to vary by node, and that seems * acceptable to ignore.) */ if ((colocation->primary->priv->stickiness >= -score) || !pcmk__colocation_has_influence(colocation, NULL) || !allowed_on_one(colocation->dependent)) { crm_trace("%s: Filtering %d + %f * %d " "(double negative disallowed)", pcmk__node_name(node), node->assign->score, factor, score); continue; } } if (node->assign->score == INFINITY_HACK) { crm_trace("%s: Filtering %d + %f * %d (node was marked unusable)", pcmk__node_name(node), node->assign->score, factor, score); continue; } delta_f = factor * score; // Round the number; see http://c-faq.com/fp/round.html delta = (int) ((delta_f < 0)? (delta_f - 0.5) : (delta_f + 0.5)); /* Small factors can obliterate the small scores that are often actually * used in configurations. If the score and factor are nonzero, ensure * that the result is nonzero as well. */ if ((delta == 0) && (score != 0)) { if (factor > 0.0) { delta = 1; } else if (factor < 0.0) { delta = -1; } } new_score = pcmk__add_scores(delta, node->assign->score); if (only_positive && (new_score < 0) && (node->assign->score > 0)) { crm_trace("%s: Filtering %d + %f * %d = %d " "(negative disallowed, marking node unusable)", pcmk__node_name(node), node->assign->score, factor, score, new_score); node->assign->score = INFINITY_HACK; continue; } if (only_positive && (new_score < 0) && (node->assign->score == 0)) { crm_trace("%s: Filtering %d + %f * %d = %d (negative disallowed)", pcmk__node_name(node), node->assign->score, factor, score, new_score); continue; } crm_trace("%s: %d + %f * %d = %d", pcmk__node_name(node), node->assign->score, factor, score, new_score); node->assign->score = new_score; } } /*! * \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] source_rsc Resource whose node scores to add * \param[in] target_rsc Resource on whose behalf to update \p *nodes * \param[in] log_id Resource ID for logs (if \c NULL, use * \p source_rsc ID) * \param[in,out] nodes Nodes to update (set initial contents to \c NULL * to copy allowed nodes from \p source_rsc) * \param[in] colocation Original colocation constraint (used to get * configured primary resource's stickiness, and * to get colocation node attribute; if \c NULL, * source_rsc's own matching node scores * will not be added, and \p *nodes must be \c NULL * as well) * \param[in] factor Incorporate scores multiplied by this factor * \param[in] flags Bitmask of enum pcmk__coloc_select values * * \note \c NULL \p target_rsc, \c NULL \p *nodes, \c NULL \p colocation, and * the \c pcmk__coloc_select_this_with flag are used together (and only by * \c cmp_resources()). * \note The caller remains responsible for freeing \p *nodes. * \note This is the shared implementation of * \c pcmk__assignment_methods_t:add_colocated_node_scores(). */ void pcmk__add_colocated_node_scores(pcmk_resource_t *source_rsc, const pcmk_resource_t *target_rsc, const char *log_id, GHashTable **nodes, const pcmk__colocation_t *colocation, float factor, uint32_t flags) { GHashTable *work = NULL; CRM_ASSERT((source_rsc != NULL) && (nodes != NULL) && ((colocation != NULL) || ((target_rsc == NULL) && (*nodes == NULL)))); if (log_id == NULL) { log_id = source_rsc->id; } // Avoid infinite recursion if (pcmk_is_set(source_rsc->flags, pcmk__rsc_updating_nodes)) { pcmk__rsc_info(source_rsc, "%s: Breaking dependency loop at %s", log_id, source_rsc->id); return; } pcmk__set_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); if (*nodes == NULL) { work = pcmk__copy_node_table(source_rsc->priv->allowed_nodes); target_rsc = source_rsc; } else { const bool pos = pcmk_is_set(flags, pcmk__coloc_select_nonnegative); pcmk__rsc_trace(source_rsc, "%s: Merging %s scores from %s (at %.6f)", log_id, (pos? "positive" : "all"), source_rsc->id, factor); work = pcmk__copy_node_table(*nodes); add_node_scores_matching_attr(work, source_rsc, target_rsc, colocation, factor, pos); } if (work == NULL) { pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); return; } if (pcmk__any_node_available(work)) { GList *colocations = NULL; if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) { colocations = pcmk__this_with_colocations(source_rsc); pcmk__rsc_trace(source_rsc, "Checking additional %d optional '%s with' " "constraints", g_list_length(colocations), source_rsc->id); } else { colocations = pcmk__with_this_colocations(source_rsc); pcmk__rsc_trace(source_rsc, "Checking additional %d optional 'with %s' " "constraints", g_list_length(colocations), source_rsc->id); } flags |= pcmk__coloc_select_active; for (GList *iter = colocations; iter != NULL; iter = iter->next) { pcmk__colocation_t *constraint = iter->data; pcmk_resource_t *other = NULL; float other_factor = factor * constraint->score / (float) PCMK_SCORE_INFINITY; if (pcmk_is_set(flags, pcmk__coloc_select_this_with)) { other = constraint->primary; } else if (!pcmk__colocation_has_influence(constraint, NULL)) { continue; } else { other = constraint->dependent; } pcmk__rsc_trace(source_rsc, "Optionally merging score of '%s' constraint " "(%s with %s)", constraint->id, constraint->dependent->id, constraint->primary->id); other->priv->cmds->add_colocated_node_scores(other, target_rsc, log_id, &work, constraint, other_factor, flags); pe__show_node_scores(true, NULL, log_id, work, source_rsc->priv->scheduler); } g_list_free(colocations); } else if (pcmk_is_set(flags, pcmk__coloc_select_active)) { pcmk__rsc_info(source_rsc, "%s: Rolling back optional scores from %s", log_id, source_rsc->id); g_hash_table_destroy(work); pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); return; } if (pcmk_is_set(flags, pcmk__coloc_select_nonnegative)) { pcmk_node_t *node = NULL; GHashTableIter iter; g_hash_table_iter_init(&iter, work); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if (node->assign->score == INFINITY_HACK) { node->assign->score = 1; } } } if (*nodes != NULL) { g_hash_table_destroy(*nodes); } *nodes = work; pcmk__clear_rsc_flags(source_rsc, pcmk__rsc_updating_nodes); } /*! * \internal * \brief Apply a "with this" colocation to a resource's allowed node scores * * \param[in,out] data Colocation to apply * \param[in,out] user_data Resource being assigned */ void pcmk__add_dependent_scores(gpointer data, gpointer user_data) { pcmk__colocation_t *colocation = data; pcmk_resource_t *primary = user_data; pcmk_resource_t *dependent = colocation->dependent; const float factor = colocation->score / (float) PCMK_SCORE_INFINITY; uint32_t flags = pcmk__coloc_select_active; if (!pcmk__colocation_has_influence(colocation, NULL)) { return; } if (pcmk__is_clone(primary)) { flags |= pcmk__coloc_select_nonnegative; } pcmk__rsc_trace(primary, "%s: Incorporating attenuated %s assignment scores due " "to colocation %s", primary->id, dependent->id, colocation->id); dependent->priv->cmds->add_colocated_node_scores(dependent, primary, dependent->id, &(primary->priv->allowed_nodes), colocation, factor, flags); } /*! * \internal * \brief Exclude nodes from a dependent's node table if not in a given list * * Given a dependent resource in a colocation and a list of nodes where the * primary resource will run, set a node's score to \c -INFINITY in the * dependent's node table if not found in the primary nodes list. * * \param[in,out] dependent Dependent resource * \param[in] primary Primary resource (for logging only) * \param[in] colocation Colocation constraint (for logging only) * \param[in] primary_nodes List of nodes where the primary will have * unblocked instances in a suitable role * \param[in] merge_scores If \c true and a node is found in both \p table * and \p list, add the node's score in \p list to * the node's score in \p table */ void pcmk__colocation_intersect_nodes(pcmk_resource_t *dependent, const pcmk_resource_t *primary, const pcmk__colocation_t *colocation, const GList *primary_nodes, bool merge_scores) { GHashTableIter iter; pcmk_node_t *dependent_node = NULL; CRM_ASSERT((dependent != NULL) && (primary != NULL) && (colocation != NULL)); g_hash_table_iter_init(&iter, dependent->priv->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &dependent_node)) { const pcmk_node_t *primary_node = NULL; primary_node = pe_find_node_id(primary_nodes, dependent_node->priv->id); if (primary_node == NULL) { dependent_node->assign->score = -PCMK_SCORE_INFINITY; pcmk__rsc_trace(dependent, "Banning %s from %s (no primary instance) for %s", dependent->id, pcmk__node_name(dependent_node), colocation->id); } else if (merge_scores) { dependent_node->assign->score = pcmk__add_scores(dependent_node->assign->score, primary_node->assign->score); pcmk__rsc_trace(dependent, "Added %s's score %s to %s's score for %s (now %d) " "for colocation %s", primary->id, pcmk_readable_score(primary_node->assign->score), dependent->id, pcmk__node_name(dependent_node), dependent_node->assign->score, colocation->id); } } } /*! * \internal * \brief Get all colocations affecting a resource as the primary * * \param[in] rsc Resource to get colocations for * * \return Newly allocated list of colocations affecting \p rsc as primary * * \note This is a convenience wrapper for the with_this_colocations() method. */ GList * pcmk__with_this_colocations(const pcmk_resource_t *rsc) { GList *list = NULL; rsc->priv->cmds->with_this_colocations(rsc, rsc, &list); return list; } /*! * \internal * \brief Get all colocations affecting a resource as the dependent * * \param[in] rsc Resource to get colocations for * * \return Newly allocated list of colocations affecting \p rsc as dependent * * \note This is a convenience wrapper for the this_with_colocations() method. */ GList * pcmk__this_with_colocations(const pcmk_resource_t *rsc) { GList *list = NULL; rsc->priv->cmds->this_with_colocations(rsc, rsc, &list); return list; } diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c index eccbcf674a..593e7339f5 100644 --- a/lib/pacemaker/pcmk_sched_location.c +++ b/lib/pacemaker/pcmk_sched_location.c @@ -1,732 +1,733 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include "libpacemaker_private.h" static int get_node_score(const char *rule, const char *score, bool raw, pcmk_node_t *node, pcmk_resource_t *rsc) { int score_f = 0; if (score == NULL) { pcmk__config_warn("Rule %s: no score specified (assuming 0)", rule); } else if (raw) { score_f = char2score(score); } else { const char *target = NULL; const char *attr_score = NULL; target = g_hash_table_lookup(rsc->priv->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET); attr_score = pcmk__node_attr(node, score, target, pcmk__rsc_node_current); if (attr_score == NULL) { crm_debug("Rule %s: %s did not have a value for %s", rule, pcmk__node_name(node), score); score_f = -PCMK_SCORE_INFINITY; } else { crm_debug("Rule %s: %s had value %s for %s", rule, pcmk__node_name(node), attr_score, score); score_f = char2score(attr_score); } } return score_f; } /*! * \internal * \brief Parse a role configuration for a location constraint * * \param[in] role_spec Role specification * \param[out] role Where to store parsed role * * \return true if role specification is valid, otherwise false */ static bool parse_location_role(const char *role_spec, enum rsc_role_e *role) { if (role_spec == NULL) { *role = pcmk_role_unknown; return true; } *role = pcmk_parse_role(role_spec); switch (*role) { case pcmk_role_unknown: return false; case pcmk_role_started: case pcmk_role_unpromoted: /* Any promotable clone instance cannot be promoted without being in * the unpromoted role first. Therefore, any constraint for the * started or unpromoted role applies to every role. */ *role = pcmk_role_unknown; break; default: break; } return true; } /*! * \internal * \brief Generate a location constraint from a rule * * \param[in,out] rsc Resource that constraint is for * \param[in] rule_xml Rule XML (sub-element of location constraint) * \param[in] discovery Value of \c PCMK_XA_RESOURCE_DISCOVERY for * constraint * \param[out] next_change Where to set when rule evaluation will change * \param[in,out] rule_input Values used to evaluate rule criteria * (node-specific values will be overwritten by * this function) * * \return true if rule is valid, otherwise false */ static bool generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml, const char *discovery, crm_time_t *next_change, pcmk_rule_input_t *rule_input) { const char *rule_id = NULL; const char *score = NULL; const char *boolean = NULL; const char *role_spec = NULL; GList *iter = NULL; bool raw_score = true; bool score_allocated = false; pcmk__location_t *location_rule = NULL; enum rsc_role_e role = pcmk_role_unknown; enum pcmk__combine combine = pcmk__combine_unknown; rule_xml = pcmk__xe_resolve_idref(rule_xml, rsc->priv->scheduler->input); if (rule_xml == NULL) { return false; // Error already logged } rule_id = crm_element_value(rule_xml, PCMK_XA_ID); if (rule_id == NULL) { pcmk__config_err("Ignoring " PCMK_XE_RULE " without " PCMK_XA_ID " in location constraint"); return false; } boolean = crm_element_value(rule_xml, PCMK_XA_BOOLEAN_OP); role_spec = crm_element_value(rule_xml, PCMK_XA_ROLE); if (parse_location_role(role_spec, &role)) { crm_trace("Setting rule %s role filter to %s", rule_id, role_spec); } else { pcmk__config_err("Ignoring rule %s: Invalid " PCMK_XA_ROLE " '%s'", rule_id, role_spec); return false; } crm_trace("Processing location constraint rule %s", rule_id); score = crm_element_value(rule_xml, PCMK_XA_SCORE); if (score == NULL) { score = crm_element_value(rule_xml, PCMK_XA_SCORE_ATTRIBUTE); if (score != NULL) { raw_score = false; } } combine = pcmk__parse_combine(boolean); switch (combine) { case pcmk__combine_and: case pcmk__combine_or: break; default: /* @COMPAT When we can break behavioral backward compatibility, * return false */ pcmk__config_warn("Location constraint rule %s has invalid " PCMK_XA_BOOLEAN_OP " value '%s', using default " "'" PCMK_VALUE_AND "'", rule_id, boolean); combine = pcmk__combine_and; break; } location_rule = pcmk__new_location(rule_id, rsc, 0, discovery, NULL); CRM_CHECK(location_rule != NULL, return NULL); location_rule->role_filter = role; if ((rule_input->rsc_id != NULL) && (rule_input->rsc_id_nmatches > 0) && !raw_score) { char *result = pcmk__replace_submatches(score, rule_input->rsc_id, rule_input->rsc_id_submatches, rule_input->rsc_id_nmatches); if (result != NULL) { score = result; score_allocated = true; } } for (iter = rsc->priv->scheduler->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = iter->data; rule_input->node_attrs = node->priv->attrs; rule_input->rsc_params = pe_rsc_params(rsc, node, rsc->priv->scheduler); if (pcmk_evaluate_rule(rule_xml, rule_input, next_change) == pcmk_rc_ok) { pcmk_node_t *local = pe__copy_node(node); location_rule->nodes = g_list_prepend(location_rule->nodes, local); local->assign->score = get_node_score(rule_id, score, raw_score, node, rsc); crm_trace("%s has score %s after %s", pcmk__node_name(node), pcmk_readable_score(local->assign->score), rule_id); } } if (score_allocated) { free((char *)score); } if (location_rule->nodes == NULL) { crm_trace("No matching nodes for location constraint rule %s", rule_id); } else { crm_trace("Location constraint rule %s matched %d nodes", rule_id, g_list_length(location_rule->nodes)); } return true; } static void unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc, const char *role_spec, const char *score, char *rsc_id_match, int rsc_id_nmatches, regmatch_t *rsc_id_submatches) { const char *rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); const char *id = crm_element_value(xml_obj, PCMK_XA_ID); const char *node = crm_element_value(xml_obj, PCMK_XA_NODE); const char *discovery = crm_element_value(xml_obj, PCMK_XA_RESOURCE_DISCOVERY); if (rsc == NULL) { pcmk__config_warn("Ignoring constraint '%s' because resource '%s' " "does not exist", id, rsc_id); return; } if (score == NULL) { score = crm_element_value(xml_obj, PCMK_XA_SCORE); } if ((node != NULL) && (score != NULL)) { int score_i = char2score(score); pcmk_node_t *match = pcmk_find_node(rsc->priv->scheduler, node); enum rsc_role_e role = pcmk_role_unknown; pcmk__location_t *location = NULL; if (match == NULL) { crm_info("Ignoring location constraint %s " "because '%s' is not a known node", pcmk__s(id, "without ID"), node); return; } if (role_spec == NULL) { role_spec = crm_element_value(xml_obj, PCMK_XA_ROLE); } if (parse_location_role(role_spec, &role)) { crm_trace("Setting location constraint %s role filter: %s", id, role_spec); } else { /* @COMPAT The previous behavior of creating the constraint ignoring * the role is retained for now, but we should ignore the entire * constraint when we can break backward compatibility. */ pcmk__config_err("Ignoring role in constraint %s: " "Invalid value '%s'", id, role_spec); } location = pcmk__new_location(id, rsc, score_i, discovery, match); if (location == NULL) { return; // Error already logged } location->role_filter = role; } else { bool empty = true; crm_time_t *next_change = crm_time_new_undefined(); pcmk_rule_input_t rule_input = { .now = rsc->priv->scheduler->priv->now, .rsc_meta = rsc->priv->meta, .rsc_id = rsc_id_match, .rsc_id_submatches = rsc_id_submatches, .rsc_id_nmatches = rsc_id_nmatches, }; /* This loop is logically parallel to pcmk__evaluate_rules(), except * instead of checking whether any rule is active, we set up location * constraints for each active rule. * * @COMPAT When we can break backward compatibility, limit location * constraints to a single rule, for consistency with other contexts. * Since a rule may contain other rules, this does not prohibit any * existing use cases. */ for (xmlNode *rule_xml = pcmk__xe_first_child(xml_obj, PCMK_XE_RULE, NULL, NULL); rule_xml != NULL; rule_xml = pcmk__xe_next_same(rule_xml)) { if (generate_location_rule(rsc, rule_xml, discovery, next_change, &rule_input)) { if (empty) { empty = false; continue; } pcmk__warn_once(pcmk__wo_location_rules, "Support for multiple " PCMK_XE_RULE " elements in a location constraint is " "deprecated and will be removed in a future " "release (use a single new rule combining the " "previous rules with " PCMK_XA_BOOLEAN_OP " set to '" PCMK_VALUE_OR "' instead)"); } } if (empty) { pcmk__config_err("Ignoring constraint '%s' because it contains " "no valid rules", id); } /* If there is a point in the future when the evaluation of a rule will * change, make sure the scheduler is re-run by that time. */ if (crm_time_is_defined(next_change)) { time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(t, rsc->priv->scheduler, "location rule evaluation"); } crm_time_free(next_change); } } static void unpack_simple_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { const char *id = crm_element_value(xml_obj, PCMK_XA_ID); const char *value = crm_element_value(xml_obj, PCMK_XA_RSC); if (value) { pcmk_resource_t *rsc; rsc = pcmk__find_constraint_resource(scheduler->priv->resources, value); unpack_rsc_location(xml_obj, rsc, NULL, NULL, NULL, 0, NULL); } value = crm_element_value(xml_obj, PCMK_XA_RSC_PATTERN); if (value) { regex_t regex; bool invert = false; if (value[0] == '!') { value++; invert = true; } if (regcomp(®ex, value, REG_EXTENDED) != 0) { pcmk__config_err("Ignoring constraint '%s' because " PCMK_XA_RSC_PATTERN " has invalid value '%s'", id, value); return; } for (GList *iter = scheduler->priv->resources; iter != NULL; iter = iter->next) { pcmk_resource_t *r = iter->data; int nregs = 0; regmatch_t *pmatch = NULL; int status; if (regex.re_nsub > 0) { nregs = regex.re_nsub + 1; } else { nregs = 1; } pmatch = pcmk__assert_alloc(nregs, sizeof(regmatch_t)); status = regexec(®ex, r->id, nregs, pmatch, 0); if (!invert && (status == 0)) { crm_debug("'%s' matched '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, r->id, nregs, pmatch); } else if (invert && (status != 0)) { crm_debug("'%s' is an inverted match of '%s' for %s", r->id, value, id); unpack_rsc_location(xml_obj, r, NULL, NULL, NULL, 0, NULL); } else { crm_trace("'%s' does not match '%s' for %s", r->id, value, id); } free(pmatch); } regfree(®ex); } } // \return Standard Pacemaker return code static int unpack_location_tags(xmlNode *xml_obj, xmlNode **expanded_xml, pcmk_scheduler_t *scheduler) { const char *id = NULL; const char *rsc_id = NULL; const char *state = NULL; pcmk_resource_t *rsc = NULL; pcmk__idref_t *tag = NULL; xmlNode *rsc_set = NULL; *expanded_xml = NULL; CRM_CHECK(xml_obj != NULL, return EINVAL); id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("Ignoring <%s> constraint without " PCMK_XA_ID, xml_obj->name); return pcmk_rc_unpack_error; } // Check whether there are any resource sets with template or tag references *expanded_xml = pcmk__expand_tags_in_sets(xml_obj, scheduler); if (*expanded_xml != NULL) { crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION); return pcmk_rc_ok; } rsc_id = crm_element_value(xml_obj, PCMK_XA_RSC); if (rsc_id == NULL) { return pcmk_rc_ok; } if (!pcmk__valid_resource_or_tag(scheduler, rsc_id, &rsc, &tag)) { pcmk__config_err("Ignoring constraint '%s' because '%s' is not a " "valid resource or tag", id, rsc_id); return pcmk_rc_unpack_error; } else if (rsc != NULL) { // No template is referenced return pcmk_rc_ok; } state = crm_element_value(xml_obj, PCMK_XA_ROLE); *expanded_xml = pcmk__xml_copy(NULL, xml_obj); /* Convert any template or tag reference into constraint * PCMK_XE_RESOURCE_SET */ if (!pcmk__tag_to_set(*expanded_xml, &rsc_set, PCMK_XA_RSC, false, scheduler)) { pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; return pcmk_rc_unpack_error; } if (rsc_set != NULL) { if (state != NULL) { /* Move PCMK_XA_RSC_ROLE into converted PCMK_XE_RESOURCE_SET as * PCMK_XA_ROLE attribute */ crm_xml_add(rsc_set, PCMK_XA_ROLE, state); pcmk__xe_remove_attr(*expanded_xml, PCMK_XA_ROLE); } crm_log_xml_trace(*expanded_xml, "Expanded " PCMK_XE_RSC_LOCATION); } else { // No sets pcmk__xml_free(*expanded_xml); *expanded_xml = NULL; } return pcmk_rc_ok; } // \return Standard Pacemaker return code static int unpack_location_set(xmlNode *location, xmlNode *set, pcmk_scheduler_t *scheduler) { xmlNode *xml_rsc = NULL; pcmk_resource_t *resource = NULL; const char *set_id; const char *role; const char *local_score; CRM_CHECK(set != NULL, return EINVAL); set_id = pcmk__xe_id(set); if (set_id == NULL) { pcmk__config_err("Ignoring " PCMK_XE_RESOURCE_SET " without " PCMK_XA_ID " in constraint '%s'", pcmk__s(pcmk__xe_id(location), "(missing ID)")); return pcmk_rc_unpack_error; } role = crm_element_value(set, PCMK_XA_ROLE); local_score = crm_element_value(set, PCMK_XA_SCORE); for (xml_rsc = pcmk__xe_first_child(set, PCMK_XE_RESOURCE_REF, NULL, NULL); xml_rsc != NULL; xml_rsc = pcmk__xe_next_same(xml_rsc)) { resource = pcmk__find_constraint_resource(scheduler->priv->resources, pcmk__xe_id(xml_rsc)); if (resource == NULL) { pcmk__config_err("%s: No resource found for %s", set_id, pcmk__xe_id(xml_rsc)); return pcmk_rc_unpack_error; } unpack_rsc_location(location, resource, role, local_score, NULL, 0, NULL); } return pcmk_rc_ok; } void pcmk__unpack_location(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { xmlNode *set = NULL; bool any_sets = false; xmlNode *orig_xml = NULL; xmlNode *expanded_xml = NULL; if (unpack_location_tags(xml_obj, &expanded_xml, scheduler) != pcmk_rc_ok) { return; } if (expanded_xml) { orig_xml = xml_obj; xml_obj = expanded_xml; } for (set = pcmk__xe_first_child(xml_obj, PCMK_XE_RESOURCE_SET, NULL, NULL); set != NULL; set = pcmk__xe_next_same(set)) { any_sets = true; set = pcmk__xe_resolve_idref(set, scheduler->input); if ((set == NULL) // Configuration error, message already logged || (unpack_location_set(xml_obj, set, scheduler) != pcmk_rc_ok)) { if (expanded_xml) { pcmk__xml_free(expanded_xml); } return; } } if (expanded_xml) { pcmk__xml_free(expanded_xml); xml_obj = orig_xml; } if (!any_sets) { unpack_simple_location(xml_obj, scheduler); } } /*! * \internal * \brief Add a new location constraint to scheduler data * * \param[in] id XML ID of location constraint * \param[in,out] rsc Resource in location constraint * \param[in] node_score Constraint score * \param[in] probe_mode When resource should be probed on node * \param[in] node Node in constraint (or NULL if rule-based) * * \return Newly allocated location constraint on success, otherwise NULL * \note The result will be added to the cluster (via \p rsc) and should not be * freed separately. */ pcmk__location_t * pcmk__new_location(const char *id, pcmk_resource_t *rsc, int node_score, const char *probe_mode, pcmk_node_t *node) { pcmk__location_t *new_con = NULL; CRM_CHECK((node != NULL) || (node_score == 0), return NULL); if (id == NULL) { pcmk__config_err("Invalid constraint: no ID specified"); return NULL; } if (rsc == NULL) { pcmk__config_err("Invalid constraint %s: no resource specified", id); return NULL; } new_con = pcmk__assert_alloc(1, sizeof(pcmk__location_t)); new_con->id = pcmk__str_copy(id); new_con->rsc = rsc; new_con->nodes = NULL; new_con->role_filter = pcmk_role_unknown; if (pcmk__str_eq(probe_mode, PCMK_VALUE_ALWAYS, pcmk__str_null_matches|pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_always; } else if (pcmk__str_eq(probe_mode, PCMK_VALUE_NEVER, pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_never; } else if (pcmk__str_eq(probe_mode, PCMK_VALUE_EXCLUSIVE, pcmk__str_casei)) { new_con->probe_mode = pcmk__probe_exclusive; pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes); } else { pcmk__config_err("Invalid " PCMK_XA_RESOURCE_DISCOVERY " value %s " "in location constraint", probe_mode); } if (node != NULL) { pcmk_node_t *copy = pe__copy_node(node); copy->assign->score = node_score; new_con->nodes = g_list_prepend(NULL, copy); } - rsc->priv->scheduler->placement_constraints = - g_list_prepend(rsc->priv->scheduler->placement_constraints, new_con); + rsc->priv->scheduler->priv->location_constraints = + g_list_prepend(rsc->priv->scheduler->priv->location_constraints, + new_con); rsc->priv->location_constraints = g_list_prepend(rsc->priv->location_constraints, new_con); return new_con; } /*! * \internal * \brief Apply all location constraints * * \param[in,out] scheduler Scheduler data */ void pcmk__apply_locations(pcmk_scheduler_t *scheduler) { - for (GList *iter = scheduler->placement_constraints; + for (GList *iter = scheduler->priv->location_constraints; iter != NULL; iter = iter->next) { pcmk__location_t *location = iter->data; location->rsc->priv->cmds->apply_location(location->rsc, location); } } /*! * \internal * \brief Apply a location constraint to a resource's allowed node scores * * \param[in,out] rsc Resource to apply constraint to * \param[in,out] location Location constraint to apply * * \note This does not consider the resource's children, so the resource's * apply_location() method should be used instead in most cases. */ void pcmk__apply_location(pcmk_resource_t *rsc, pcmk__location_t *location) { bool need_role = false; CRM_ASSERT((rsc != NULL) && (location != NULL)); // If a role was specified, ensure constraint is applicable need_role = (location->role_filter > pcmk_role_unknown); if (need_role && (location->role_filter != rsc->priv->next_role)) { pcmk__rsc_trace(rsc, "Not applying %s to %s because role will be %s not %s", location->id, rsc->id, pcmk_role_text(rsc->priv->next_role), pcmk_role_text(location->role_filter)); return; } if (location->nodes == NULL) { pcmk__rsc_trace(rsc, "Not applying %s to %s because no nodes match", location->id, rsc->id); return; } for (GList *iter = location->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = iter->data; pcmk_node_t *allowed_node = NULL; allowed_node = g_hash_table_lookup(rsc->priv->allowed_nodes, node->priv->id); pcmk__rsc_trace(rsc, "Applying %s%s%s to %s score on %s: %c %s", location->id, (need_role? " for role " : ""), (need_role? pcmk_role_text(location->role_filter) : ""), rsc->id, pcmk__node_name(node), ((allowed_node == NULL)? '=' : '+'), pcmk_readable_score(node->assign->score)); if (allowed_node == NULL) { allowed_node = pe__copy_node(node); g_hash_table_insert(rsc->priv->allowed_nodes, (gpointer) allowed_node->priv->id, allowed_node); } else { allowed_node->assign->score = pcmk__add_scores(allowed_node->assign->score, node->assign->score); } if (allowed_node->assign->probe_mode < location->probe_mode) { if (location->probe_mode == pcmk__probe_exclusive) { pcmk__set_rsc_flags(rsc, pcmk__rsc_exclusive_probes); } /* exclusive > never > always... always is default */ allowed_node->assign->probe_mode = location->probe_mode; } } } diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 550efee938..1d149e95b4 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1,3463 +1,3463 @@ /* * Copyright 2019-2024 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. */ #include #include #include #include #include #include #include #include const char * pe__resource_description(const pcmk_resource_t *rsc, uint32_t show_opts) { const char * desc = NULL; // User-supplied description if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description)) { desc = crm_element_value(rsc->priv->xml, PCMK_XA_DESCRIPTION); } return desc; } /* Never display node attributes whose name starts with one of these prefixes */ #define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX, \ PCMK__NODE_ATTR_SHUTDOWN, PCMK_NODE_ATTR_TERMINATE, \ PCMK_NODE_ATTR_STANDBY, "#", NULL } static int compare_attribute(gconstpointer a, gconstpointer b) { int rc; rc = strcmp((const char *)a, (const char *)b); return rc; } /*! * \internal * \brief Determine whether extended information about an attribute should be added. * * \param[in] node Node that ran this resource * \param[in,out] rsc_list List of resources for this node * \param[in,out] scheduler Scheduler data * \param[in] attrname Attribute to find * \param[out] expected_score Expected value for this attribute * * \return true if extended information should be printed, false otherwise * \note Currently, extended information is only supported for ping/pingd * resources, for which a message will be printed if connectivity is lost * or degraded. */ static bool add_extra_info(const pcmk_node_t *node, GList *rsc_list, pcmk_scheduler_t *scheduler, const char *attrname, int *expected_score) { GList *gIter = NULL; for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data; const char *type = g_hash_table_lookup(rsc->priv->meta, PCMK_XA_TYPE); const char *name = NULL; GHashTable *params = NULL; if (rsc->priv->children != NULL) { if (add_extra_info(node, rsc->priv->children, scheduler, attrname, expected_score)) { return true; } } if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) { continue; } params = pe_rsc_params(rsc, node, scheduler); name = g_hash_table_lookup(params, PCMK_XA_NAME); if (name == NULL) { name = "pingd"; } /* To identify the resource with the attribute name. */ if (pcmk__str_eq(name, attrname, pcmk__str_casei)) { int host_list_num = 0; const char *hosts = g_hash_table_lookup(params, "host_list"); const char *multiplier = g_hash_table_lookup(params, "multiplier"); int multiplier_i; if (hosts) { char **host_list = g_strsplit(hosts, " ", 0); host_list_num = g_strv_length(host_list); g_strfreev(host_list); } if ((multiplier == NULL) || (pcmk__scan_min_int(multiplier, &multiplier_i, INT_MIN) != pcmk_rc_ok)) { /* The ocf:pacemaker:ping resource agent defaults multiplier to * 1. The agent currently does not handle invalid text, but it * should, and this would be a reasonable choice ... */ multiplier_i = 1; } *expected_score = host_list_num * multiplier_i; return true; } } return false; } static GList * filter_attr_list(GList *attr_list, char *name) { int i; const char *filt_str[] = FILTER_STR; CRM_CHECK(name != NULL, return attr_list); /* filtering automatic attributes */ for (i = 0; filt_str[i] != NULL; i++) { if (g_str_has_prefix(name, filt_str[i])) { return attr_list; } } return g_list_insert_sorted(attr_list, name, compare_attribute); } static GList * get_operation_list(xmlNode *rsc_entry) { GList *op_list = NULL; xmlNode *rsc_op = NULL; for (rsc_op = pcmk__xe_first_child(rsc_entry, NULL, NULL, NULL); rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op)) { const char *task = crm_element_value(rsc_op, PCMK_XA_OPERATION); const char *interval_ms_s = crm_element_value(rsc_op, PCMK_META_INTERVAL); const char *op_rc = crm_element_value(rsc_op, PCMK__XA_RC_CODE); int op_rc_i; pcmk__scan_min_int(op_rc, &op_rc_i, 0); /* Display 0-interval monitors as "probe" */ if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei) && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { task = "probe"; } /* Ignore notifies and some probes */ if (pcmk__str_eq(task, PCMK_ACTION_NOTIFY, pcmk__str_none) || (pcmk__str_eq(task, "probe", pcmk__str_none) && (op_rc_i == CRM_EX_NOT_RUNNING))) { continue; } if (pcmk__xe_is(rsc_op, PCMK__XE_LRM_RSC_OP)) { op_list = g_list_append(op_list, rsc_op); } } op_list = g_list_sort(op_list, sort_op_by_callid); return op_list; } static void add_dump_node(gpointer key, gpointer value, gpointer user_data) { xmlNodePtr node = user_data; node = pcmk__xe_create(node, (const char *) key); pcmk__xe_set_content(node, "%s", (const char *) value); } static void append_dump_text(gpointer key, gpointer value, gpointer user_data) { char **dump_text = user_data; char *new_text = crm_strdup_printf("%s %s=%s", *dump_text, (char *)key, (char *)value); free(*dump_text); *dump_text = new_text; } #define XPATH_STACK "//" PCMK_XE_NVPAIR \ "[@" PCMK_XA_NAME "='" \ PCMK_OPT_CLUSTER_INFRASTRUCTURE "']" static const char * get_cluster_stack(pcmk_scheduler_t *scheduler) { xmlNode *stack = get_xpath_object(XPATH_STACK, scheduler->input, LOG_DEBUG); if (stack != NULL) { return crm_element_value(stack, PCMK_XA_VALUE); } return PCMK_VALUE_UNKNOWN; } static char * last_changed_string(const char *last_written, const char *user, const char *client, const char *origin) { if (last_written != NULL || user != NULL || client != NULL || origin != NULL) { return crm_strdup_printf("%s%s%s%s%s%s%s", last_written ? last_written : "", user ? " by " : "", user ? user : "", client ? " via " : "", client ? client : "", origin ? " on " : "", origin ? origin : ""); } else { return strdup(""); } } static char * op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s, int rc, bool print_timing) { const char *call = crm_element_value(xml_op, PCMK__XA_CALL_ID); char *interval_str = NULL; char *buf = NULL; if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { char *pair = pcmk__format_nvpair(PCMK_XA_INTERVAL, interval_ms_s, "ms"); interval_str = crm_strdup_printf(" %s", pair); free(pair); } if (print_timing) { char *last_change_str = NULL; char *exec_str = NULL; char *queue_str = NULL; const char *value = NULL; time_t epoch = 0; if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &epoch) == pcmk_ok) && (epoch > 0)) { char *epoch_str = pcmk__epoch2str(&epoch, 0); last_change_str = crm_strdup_printf(" %s=\"%s\"", PCMK_XA_LAST_RC_CHANGE, pcmk__s(epoch_str, "")); free(epoch_str); } value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); if (value) { char *pair = pcmk__format_nvpair(PCMK_XA_EXEC_TIME, value, "ms"); exec_str = crm_strdup_printf(" %s", pair); free(pair); } value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME); if (value) { char *pair = pcmk__format_nvpair(PCMK_XA_QUEUE_TIME, value, "ms"); queue_str = crm_strdup_printf(" %s", pair); free(pair); } buf = crm_strdup_printf("(%s) %s:%s%s%s%s rc=%d (%s)", call, task, interval_str ? interval_str : "", last_change_str ? last_change_str : "", exec_str ? exec_str : "", queue_str ? queue_str : "", rc, services_ocf_exitcode_str(rc)); if (last_change_str) { free(last_change_str); } if (exec_str) { free(exec_str); } if (queue_str) { free(queue_str); } } else { buf = crm_strdup_printf("(%s) %s%s%s", call, task, interval_str ? ":" : "", interval_str ? interval_str : ""); } if (interval_str) { free(interval_str); } return buf; } static char * resource_history_string(pcmk_resource_t *rsc, const char *rsc_id, bool all, int failcount, time_t last_failure) { char *buf = NULL; if (rsc == NULL) { buf = crm_strdup_printf("%s: orphan", rsc_id); } else if (all || failcount || last_failure > 0) { char *failcount_s = NULL; char *lastfail_s = NULL; if (failcount > 0) { failcount_s = crm_strdup_printf(" %s=%d", PCMK_XA_FAIL_COUNT, failcount); } else { failcount_s = strdup(""); } if (last_failure > 0) { buf = pcmk__epoch2str(&last_failure, 0); lastfail_s = crm_strdup_printf(" %s='%s'", PCMK_XA_LAST_FAILURE, buf); free(buf); } buf = crm_strdup_printf("%s: " PCMK_META_MIGRATION_THRESHOLD "=%d%s%s", rsc_id, rsc->priv->ban_after_failures, failcount_s, pcmk__s(lastfail_s, "")); free(failcount_s); free(lastfail_s); } else { buf = crm_strdup_printf("%s:", rsc_id); } return buf; } /*! * \internal * \brief Get a node's feature set for status display purposes * * \param[in] node Node to check * * \return String representation of feature set if the node is fully up (using * "<3.15.1" for older nodes that don't set the #feature-set attribute), * otherwise NULL */ static const char * get_node_feature_set(const pcmk_node_t *node) { if (node->details->online && pcmk_is_set(node->priv->flags, pcmk__node_expected_up) && !pcmk__is_pacemaker_remote_node(node)) { const char *feature_set = g_hash_table_lookup(node->priv->attrs, CRM_ATTR_FEATURE_SET); /* The feature set attribute is present since 3.15.1. If it is missing, * then the node must be running an earlier version. */ return pcmk__s(feature_set, "<3.15.1"); } return NULL; } static bool is_mixed_version(pcmk_scheduler_t *scheduler) { const char *feature_set = NULL; for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = gIter->data; const char *node_feature_set = get_node_feature_set(node); if (node_feature_set != NULL) { if (feature_set == NULL) { feature_set = node_feature_set; } else if (strcmp(feature_set, node_feature_set) != 0) { return true; } } } return false; } static void formatted_xml_buf(const pcmk_resource_t *rsc, GString *xml_buf, bool raw) { if (raw && (rsc->priv->orig_xml != NULL)) { pcmk__xml_string(rsc->priv->orig_xml, pcmk__xml_fmt_pretty, xml_buf, 0); } else { pcmk__xml_string(rsc->priv->xml, pcmk__xml_fmt_pretty, xml_buf, 0); } } #define XPATH_DC_VERSION "//" PCMK_XE_NVPAIR \ "[@" PCMK_XA_NAME "='" PCMK_OPT_DC_VERSION "']" PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *", "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t") static int cluster_summary(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); uint32_t section_opts = va_arg(args, uint32_t); uint32_t show_opts = va_arg(args, uint32_t); int rc = pcmk_rc_no_output; const char *stack_s = get_cluster_stack(scheduler); if (pcmk_is_set(section_opts, pcmk_section_stack)) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-stack", stack_s, pcmkd_state); } if (pcmk_is_set(section_opts, pcmk_section_dc)) { xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION, scheduler->input, LOG_DEBUG); const char *dc_version_s = dc_version? crm_element_value(dc_version, PCMK_XA_VALUE) : NULL; const char *quorum = crm_element_value(scheduler->input, PCMK_XA_HAVE_QUORUM); char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL; bool mixed_version = is_mixed_version(scheduler); PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-dc", scheduler->dc_node, quorum, dc_version_s, dc_name, mixed_version); free(dc_name); } if (pcmk_is_set(section_opts, pcmk_section_times)) { const char *last_written = crm_element_value(scheduler->input, PCMK_XA_CIB_LAST_WRITTEN); const char *user = crm_element_value(scheduler->input, PCMK_XA_UPDATE_USER); const char *client = crm_element_value(scheduler->input, PCMK_XA_UPDATE_CLIENT); const char *origin = crm_element_value(scheduler->input, PCMK_XA_UPDATE_ORIGIN); PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-times", scheduler->localhost, last_written, user, client, origin); } if (pcmk_is_set(section_opts, pcmk_section_counts)) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-counts", g_list_length(scheduler->nodes), scheduler->ninstances, scheduler->disabled_resources, scheduler->blocked_resources); } if (pcmk_is_set(section_opts, pcmk_section_options)) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-options", scheduler); } PCMK__OUTPUT_LIST_FOOTER(out, rc); if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) { if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) { rc = pcmk_rc_ok; } } return rc; } PCMK__OUTPUT_ARGS("cluster-summary", "pcmk_scheduler_t *", "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t") static int cluster_summary_html(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); uint32_t section_opts = va_arg(args, uint32_t); uint32_t show_opts = va_arg(args, uint32_t); int rc = pcmk_rc_no_output; const char *stack_s = get_cluster_stack(scheduler); if (pcmk_is_set(section_opts, pcmk_section_stack)) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-stack", stack_s, pcmkd_state); } /* Always print DC if none, even if not requested */ if ((scheduler->dc_node == NULL) || pcmk_is_set(section_opts, pcmk_section_dc)) { xmlNode *dc_version = get_xpath_object(XPATH_DC_VERSION, scheduler->input, LOG_DEBUG); const char *dc_version_s = dc_version? crm_element_value(dc_version, PCMK_XA_VALUE) : NULL; const char *quorum = crm_element_value(scheduler->input, PCMK_XA_HAVE_QUORUM); char *dc_name = scheduler->dc_node? pe__node_display_name(scheduler->dc_node, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL; bool mixed_version = is_mixed_version(scheduler); PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-dc", scheduler->dc_node, quorum, dc_version_s, dc_name, mixed_version); free(dc_name); } if (pcmk_is_set(section_opts, pcmk_section_times)) { const char *last_written = crm_element_value(scheduler->input, PCMK_XA_CIB_LAST_WRITTEN); const char *user = crm_element_value(scheduler->input, PCMK_XA_UPDATE_USER); const char *client = crm_element_value(scheduler->input, PCMK_XA_UPDATE_CLIENT); const char *origin = crm_element_value(scheduler->input, PCMK_XA_UPDATE_ORIGIN); PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-times", scheduler->localhost, last_written, user, client, origin); } if (pcmk_is_set(section_opts, pcmk_section_counts)) { PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-counts", g_list_length(scheduler->nodes), scheduler->ninstances, scheduler->disabled_resources, scheduler->blocked_resources); } if (pcmk_is_set(section_opts, pcmk_section_options)) { /* Kind of a hack - close the list we may have opened earlier in this * function so we can put all the options into their own list. We * only want to do this on HTML output, though. */ PCMK__OUTPUT_LIST_FOOTER(out, rc); out->begin_list(out, NULL, NULL, "Config Options"); out->message(out, "cluster-options", scheduler); } PCMK__OUTPUT_LIST_FOOTER(out, rc); if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) { if (out->message(out, "maint-mode", scheduler->flags) == pcmk_rc_ok) { rc = pcmk_rc_ok; } } return rc; } char * pe__node_display_name(pcmk_node_t *node, bool print_detail) { char *node_name; const char *node_host = NULL; const char *node_id = NULL; int name_len; CRM_ASSERT((node != NULL) && (node->priv->name != NULL)); /* Host is displayed only if this is a guest node and detail is requested */ if (print_detail && pcmk__is_guest_or_bundle_node(node)) { const pcmk_resource_t *launcher = NULL; const pcmk_node_t *host_node = NULL; launcher = node->priv->remote->priv->launcher; host_node = pcmk__current_node(launcher); if (host_node && host_node->details) { node_host = host_node->priv->name; } if (node_host == NULL) { node_host = ""; /* so we at least get "uname@" to indicate guest */ } } /* Node ID is displayed if different from uname and detail is requested */ if (print_detail && !pcmk__str_eq(node->priv->name, node->priv->id, pcmk__str_casei)) { node_id = node->priv->id; } /* Determine name length */ name_len = strlen(node->priv->name) + 1; if (node_host) { name_len += strlen(node_host) + 1; /* "@node_host" */ } if (node_id) { name_len += strlen(node_id) + 3; /* + " (node_id)" */ } /* Allocate and populate display name */ node_name = pcmk__assert_alloc(name_len, sizeof(char)); strcpy(node_name, node->priv->name); if (node_host) { strcat(node_name, "@"); strcat(node_name, node_host); } if (node_id) { strcat(node_name, " ("); strcat(node_name, node_id); strcat(node_name, ")"); } return node_name; } int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name, ...) { xmlNodePtr xml_node = NULL; va_list pairs; CRM_ASSERT(tag_name != NULL); xml_node = pcmk__output_xml_peek_parent(out); CRM_ASSERT(xml_node != NULL); xml_node = pcmk__xe_create(xml_node, tag_name); va_start(pairs, tag_name); pcmk__xe_set_propv(xml_node, pairs); va_end(pairs); if (is_list) { pcmk__output_xml_push_parent(out, xml_node); } return pcmk_rc_ok; } static const char * role_desc(enum rsc_role_e role) { if (role == pcmk_role_promoted) { return "in " PCMK_ROLE_PROMOTED " role "; } return ""; } PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t") static int ban_html(pcmk__output_t *out, va_list args) { pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *); pcmk__location_t *location = va_arg(args, pcmk__location_t *); uint32_t show_opts = va_arg(args, uint32_t); char *node_name = pe__node_display_name(pe_node, pcmk_is_set(show_opts, pcmk_show_node_id)); char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s", location->id, location->rsc->id, role_desc(location->role_filter), node_name); pcmk__output_create_html_node(out, "li", NULL, NULL, buf); free(node_name); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t") static int ban_text(pcmk__output_t *out, va_list args) { pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *); pcmk__location_t *location = va_arg(args, pcmk__location_t *); uint32_t show_opts = va_arg(args, uint32_t); char *node_name = pe__node_display_name(pe_node, pcmk_is_set(show_opts, pcmk_show_node_id)); out->list_item(out, NULL, "%s\tprevents %s from running %son %s", location->id, location->rsc->id, role_desc(location->role_filter), node_name); free(node_name); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban", "pcmk_node_t *", "pcmk__location_t *", "uint32_t") static int ban_xml(pcmk__output_t *out, va_list args) { pcmk_node_t *pe_node = va_arg(args, pcmk_node_t *); pcmk__location_t *location = va_arg(args, pcmk__location_t *); uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); const char *promoted_only = pcmk__btoa(location->role_filter == pcmk_role_promoted); char *weight_s = pcmk__itoa(pe_node->assign->score); pcmk__output_create_xml_node(out, PCMK_XE_BAN, PCMK_XA_ID, location->id, PCMK_XA_RESOURCE, location->rsc->id, PCMK_XA_NODE, pe_node->priv->name, PCMK_XA_WEIGHT, weight_s, PCMK_XA_PROMOTED_ONLY, promoted_only, /* This is a deprecated alias for * promoted_only. Removing it will break * backward compatibility of the API schema, * which will require an API schema major * version bump. */ PCMK__XA_PROMOTED_ONLY_LEGACY, promoted_only, NULL); free(weight_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ban-list", "pcmk_scheduler_t *", "const char *", "GList *", "uint32_t", "bool") static int ban_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); const char *prefix = va_arg(args, const char *); GList *only_rsc = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer = va_arg(args, int); GList *gIter, *gIter2; int rc = pcmk_rc_no_output; /* Print each ban */ - for (gIter = scheduler->placement_constraints; + for (gIter = scheduler->priv->location_constraints; gIter != NULL; gIter = gIter->next) { pcmk__location_t *location = gIter->data; const pcmk_resource_t *rsc = location->rsc; if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) { continue; } if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) && !pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)), only_rsc, pcmk__str_star_matches)) { continue; } for (gIter2 = location->nodes; gIter2 != NULL; gIter2 = gIter2->next) { pcmk_node_t *node = (pcmk_node_t *) gIter2->data; if (node->assign->score < 0) { PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints"); out->message(out, "ban", node, location, show_opts); } } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") static int cluster_counts_html(pcmk__output_t *out, va_list args) { unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL); xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; child = pcmk__html_create(nodes_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d node%s configured", nnodes, pcmk__plural_s(nnodes)); if (ndisabled && nblocked) { child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ", %d ", nblocked); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "BLOCKED"); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " from further action due to failure)"); } else if (ndisabled && !nblocked) { child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), ndisabled); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ")"); } else if (!ndisabled && nblocked) { child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured (%d ", nresources, pcmk__plural_s(nresources), nblocked); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "BLOCKED"); child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " from further action due to failure)"); } else { child = pcmk__html_create(resources_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%d resource instance%s configured", nresources, pcmk__plural_s(nresources)); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") static int cluster_counts_text(pcmk__output_t *out, va_list args) { unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); out->list_item(out, NULL, "%d node%s configured", nnodes, pcmk__plural_s(nnodes)); if (ndisabled && nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d DISABLED, %d BLOCKED from " "further action due to failure)", nresources, pcmk__plural_s(nresources), ndisabled, nblocked); } else if (ndisabled && !nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d DISABLED)", nresources, pcmk__plural_s(nresources), ndisabled); } else if (!ndisabled && nblocked) { out->list_item(out, NULL, "%d resource instance%s configured " "(%d BLOCKED from further action " "due to failure)", nresources, pcmk__plural_s(nresources), nblocked); } else { out->list_item(out, NULL, "%d resource instance%s configured", nresources, pcmk__plural_s(nresources)); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") static int cluster_counts_xml(pcmk__output_t *out, va_list args) { unsigned int nnodes = va_arg(args, unsigned int); int nresources = va_arg(args, int); int ndisabled = va_arg(args, int); int nblocked = va_arg(args, int); xmlNodePtr nodes_node = NULL; xmlNodePtr resources_node = NULL; char *s = NULL; nodes_node = pcmk__output_create_xml_node(out, PCMK_XE_NODES_CONFIGURED, NULL); resources_node = pcmk__output_create_xml_node(out, PCMK_XE_RESOURCES_CONFIGURED, NULL); s = pcmk__itoa(nnodes); crm_xml_add(nodes_node, PCMK_XA_NUMBER, s); free(s); s = pcmk__itoa(nresources); crm_xml_add(resources_node, PCMK_XA_NUMBER, s); free(s); s = pcmk__itoa(ndisabled); crm_xml_add(resources_node, PCMK_XA_DISABLED, s); free(s); s = pcmk__itoa(nblocked); crm_xml_add(resources_node, PCMK_XA_BLOCKED, s); free(s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *", "char *", "int") static int cluster_dc_html(pcmk__output_t *out, va_list args) { pcmk_node_t *dc = va_arg(args, pcmk_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name = va_arg(args, char *); bool mixed_version = va_arg(args, int); xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Current DC: "); if (dc) { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s (version %s) -", dc_name, pcmk__s(dc_version_s, "unknown")); if (mixed_version) { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, " MIXED-VERSION"); } child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " partition"); if (crm_is_true(quorum)) { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " with"); } else { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, " WITHOUT"); } child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " quorum"); } else { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_WARNING); pcmk__xe_set_content(child, "NONE"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *", "char *", "int") static int cluster_dc_text(pcmk__output_t *out, va_list args) { pcmk_node_t *dc = va_arg(args, pcmk_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name = va_arg(args, char *); bool mixed_version = va_arg(args, int); if (dc) { out->list_item(out, "Current DC", "%s (version %s) - %spartition %s quorum", dc_name, dc_version_s ? dc_version_s : "unknown", mixed_version ? "MIXED-VERSION " : "", crm_is_true(quorum) ? "with" : "WITHOUT"); } else { out->list_item(out, "Current DC", "NONE"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-dc", "pcmk_node_t *", "const char *", "const char *", "char *", "int") static int cluster_dc_xml(pcmk__output_t *out, va_list args) { pcmk_node_t *dc = va_arg(args, pcmk_node_t *); const char *quorum = va_arg(args, const char *); const char *dc_version_s = va_arg(args, const char *); char *dc_name G_GNUC_UNUSED = va_arg(args, char *); bool mixed_version = va_arg(args, int); if (dc) { const char *with_quorum = pcmk__btoa(crm_is_true(quorum)); const char *mixed_version_s = pcmk__btoa(mixed_version); pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC, PCMK_XA_PRESENT, PCMK_VALUE_TRUE, PCMK_XA_VERSION, pcmk__s(dc_version_s, ""), PCMK_XA_NAME, dc->priv->name, PCMK_XA_ID, dc->priv->id, PCMK_XA_WITH_QUORUM, with_quorum, PCMK_XA_MIXED_VERSION, mixed_version_s, NULL); } else { pcmk__output_create_xml_node(out, PCMK_XE_CURRENT_DC, PCMK_XA_PRESENT, PCMK_VALUE_FALSE, NULL); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int") static int cluster_maint_mode_text(pcmk__output_t *out, va_list args) { unsigned long long flags = va_arg(args, unsigned long long); if (pcmk_is_set(flags, pcmk__sched_in_maintenance)) { pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); pcmk__formatted_printf(out, " The cluster will not attempt to start, stop or recover services\n"); return pcmk_rc_ok; } else if (pcmk_is_set(flags, pcmk__sched_stop_all)) { pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); pcmk__formatted_printf(out, " The cluster will keep all resources stopped\n"); return pcmk_rc_ok; } else { return pcmk_rc_no_output; } } PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *") static int cluster_options_html(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) { out->list_item(out, NULL, "STONITH of failed nodes enabled"); } else { out->list_item(out, NULL, "STONITH of failed nodes disabled"); } if (pcmk_is_set(scheduler->flags, pcmk__sched_symmetric_cluster)) { out->list_item(out, NULL, "Cluster is symmetric"); } else { out->list_item(out, NULL, "Cluster is asymmetric"); } switch (scheduler->no_quorum_policy) { case pcmk_no_quorum_freeze: out->list_item(out, NULL, "No quorum policy: Freeze resources"); break; case pcmk_no_quorum_stop: out->list_item(out, NULL, "No quorum policy: Stop ALL resources"); break; case pcmk_no_quorum_demote: out->list_item(out, NULL, "No quorum policy: Demote promotable " "resources and stop all other resources"); break; case pcmk_no_quorum_ignore: out->list_item(out, NULL, "No quorum policy: Ignore"); break; case pcmk_no_quorum_fence: out->list_item(out, NULL, "No quorum policy: Suicide"); break; } if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Resource management: "); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "DISABLED"); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " (the cluster will not attempt to start, stop," " or recover services)"); } else if (pcmk_is_set(scheduler->flags, pcmk__sched_stop_all)) { xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Resource management: "); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "STOPPED"); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " (the cluster will keep all resources stopped)"); } else { out->list_item(out, NULL, "Resource management: enabled"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *") static int cluster_options_log(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); if (pcmk_is_set(scheduler->flags, pcmk__sched_in_maintenance)) { return out->info(out, "Resource management is DISABLED. The cluster will not attempt to start, stop or recover services."); } else if (pcmk_is_set(scheduler->flags, pcmk__sched_stop_all)) { return out->info(out, "Resource management is DISABLED. The cluster has stopped all resources."); } else { return pcmk_rc_no_output; } } PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *") static int cluster_options_text(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); if (pcmk_is_set(scheduler->flags, pcmk__sched_fencing_enabled)) { out->list_item(out, NULL, "STONITH of failed nodes enabled"); } else { out->list_item(out, NULL, "STONITH of failed nodes disabled"); } if (pcmk_is_set(scheduler->flags, pcmk__sched_symmetric_cluster)) { out->list_item(out, NULL, "Cluster is symmetric"); } else { out->list_item(out, NULL, "Cluster is asymmetric"); } switch (scheduler->no_quorum_policy) { case pcmk_no_quorum_freeze: out->list_item(out, NULL, "No quorum policy: Freeze resources"); break; case pcmk_no_quorum_stop: out->list_item(out, NULL, "No quorum policy: Stop ALL resources"); break; case pcmk_no_quorum_demote: out->list_item(out, NULL, "No quorum policy: Demote promotable " "resources and stop all other resources"); break; case pcmk_no_quorum_ignore: out->list_item(out, NULL, "No quorum policy: Ignore"); break; case pcmk_no_quorum_fence: out->list_item(out, NULL, "No quorum policy: Suicide"); break; } return pcmk_rc_ok; } /*! * \internal * \brief Get readable string representation of a no-quorum policy * * \param[in] policy No-quorum policy * * \return String representation of \p policy */ static const char * no_quorum_policy_text(enum pe_quorum_policy policy) { switch (policy) { case pcmk_no_quorum_freeze: return PCMK_VALUE_FREEZE; case pcmk_no_quorum_stop: return PCMK_VALUE_STOP; case pcmk_no_quorum_demote: return PCMK_VALUE_DEMOTE; case pcmk_no_quorum_ignore: return PCMK_VALUE_IGNORE; case pcmk_no_quorum_fence: return PCMK_VALUE_FENCE_LEGACY; default: return PCMK_VALUE_UNKNOWN; } } PCMK__OUTPUT_ARGS("cluster-options", "pcmk_scheduler_t *") static int cluster_options_xml(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); const char *stonith_enabled = pcmk__flag_text(scheduler->flags, pcmk__sched_fencing_enabled); const char *symmetric_cluster = pcmk__flag_text(scheduler->flags, pcmk__sched_symmetric_cluster); const char *no_quorum_policy = no_quorum_policy_text(scheduler->no_quorum_policy); const char *maintenance_mode = pcmk__flag_text(scheduler->flags, pcmk__sched_in_maintenance); const char *stop_all_resources = pcmk__flag_text(scheduler->flags, pcmk__sched_stop_all); char *stonith_timeout_ms_s = pcmk__itoa(scheduler->priv->fence_timeout_ms); char *priority_fencing_delay_ms_s = pcmk__itoa(scheduler->priority_fencing_delay * 1000); pcmk__output_create_xml_node(out, PCMK_XE_CLUSTER_OPTIONS, PCMK_XA_STONITH_ENABLED, stonith_enabled, PCMK_XA_SYMMETRIC_CLUSTER, symmetric_cluster, PCMK_XA_NO_QUORUM_POLICY, no_quorum_policy, PCMK_XA_MAINTENANCE_MODE, maintenance_mode, PCMK_XA_STOP_ALL_RESOURCES, stop_all_resources, PCMK_XA_STONITH_TIMEOUT_MS, stonith_timeout_ms_s, PCMK_XA_PRIORITY_FENCING_DELAY_MS, priority_fencing_delay_ms_s, NULL); free(stonith_timeout_ms_s); free(priority_fencing_delay_ms_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") static int cluster_stack_html(pcmk__output_t *out, va_list args) { const char *stack_s = va_arg(args, const char *); enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Stack: "); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", stack_s); if (pcmkd_state != pcmk_pacemakerd_state_invalid) { child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " ("); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", pcmk__pcmkd_state_enum2friendly(pcmkd_state)); child = pcmk__html_create(node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ")"); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") static int cluster_stack_text(pcmk__output_t *out, va_list args) { const char *stack_s = va_arg(args, const char *); enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); if (pcmkd_state != pcmk_pacemakerd_state_invalid) { out->list_item(out, "Stack", "%s (%s)", stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state)); } else { out->list_item(out, "Stack", "%s", stack_s); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") static int cluster_stack_xml(pcmk__output_t *out, va_list args) { const char *stack_s = va_arg(args, const char *); enum pcmk_pacemakerd_state pcmkd_state = (enum pcmk_pacemakerd_state) va_arg(args, int); const char *state_s = NULL; if (pcmkd_state != pcmk_pacemakerd_state_invalid) { state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state); } pcmk__output_create_xml_node(out, PCMK_XE_STACK, PCMK_XA_TYPE, stack_s, PCMK_XA_PACEMAKERD_STATE, state_s, NULL); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *", "const char *") static int cluster_times_html(pcmk__output_t *out, va_list args) { const char *our_nodename = va_arg(args, const char *); const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL); xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; char *time_s = NULL; child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Last updated: "); child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); time_s = pcmk__epoch2str(NULL, 0); pcmk__xe_set_content(child, "%s", time_s); free(time_s); if (our_nodename != NULL) { child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, " on "); child = pcmk__html_create(updated_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s", our_nodename); } child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "Last change: "); child = pcmk__html_create(changed_node, PCMK__XE_SPAN, NULL, NULL); time_s = last_changed_string(last_written, user, client, origin); pcmk__xe_set_content(child, "%s", time_s); free(time_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *", "const char *") static int cluster_times_xml(pcmk__output_t *out, va_list args) { const char *our_nodename = va_arg(args, const char *); const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); char *time_s = pcmk__epoch2str(NULL, 0); pcmk__output_create_xml_node(out, PCMK_XE_LAST_UPDATE, PCMK_XA_TIME, time_s, PCMK_XA_ORIGIN, our_nodename, NULL); pcmk__output_create_xml_node(out, PCMK_XE_LAST_CHANGE, PCMK_XA_TIME, pcmk__s(last_written, ""), PCMK_XA_USER, pcmk__s(user, ""), PCMK_XA_CLIENT, pcmk__s(client, ""), PCMK_XA_ORIGIN, pcmk__s(origin, ""), NULL); free(time_s); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", "const char *", "const char *", "const char *") static int cluster_times_text(pcmk__output_t *out, va_list args) { const char *our_nodename = va_arg(args, const char *); const char *last_written = va_arg(args, const char *); const char *user = va_arg(args, const char *); const char *client = va_arg(args, const char *); const char *origin = va_arg(args, const char *); char *time_s = pcmk__epoch2str(NULL, 0); out->list_item(out, "Last updated", "%s%s%s", time_s, (our_nodename != NULL)? " on " : "", pcmk__s(our_nodename, "")); free(time_s); time_s = last_changed_string(last_written, user, client, origin); out->list_item(out, "Last change", " %s", time_s); free(time_s); return pcmk_rc_ok; } /*! * \internal * \brief Display a failed action in less-technical natural language * * \param[in,out] out Output object to use for display * \param[in] xml_op XML containing failed action * \param[in] op_key Operation key of failed action * \param[in] node_name Where failed action occurred * \param[in] rc OCF exit code of failed action * \param[in] status Execution status of failed action * \param[in] exit_reason Exit reason given for failed action * \param[in] exec_time String containing execution time in milliseconds */ static void failed_action_friendly(pcmk__output_t *out, const xmlNode *xml_op, const char *op_key, const char *node_name, int rc, int status, const char *exit_reason, const char *exec_time) { char *rsc_id = NULL; char *task = NULL; guint interval_ms = 0; time_t last_change_epoch = 0; GString *str = NULL; if (pcmk__str_empty(op_key) || !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) { pcmk__str_update(&rsc_id, "unknown resource"); pcmk__str_update(&task, "unknown action"); interval_ms = 0; } CRM_ASSERT((rsc_id != NULL) && (task != NULL)); str = g_string_sized_new(256); // Should be sufficient for most messages pcmk__g_strcat(str, rsc_id, " ", NULL); if (interval_ms != 0) { pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ", NULL); } pcmk__g_strcat(str, pcmk__readable_action(task, interval_ms), " on ", node_name, NULL); if (status == PCMK_EXEC_DONE) { pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'", NULL); if (!pcmk__str_empty(exit_reason)) { pcmk__g_strcat(str, " (", exit_reason, ")", NULL); } } else { pcmk__g_strcat(str, " could not be executed (", pcmk_exec_status_str(status), NULL); if (!pcmk__str_empty(exit_reason)) { pcmk__g_strcat(str, ": ", exit_reason, NULL); } g_string_append_c(str, ')'); } if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &last_change_epoch) == pcmk_ok) { char *s = pcmk__epoch2str(&last_change_epoch, 0); pcmk__g_strcat(str, " at ", s, NULL); free(s); } if (!pcmk__str_empty(exec_time)) { int exec_time_ms = 0; if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok) && (exec_time_ms > 0)) { pcmk__g_strcat(str, " after ", pcmk__readable_interval(exec_time_ms), NULL); } } out->list_item(out, NULL, "%s", str->str); g_string_free(str, TRUE); free(rsc_id); free(task); } /*! * \internal * \brief Display a failed action with technical details * * \param[in,out] out Output object to use for display * \param[in] xml_op XML containing failed action * \param[in] op_key Operation key of failed action * \param[in] node_name Where failed action occurred * \param[in] rc OCF exit code of failed action * \param[in] status Execution status of failed action * \param[in] exit_reason Exit reason given for failed action * \param[in] exec_time String containing execution time in milliseconds */ static void failed_action_technical(pcmk__output_t *out, const xmlNode *xml_op, const char *op_key, const char *node_name, int rc, int status, const char *exit_reason, const char *exec_time) { const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID); const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME); const char *exit_status = services_ocf_exitcode_str(rc); const char *lrm_status = pcmk_exec_status_str(status); time_t last_change_epoch = 0; GString *str = NULL; if (pcmk__str_empty(op_key)) { op_key = "unknown operation"; } if (pcmk__str_empty(exit_status)) { exit_status = "unknown exit status"; } if (pcmk__str_empty(call_id)) { call_id = "unknown"; } str = g_string_sized_new(256); g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'", op_key, node_name, exit_status, rc, call_id, lrm_status); if (!pcmk__str_empty(exit_reason)) { pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL); } if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &last_change_epoch) == pcmk_ok) { char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0); pcmk__g_strcat(str, ", " PCMK_XA_LAST_RC_CHANGE "=" "'", last_change_str, "'", NULL); free(last_change_str); } if (!pcmk__str_empty(queue_time)) { pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL); } if (!pcmk__str_empty(exec_time)) { pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL); } out->list_item(out, NULL, "%s", str->str); g_string_free(str, TRUE); } PCMK__OUTPUT_ARGS("failed-action", "xmlNode *", "uint32_t") static int failed_action_default(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); uint32_t show_opts = va_arg(args, uint32_t); const char *op_key = pcmk__xe_history_key(xml_op); const char *node_name = crm_element_value(xml_op, PCMK_XA_UNAME); const char *exit_reason = crm_element_value(xml_op, PCMK_XA_EXIT_REASON); const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); int rc; int status; pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0); pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status, 0); if (pcmk__str_empty(node_name)) { node_name = "unknown node"; } if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) { failed_action_technical(out, xml_op, op_key, node_name, rc, status, exit_reason, exec_time); } else { failed_action_friendly(out, xml_op, op_key, node_name, rc, status, exit_reason, exec_time); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("failed-action", "xmlNode *", "uint32_t") static int failed_action_xml(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); const char *op_key = pcmk__xe_history_key(xml_op); const char *op_key_name = PCMK_XA_OP_KEY; int rc; int status; const char *uname = crm_element_value(xml_op, PCMK_XA_UNAME); const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID); const char *exitstatus = NULL; const char *exit_reason = pcmk__s(crm_element_value(xml_op, PCMK_XA_EXIT_REASON), "none"); const char *status_s = NULL; time_t epoch = 0; gchar *exit_reason_esc = NULL; char *rc_s = NULL; xmlNodePtr node = NULL; if (pcmk__xml_needs_escape(exit_reason, pcmk__xml_escape_attr)) { exit_reason_esc = pcmk__xml_escape(exit_reason, pcmk__xml_escape_attr); exit_reason = exit_reason_esc; } pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_RC_CODE), &rc, 0); pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status, 0); if (crm_element_value(xml_op, PCMK__XA_OPERATION_KEY) == NULL) { op_key_name = PCMK_XA_ID; } exitstatus = services_ocf_exitcode_str(rc); rc_s = pcmk__itoa(rc); status_s = pcmk_exec_status_str(status); node = pcmk__output_create_xml_node(out, PCMK_XE_FAILURE, op_key_name, op_key, PCMK_XA_NODE, uname, PCMK_XA_EXITSTATUS, exitstatus, PCMK_XA_EXITREASON, exit_reason, PCMK_XA_EXITCODE, rc_s, PCMK_XA_CALL, call_id, PCMK_XA_STATUS, status_s, NULL); free(rc_s); if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &epoch) == pcmk_ok) && (epoch > 0)) { const char *queue_time = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME); const char *exec = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION); guint interval_ms = 0; char *interval_ms_s = NULL; char *rc_change = pcmk__epoch2str(&epoch, crm_time_log_date |crm_time_log_timeofday |crm_time_log_with_timezone); crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms); interval_ms_s = crm_strdup_printf("%u", interval_ms); pcmk__xe_set_props(node, PCMK_XA_LAST_RC_CHANGE, rc_change, PCMK_XA_QUEUED, queue_time, PCMK_XA_EXEC, exec, PCMK_XA_INTERVAL, interval_ms_s, PCMK_XA_TASK, task, NULL); free(interval_ms_s); free(rc_change); } g_free(exit_reason_esc); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("failed-action-list", "pcmk_scheduler_t *", "GList *", "GList *", "uint32_t", "bool") static int failed_action_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer = va_arg(args, int); xmlNode *xml_op = NULL; int rc = pcmk_rc_no_output; if (xmlChildElementCount(scheduler->failed) == 0) { return rc; } for (xml_op = pcmk__xe_first_child(scheduler->failed, NULL, NULL, NULL); xml_op != NULL; xml_op = pcmk__xe_next(xml_op)) { char *rsc = NULL; if (!pcmk__str_in_list(crm_element_value(xml_op, PCMK_XA_UNAME), only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } if (pcmk_xe_mask_probe_failure(xml_op)) { continue; } if (!parse_op_key(pcmk__xe_history_key(xml_op), &rsc, NULL, NULL)) { continue; } if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) { free(rsc); continue; } free(rsc); PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions"); out->message(out, "failed-action", xml_op, show_opts); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } static void status_node(pcmk_node_t *node, xmlNodePtr parent, uint32_t show_opts) { int health = pe__node_health(node); xmlNode *child = NULL; // Cluster membership if (node->details->online) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK_VALUE_ONLINE); pcmk__xe_set_content(child, " online"); } else { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK_VALUE_OFFLINE); pcmk__xe_set_content(child, " OFFLINE"); } // Standby mode if (pcmk_is_set(node->priv->flags, pcmk__node_fail_standby)) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK_VALUE_STANDBY); if (node->details->running_rsc == NULL) { pcmk__xe_set_content(child, " (in standby due to " PCMK_META_ON_FAIL ")"); } else { pcmk__xe_set_content(child, " (in standby due to " PCMK_META_ON_FAIL "," " with active resources)"); } } else if (pcmk_is_set(node->priv->flags, pcmk__node_standby)) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK_VALUE_STANDBY); if (node->details->running_rsc == NULL) { pcmk__xe_set_content(child, " (in standby)"); } else { pcmk__xe_set_content(child, " (in standby, with active resources)"); } } // Maintenance mode if (node->details->maintenance) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK__VALUE_MAINT); pcmk__xe_set_content(child, " (in maintenance mode)"); } // Node health if (health < 0) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK__VALUE_HEALTH_RED); pcmk__xe_set_content(child, " (health is RED)"); } else if (health == 0) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, PCMK__VALUE_HEALTH_YELLOW); pcmk__xe_set_content(child, " (health is YELLOW)"); } // Feature set if (pcmk_is_set(show_opts, pcmk_show_feature_set)) { const char *feature_set = get_node_feature_set(node); if (feature_set != NULL) { child = pcmk__html_create(parent, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, ", feature set %s", feature_set); } } } PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *", "GList *") static int node_html(pcmk__output_t *out, va_list args) { pcmk_node_t *node = va_arg(args, pcmk_node_t *); uint32_t show_opts = va_arg(args, uint32_t); bool full = va_arg(args, int); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); if (full) { xmlNode *item_node = NULL; xmlNode *child = NULL; if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) { GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); out->begin_list(out, NULL, NULL, "%s:", node_name); item_node = pcmk__output_xml_create_parent(out, "li", NULL); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Status:"); status_node(node, item_node, show_opts); if (rscs != NULL) { uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs; out->begin_list(out, NULL, NULL, "Resources"); pe__rscs_brief_output(out, rscs, new_show_opts); out->end_list(out); } pcmk__output_xml_pop_parent(out); out->end_list(out); } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { GList *lpc2 = NULL; int rc = pcmk_rc_no_output; out->begin_list(out, NULL, NULL, "%s:", node_name); item_node = pcmk__output_xml_create_parent(out, "li", NULL); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "Status:"); status_node(node, item_node, show_opts); for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) lpc2->data; PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources"); show_opts |= pcmk_show_rsc_only; out->message(out, pcmk__map_element_name(rsc->priv->xml), show_opts, rsc, only_node, only_rsc); } PCMK__OUTPUT_LIST_FOOTER(out, rc); pcmk__output_xml_pop_parent(out); out->end_list(out); } else { item_node = pcmk__output_create_xml_node(out, "li", NULL); child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "%s:", node_name); status_node(node, item_node, show_opts); } } else { out->begin_list(out, NULL, NULL, "%s:", node_name); } free(node_name); return pcmk_rc_ok; } /*! * \internal * \brief Get a human-friendly textual description of a node's status * * \param[in] node Node to check * * \return String representation of node's status */ static const char * node_text_status(const pcmk_node_t *node) { if (node->details->unclean) { if (node->details->online) { return "UNCLEAN (online)"; } else if (node->details->pending) { return "UNCLEAN (pending)"; } else { return "UNCLEAN (offline)"; } } else if (node->details->pending) { return "pending"; } else if (pcmk_is_set(node->priv->flags, pcmk__node_fail_standby) && node->details->online) { return "standby (" PCMK_META_ON_FAIL ")"; } else if (pcmk_is_set(node->priv->flags, pcmk__node_standby)) { if (!node->details->online) { return "OFFLINE (standby)"; } else if (node->details->running_rsc == NULL) { return "standby"; } else { return "standby (with active resources)"; } } else if (node->details->maintenance) { if (node->details->online) { return "maintenance"; } else { return "OFFLINE (maintenance)"; } } else if (node->details->online) { return "online"; } return "OFFLINE"; } PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *", "GList *") static int node_text(pcmk__output_t *out, va_list args) { pcmk_node_t *node = va_arg(args, pcmk_node_t *); uint32_t show_opts = va_arg(args, uint32_t); bool full = va_arg(args, int); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); if (full) { char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); GString *str = g_string_sized_new(64); int health = pe__node_health(node); // Create a summary line with node type, name, and status if (pcmk__is_guest_or_bundle_node(node)) { g_string_append(str, "GuestNode"); } else if (pcmk__is_remote_node(node)) { g_string_append(str, "RemoteNode"); } else { g_string_append(str, "Node"); } pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL); if (health < 0) { g_string_append(str, " (health is RED)"); } else if (health == 0) { g_string_append(str, " (health is YELLOW)"); } if (pcmk_is_set(show_opts, pcmk_show_feature_set)) { const char *feature_set = get_node_feature_set(node); if (feature_set != NULL) { pcmk__g_strcat(str, ", feature set ", feature_set, NULL); } } /* If we're grouping by node, print its resources */ if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { if (pcmk_is_set(show_opts, pcmk_show_brief)) { GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); if (rscs != NULL) { uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs; out->begin_list(out, NULL, NULL, "%s", str->str); out->begin_list(out, NULL, NULL, "Resources"); pe__rscs_brief_output(out, rscs, new_show_opts); out->end_list(out); out->end_list(out); g_list_free(rscs); } } else { GList *gIter2 = NULL; out->begin_list(out, NULL, NULL, "%s", str->str); out->begin_list(out, NULL, NULL, "Resources"); for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) gIter2->data; show_opts |= pcmk_show_rsc_only; out->message(out, pcmk__map_element_name(rsc->priv->xml), show_opts, rsc, only_node, only_rsc); } out->end_list(out); out->end_list(out); } } else { out->list_item(out, NULL, "%s", str->str); } g_string_free(str, TRUE); free(node_name); } else { char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); out->begin_list(out, NULL, NULL, "Node: %s", node_name); free(node_name); } return pcmk_rc_ok; } /*! * \internal * \brief Convert an integer health value to a string representation * * \param[in] health Integer health value * * \retval \c PCMK_VALUE_RED if \p health is less than 0 * \retval \c PCMK_VALUE_YELLOW if \p health is equal to 0 * \retval \c PCMK_VALUE_GREEN if \p health is greater than 0 */ static const char * health_text(int health) { if (health < 0) { return PCMK_VALUE_RED; } else if (health == 0) { return PCMK_VALUE_YELLOW; } else { return PCMK_VALUE_GREEN; } } /*! * \internal * \brief Convert a node variant to a string representation * * \param[in] variant Node variant * * \retval \c PCMK_VALUE_MEMBER if \p node_type is \c pcmk__node_variant_cluster * \retval \c PCMK_VALUE_REMOTE if \p node_type is \c pcmk__node_variant_remote * \retval \c PCMK__VALUE_PING if \p node_type is \c pcmk__node_variant_ping * \retval \c PCMK_VALUE_UNKNOWN otherwise */ static const char * node_variant_text(enum pcmk__node_variant variant) { switch (variant) { case pcmk__node_variant_cluster: return PCMK_VALUE_MEMBER; case pcmk__node_variant_remote: return PCMK_VALUE_REMOTE; case pcmk__node_variant_ping: return PCMK__VALUE_PING; default: return PCMK_VALUE_UNKNOWN; } } PCMK__OUTPUT_ARGS("node", "pcmk_node_t *", "uint32_t", "bool", "GList *", "GList *") static int node_xml(pcmk__output_t *out, va_list args) { pcmk_node_t *node = va_arg(args, pcmk_node_t *); uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); bool full = va_arg(args, int); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); if (full) { const char *online = pcmk__btoa(node->details->online); const char *standby = pcmk__flag_text(node->priv->flags, pcmk__node_standby); const char *standby_onfail = pcmk__flag_text(node->priv->flags, pcmk__node_fail_standby); const char *maintenance = pcmk__btoa(node->details->maintenance); const char *pending = pcmk__btoa(node->details->pending); const char *unclean = pcmk__btoa(node->details->unclean); const char *health = health_text(pe__node_health(node)); const char *feature_set = get_node_feature_set(node); const char *shutdown = pcmk__btoa(node->details->shutdown); const char *expected_up = pcmk__flag_text(node->priv->flags, pcmk__node_expected_up); const bool is_dc = pcmk__same_node(node, node->priv->scheduler->dc_node); int length = g_list_length(node->details->running_rsc); char *resources_running = pcmk__itoa(length); const char *node_type = node_variant_text(node->priv->variant); int rc = pcmk_rc_ok; rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_NODE, PCMK_XA_NAME, node->priv->name, PCMK_XA_ID, node->priv->id, PCMK_XA_ONLINE, online, PCMK_XA_STANDBY, standby, PCMK_XA_STANDBY_ONFAIL, standby_onfail, PCMK_XA_MAINTENANCE, maintenance, PCMK_XA_PENDING, pending, PCMK_XA_UNCLEAN, unclean, PCMK_XA_HEALTH, health, PCMK_XA_FEATURE_SET, feature_set, PCMK_XA_SHUTDOWN, shutdown, PCMK_XA_EXPECTED_UP, expected_up, PCMK_XA_IS_DC, pcmk__btoa(is_dc), PCMK_XA_RESOURCES_RUNNING, resources_running, PCMK_XA_TYPE, node_type, NULL); free(resources_running); CRM_ASSERT(rc == pcmk_rc_ok); if (pcmk__is_guest_or_bundle_node(node)) { xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); crm_xml_add(xml_node, PCMK_XA_ID_AS_RESOURCE, node->priv->remote->priv->launcher->id); } if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { GList *lpc = NULL; for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) lpc->data; show_opts |= pcmk_show_rsc_only; out->message(out, pcmk__map_element_name(rsc->priv->xml), show_opts, rsc, only_node, only_rsc); } } out->end_list(out); } else { pcmk__output_xml_create_parent(out, PCMK_XE_NODE, PCMK_XA_NAME, node->priv->name, NULL); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") static int node_attribute_text(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); bool add_extra = va_arg(args, int); int expected_score = va_arg(args, int); if (add_extra) { int v; if (value == NULL) { v = 0; } else { pcmk__scan_min_int(value, &v, INT_MIN); } if (v <= 0) { out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value); } else if (v < expected_score) { out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score); } else { out->list_item(out, NULL, "%-32s\t: %-10s", name, value); } } else { out->list_item(out, NULL, "%-32s\t: %-10s", name, value); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") static int node_attribute_html(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); bool add_extra = va_arg(args, int); int expected_score = va_arg(args, int); if (add_extra) { int v = 0; xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL); xmlNode *child = NULL; if (value != NULL) { pcmk__scan_min_int(value, &v, INT_MIN); } child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, NULL); pcmk__xe_set_content(child, "%s: %s", name, value); if (v <= 0) { child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "(connectivity is lost)"); } else if (v < expected_score) { child = pcmk__html_create(item_node, PCMK__XE_SPAN, NULL, PCMK__VALUE_BOLD); pcmk__xe_set_content(child, "(connectivity is degraded -- expected %d)", expected_score); } } else { out->list_item(out, NULL, "%s: %s", name, value); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNode *") static int node_and_op(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); xmlNodePtr xml_op = va_arg(args, xmlNodePtr); pcmk_resource_t *rsc = NULL; gchar *node_str = NULL; char *last_change_str = NULL; const char *op_rsc = crm_element_value(xml_op, PCMK_XA_RESOURCE); int status; time_t last_change = 0; pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status, PCMK_EXEC_UNKNOWN); rsc = pe_find_resource(scheduler->priv->resources, op_rsc); if (rsc) { const pcmk_node_t *node = pcmk__current_node(rsc); const char *target_role = g_hash_table_lookup(rsc->priv->meta, PCMK_META_TARGET_ROLE); uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending; if (node == NULL) { node = rsc->priv->pending_node; } node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node, show_opts, target_role, false); } else { node_str = crm_strdup_printf("Unknown resource %s", op_rsc); } if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &last_change) == pcmk_ok) { const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); last_change_str = crm_strdup_printf(", %s='%s', exec=%sms", PCMK_XA_LAST_RC_CHANGE, pcmk__trim(ctime(&last_change)), exec_time); } out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s", node_str, pcmk__xe_history_key(xml_op), crm_element_value(xml_op, PCMK_XA_UNAME), crm_element_value(xml_op, PCMK__XA_CALL_ID), crm_element_value(xml_op, PCMK__XA_RC_CODE), last_change_str ? last_change_str : "", pcmk_exec_status_str(status)); g_free(node_str); free(last_change_str); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-and-op", "pcmk_scheduler_t *", "xmlNode *") static int node_and_op_xml(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); xmlNodePtr xml_op = va_arg(args, xmlNodePtr); pcmk_resource_t *rsc = NULL; const char *uname = crm_element_value(xml_op, PCMK_XA_UNAME); const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID); const char *rc_s = crm_element_value(xml_op, PCMK__XA_RC_CODE); const char *status_s = NULL; const char *op_rsc = crm_element_value(xml_op, PCMK_XA_RESOURCE); int status; time_t last_change = 0; xmlNode *node = NULL; pcmk__scan_min_int(crm_element_value(xml_op, PCMK__XA_OP_STATUS), &status, PCMK_EXEC_UNKNOWN); status_s = pcmk_exec_status_str(status); node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION, PCMK_XA_OP, pcmk__xe_history_key(xml_op), PCMK_XA_NODE, uname, PCMK_XA_CALL, call_id, PCMK_XA_RC, rc_s, PCMK_XA_STATUS, status_s, NULL); rsc = pe_find_resource(scheduler->priv->resources, op_rsc); if (rsc) { const char *class = crm_element_value(rsc->priv->xml, PCMK_XA_CLASS); const char *provider = crm_element_value(rsc->priv->xml, PCMK_XA_PROVIDER); const char *kind = crm_element_value(rsc->priv->xml, PCMK_XA_TYPE); bool has_provider = pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider); char *agent_tuple = crm_strdup_printf("%s:%s:%s", class, (has_provider? provider : ""), kind); pcmk__xe_set_props(node, PCMK_XA_RSC, rsc_printable_id(rsc), PCMK_XA_AGENT, agent_tuple, NULL); free(agent_tuple); } if (crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &last_change) == pcmk_ok) { const char *last_rc_change = pcmk__trim(ctime(&last_change)); const char *exec_time = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); pcmk__xe_set_props(node, PCMK_XA_LAST_RC_CHANGE, last_rc_change, PCMK_XA_EXEC_TIME, exec_time, NULL); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") static int node_attribute_xml(pcmk__output_t *out, va_list args) { const char *name = va_arg(args, const char *); const char *value = va_arg(args, const char *); bool add_extra = va_arg(args, int); int expected_score = va_arg(args, int); xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_ATTRIBUTE, PCMK_XA_NAME, name, PCMK_XA_VALUE, value, NULL); if (add_extra) { char *buf = pcmk__itoa(expected_score); crm_xml_add(node, PCMK_XA_EXPECTED, buf); free(buf); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-attribute-list", "pcmk_scheduler_t *", "uint32_t", "bool", "GList *", "GList *") static int node_attribute_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer = va_arg(args, int); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); int rc = pcmk_rc_no_output; /* Display each node's attributes */ for (GList *gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = gIter->data; GList *attr_list = NULL; GHashTableIter iter; gpointer key; if (!node || !node->details || !node->details->online) { continue; } g_hash_table_iter_init(&iter, node->priv->attrs); while (g_hash_table_iter_next (&iter, &key, NULL)) { attr_list = filter_attr_list(attr_list, key); } if (attr_list == NULL) { continue; } if (!pcmk__str_in_list(node->priv->name, only_node, pcmk__str_star_matches|pcmk__str_casei)) { g_list_free(attr_list); continue; } PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes"); out->message(out, "node", node, show_opts, false, only_node, only_rsc); for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) { const char *name = aIter->data; const char *value = NULL; int expected_score = 0; bool add_extra = false; value = pcmk__node_attr(node, name, NULL, pcmk__rsc_node_current); add_extra = add_extra_info(node, node->details->running_rsc, scheduler, name, &expected_score); /* Print attribute name and value */ out->message(out, "node-attribute", name, value, add_extra, expected_score); } g_list_free(attr_list); out->end_list(out); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *") static int node_capacity(pcmk__output_t *out, va_list args) { const pcmk_node_t *node = va_arg(args, pcmk_node_t *); const char *comment = va_arg(args, const char *); char *dump_text = crm_strdup_printf("%s: %s capacity:", comment, pcmk__node_name(node)); g_hash_table_foreach(node->priv->utilization, append_dump_text, &dump_text); out->list_item(out, NULL, "%s", dump_text); free(dump_text); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-capacity", "const pcmk_node_t *", "const char *") static int node_capacity_xml(pcmk__output_t *out, va_list args) { const pcmk_node_t *node = va_arg(args, pcmk_node_t *); const char *uname = node->priv->name; const char *comment = va_arg(args, const char *); xmlNodePtr xml_node = pcmk__output_create_xml_node(out, PCMK_XE_CAPACITY, PCMK_XA_NODE, uname, PCMK_XA_COMMENT, comment, NULL); g_hash_table_foreach(node->priv->utilization, add_dump_node, xml_node); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-history-list", "pcmk_scheduler_t *", "pcmk_node_t *", "xmlNode *", "GList *", "GList *", "uint32_t", "uint32_t") static int node_history_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); pcmk_node_t *node = va_arg(args, pcmk_node_t *); xmlNode *node_state = va_arg(args, xmlNode *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t section_opts = va_arg(args, uint32_t); uint32_t show_opts = va_arg(args, uint32_t); xmlNode *lrm_rsc = NULL; xmlNode *rsc_entry = NULL; int rc = pcmk_rc_no_output; lrm_rsc = pcmk__xe_first_child(node_state, PCMK__XE_LRM, NULL, NULL); lrm_rsc = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCES, NULL, NULL); /* Print history of each of the node's resources */ for (rsc_entry = pcmk__xe_first_child(lrm_rsc, PCMK__XE_LRM_RESOURCE, NULL, NULL); rsc_entry != NULL; rsc_entry = pcmk__xe_next_same(rsc_entry)) { const char *rsc_id = crm_element_value(rsc_entry, PCMK_XA_ID); pcmk_resource_t *rsc = pe_find_resource(scheduler->priv->resources, rsc_id); const pcmk_resource_t *parent = pe__const_top_resource(rsc, false); /* We can't use is_filtered here to filter group resources. For is_filtered, * we have to decide whether to check the parent or not. If we check the * parent, all elements of a group will always be printed because that's how * is_filtered works for groups. If we do not check the parent, sometimes * this will filter everything out. * * For other resource types, is_filtered is okay. */ if (pcmk__is_group(parent)) { if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) && !pcmk__str_in_list(rsc_printable_id(parent), only_rsc, pcmk__str_star_matches)) { continue; } } else if (rsc->priv->fns->is_filtered(rsc, only_rsc, TRUE)) { continue; } if (!pcmk_is_set(section_opts, pcmk_section_operations)) { time_t last_failure = 0; int failcount = pe_get_failcount(node, rsc, &last_failure, pcmk__fc_default, NULL); if (failcount <= 0) { continue; } if (rc == pcmk_rc_no_output) { rc = pcmk_rc_ok; out->message(out, "node", node, show_opts, false, only_node, only_rsc); } out->message(out, "resource-history", rsc, rsc_id, false, failcount, last_failure, false); } else { GList *op_list = get_operation_list(rsc_entry); pcmk_resource_t *rsc = NULL; if (op_list == NULL) { continue; } rsc = pe_find_resource(scheduler->priv->resources, crm_element_value(rsc_entry, PCMK_XA_ID)); if (rc == pcmk_rc_no_output) { rc = pcmk_rc_ok; out->message(out, "node", node, show_opts, false, only_node, only_rsc); } out->message(out, "resource-operation-list", scheduler, rsc, node, op_list, show_opts); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") static int node_list_html(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer G_GNUC_UNUSED = va_arg(args, int); int rc = pcmk_rc_no_output; for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; if (!pcmk__str_in_list(node->priv->name, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List"); out->message(out, "node", node, show_opts, true, only_node, only_rsc); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") static int node_list_text(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer = va_arg(args, int); /* space-separated lists of node names */ GString *online_nodes = NULL; GString *online_remote_nodes = NULL; GString *online_guest_nodes = NULL; GString *offline_nodes = NULL; GString *offline_remote_nodes = NULL; int rc = pcmk_rc_no_output; for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); if (!pcmk__str_in_list(node->priv->name, only_node, pcmk__str_star_matches|pcmk__str_casei)) { free(node_name); continue; } PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List"); // Determine whether to display node individually or in a list if (node->details->unclean || node->details->pending || (pcmk_is_set(node->priv->flags, pcmk__node_fail_standby) && node->details->online) || pcmk_is_set(node->priv->flags, pcmk__node_standby) || node->details->maintenance || pcmk_is_set(show_opts, pcmk_show_rscs_by_node) || pcmk_is_set(show_opts, pcmk_show_feature_set) || (pe__node_health(node) <= 0)) { // Display node individually } else if (node->details->online) { // Display online node in a list if (pcmk__is_guest_or_bundle_node(node)) { pcmk__add_word(&online_guest_nodes, 1024, node_name); } else if (pcmk__is_remote_node(node)) { pcmk__add_word(&online_remote_nodes, 1024, node_name); } else { pcmk__add_word(&online_nodes, 1024, node_name); } free(node_name); continue; } else { // Display offline node in a list if (pcmk__is_remote_node(node)) { pcmk__add_word(&offline_remote_nodes, 1024, node_name); } else if (pcmk__is_guest_or_bundle_node(node)) { /* ignore offline guest nodes */ } else { pcmk__add_word(&offline_nodes, 1024, node_name); } free(node_name); continue; } /* If we get here, node is in bad state, or we're grouping by node */ out->message(out, "node", node, show_opts, true, only_node, only_rsc); free(node_name); } /* If we're not grouping by node, summarize nodes by status */ if (online_nodes != NULL) { out->list_item(out, "Online", "[ %s ]", (const char *) online_nodes->str); g_string_free(online_nodes, TRUE); } if (offline_nodes != NULL) { out->list_item(out, "OFFLINE", "[ %s ]", (const char *) offline_nodes->str); g_string_free(offline_nodes, TRUE); } if (online_remote_nodes) { out->list_item(out, "RemoteOnline", "[ %s ]", (const char *) online_remote_nodes->str); g_string_free(online_remote_nodes, TRUE); } if (offline_remote_nodes) { out->list_item(out, "RemoteOFFLINE", "[ %s ]", (const char *) offline_remote_nodes->str); g_string_free(offline_remote_nodes, TRUE); } if (online_guest_nodes != NULL) { out->list_item(out, "GuestOnline", "[ %s ]", (const char *) online_guest_nodes->str); g_string_free(online_guest_nodes, TRUE); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") static int node_list_xml(pcmk__output_t *out, va_list args) { GList *nodes = va_arg(args, GList *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer G_GNUC_UNUSED = va_arg(args, int); /* PCMK_XE_NODES acts as the list's element name for CLI tools that use * pcmk__output_enable_list_element. Otherwise PCMK_XE_NODES is the * value of the list's PCMK_XA_NAME attribute. */ out->begin_list(out, NULL, NULL, PCMK_XE_NODES); for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; if (!pcmk__str_in_list(node->priv->name, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } out->message(out, "node", node, show_opts, true, only_node, only_rsc); } out->end_list(out); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-summary", "pcmk_scheduler_t *", "GList *", "GList *", "uint32_t", "uint32_t", "bool") static int node_summary(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); uint32_t section_opts = va_arg(args, uint32_t); uint32_t show_opts = va_arg(args, uint32_t); bool print_spacer = va_arg(args, int); xmlNode *node_state = NULL; xmlNode *cib_status = pcmk_find_cib_element(scheduler->input, PCMK_XE_STATUS); int rc = pcmk_rc_no_output; if (xmlChildElementCount(cib_status) == 0) { return rc; } for (node_state = pcmk__xe_first_child(cib_status, PCMK__XE_NODE_STATE, NULL, NULL); node_state != NULL; node_state = pcmk__xe_next_same(node_state)) { pcmk_node_t *node = pe_find_node_id(scheduler->nodes, pcmk__xe_id(node_state)); if (!node || !node->details || !node->details->online) { continue; } if (!pcmk__str_in_list(node->priv->name, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary"); out->message(out, "node-history-list", scheduler, node, node_state, only_node, only_rsc, section_opts, show_opts); } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *", "const char *", "const char *") static int node_weight(pcmk__output_t *out, va_list args) { const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *); const char *prefix = va_arg(args, const char *); const char *uname = va_arg(args, const char *); const char *score = va_arg(args, const char *); if (rsc) { out->list_item(out, NULL, "%s: %s allocation score on %s: %s", prefix, rsc->id, uname, score); } else { out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("node-weight", "const pcmk_resource_t *", "const char *", "const char *", "const char *") static int node_weight_xml(pcmk__output_t *out, va_list args) { const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *); const char *prefix = va_arg(args, const char *); const char *uname = va_arg(args, const char *); const char *score = va_arg(args, const char *); xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_NODE_WEIGHT, PCMK_XA_FUNCTION, prefix, PCMK_XA_NODE, uname, PCMK_XA_SCORE, score, NULL); if (rsc) { crm_xml_add(node, PCMK_XA_ID, rsc->id); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("op-history", "xmlNode *", "const char *", "const char *", "int", "uint32_t") static int op_history_text(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); const char *task = va_arg(args, const char *); const char *interval_ms_s = va_arg(args, const char *); int rc = va_arg(args, int); uint32_t show_opts = va_arg(args, uint32_t); char *buf = op_history_string(xml_op, task, interval_ms_s, rc, pcmk_is_set(show_opts, pcmk_show_timing)); out->list_item(out, NULL, "%s", buf); free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("op-history", "xmlNode *", "const char *", "const char *", "int", "uint32_t") static int op_history_xml(pcmk__output_t *out, va_list args) { xmlNodePtr xml_op = va_arg(args, xmlNodePtr); const char *task = va_arg(args, const char *); const char *interval_ms_s = va_arg(args, const char *); int rc = va_arg(args, int); uint32_t show_opts = va_arg(args, uint32_t); const char *call_id = crm_element_value(xml_op, PCMK__XA_CALL_ID); char *rc_s = pcmk__itoa(rc); const char *rc_text = services_ocf_exitcode_str(rc); xmlNodePtr node = NULL; node = pcmk__output_create_xml_node(out, PCMK_XE_OPERATION_HISTORY, PCMK_XA_CALL, call_id, PCMK_XA_TASK, task, PCMK_XA_RC, rc_s, PCMK_XA_RC_TEXT, rc_text, NULL); free(rc_s); if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { char *s = crm_strdup_printf("%sms", interval_ms_s); crm_xml_add(node, PCMK_XA_INTERVAL, s); free(s); } if (pcmk_is_set(show_opts, pcmk_show_timing)) { const char *value = NULL; time_t epoch = 0; if ((crm_element_value_epoch(xml_op, PCMK_XA_LAST_RC_CHANGE, &epoch) == pcmk_ok) && (epoch > 0)) { char *s = pcmk__epoch2str(&epoch, 0); crm_xml_add(node, PCMK_XA_LAST_RC_CHANGE, s); free(s); } value = crm_element_value(xml_op, PCMK_XA_EXEC_TIME); if (value) { char *s = crm_strdup_printf("%sms", value); crm_xml_add(node, PCMK_XA_EXEC_TIME, s); free(s); } value = crm_element_value(xml_op, PCMK_XA_QUEUE_TIME); if (value) { char *s = crm_strdup_printf("%sms", value); crm_xml_add(node, PCMK_XA_QUEUE_TIME, s); free(s); } } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *", "const char *") static int promotion_score(pcmk__output_t *out, va_list args) { pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *); pcmk_node_t *chosen = va_arg(args, pcmk_node_t *); const char *score = va_arg(args, const char *); if (chosen == NULL) { out->list_item(out, NULL, "%s promotion score (inactive): %s", child_rsc->id, score); } else { out->list_item(out, NULL, "%s promotion score on %s: %s", child_rsc->id, pcmk__node_name(chosen), score); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("promotion-score", "pcmk_resource_t *", "pcmk_node_t *", "const char *") static int promotion_score_xml(pcmk__output_t *out, va_list args) { pcmk_resource_t *child_rsc = va_arg(args, pcmk_resource_t *); pcmk_node_t *chosen = va_arg(args, pcmk_node_t *); const char *score = va_arg(args, const char *); xmlNodePtr node = pcmk__output_create_xml_node(out, PCMK_XE_PROMOTION_SCORE, PCMK_XA_ID, child_rsc->id, PCMK_XA_SCORE, score, NULL); if (chosen) { crm_xml_add(node, PCMK_XA_NODE, chosen->priv->name); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool") static int resource_config(pcmk__output_t *out, va_list args) { const pcmk_resource_t *rsc = va_arg(args, const pcmk_resource_t *); GString *xml_buf = g_string_sized_new(1024); bool raw = va_arg(args, int); formatted_xml_buf(rsc, xml_buf, raw); out->output_xml(out, PCMK_XE_XML, xml_buf->str); g_string_free(xml_buf, TRUE); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-config", "const pcmk_resource_t *", "bool") static int resource_config_text(pcmk__output_t *out, va_list args) { pcmk__formatted_printf(out, "Resource XML:\n"); return resource_config(out, args); } PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *", "bool", "int", "time_t", "bool") static int resource_history_text(pcmk__output_t *out, va_list args) { pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); const char *rsc_id = va_arg(args, const char *); bool all = va_arg(args, int); int failcount = va_arg(args, int); time_t last_failure = va_arg(args, time_t); bool as_header = va_arg(args, int); char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure); if (as_header) { out->begin_list(out, NULL, NULL, "%s", buf); } else { out->list_item(out, NULL, "%s", buf); } free(buf); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-history", "pcmk_resource_t *", "const char *", "bool", "int", "time_t", "bool") static int resource_history_xml(pcmk__output_t *out, va_list args) { pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); const char *rsc_id = va_arg(args, const char *); bool all = va_arg(args, int); int failcount = va_arg(args, int); time_t last_failure = va_arg(args, time_t); bool as_header = va_arg(args, int); xmlNodePtr node = pcmk__output_xml_create_parent(out, PCMK_XE_RESOURCE_HISTORY, PCMK_XA_ID, rsc_id, NULL); if (rsc == NULL) { pcmk__xe_set_bool_attr(node, PCMK_XA_ORPHAN, true); } else if (all || failcount || last_failure > 0) { char *migration_s = pcmk__itoa(rsc->priv->ban_after_failures); pcmk__xe_set_props(node, PCMK_XA_ORPHAN, PCMK_VALUE_FALSE, PCMK_META_MIGRATION_THRESHOLD, migration_s, NULL); free(migration_s); if (failcount > 0) { char *s = pcmk__itoa(failcount); crm_xml_add(node, PCMK_XA_FAIL_COUNT, s); free(s); } if (last_failure > 0) { char *s = pcmk__epoch2str(&last_failure, 0); crm_xml_add(node, PCMK_XA_LAST_FAILURE, s); free(s); } } if (!as_header) { pcmk__output_xml_pop_parent(out); } return pcmk_rc_ok; } static void print_resource_header(pcmk__output_t *out, uint32_t show_opts) { if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { /* Active resources have already been printed by node */ out->begin_list(out, NULL, NULL, "Inactive Resources"); } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { out->begin_list(out, NULL, NULL, "Full List of Resources"); } else { out->begin_list(out, NULL, NULL, "Active Resources"); } } PCMK__OUTPUT_ARGS("resource-list", "pcmk_scheduler_t *", "uint32_t", "bool", "GList *", "GList *", "bool") static int resource_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler = va_arg(args, pcmk_scheduler_t *); uint32_t show_opts = va_arg(args, uint32_t); bool print_summary = va_arg(args, int); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); bool print_spacer = va_arg(args, int); GList *rsc_iter; int rc = pcmk_rc_no_output; bool printed_header = false; /* If we already showed active resources by node, and * we're not showing inactive resources, we have nothing to do */ if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { return rc; } /* If we haven't already printed resources grouped by node, * and brief output was requested, print resource summary */ if (pcmk_is_set(show_opts, pcmk_show_brief) && !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { GList *rscs = pe__filter_rsc_list(scheduler->priv->resources, only_rsc); PCMK__OUTPUT_SPACER_IF(out, print_spacer); print_resource_header(out, show_opts); printed_header = true; rc = pe__rscs_brief_output(out, rscs, show_opts); g_list_free(rscs); } /* For each resource, display it if appropriate */ for (rsc_iter = scheduler->priv->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) rsc_iter->data; int x; /* Complex resources may have some sub-resources active and some inactive */ gboolean is_active = rsc->priv->fns->active(rsc, TRUE); gboolean partially_active = rsc->priv->fns->active(rsc, FALSE); /* Skip inactive orphans (deleted but still in CIB) */ if (pcmk_is_set(rsc->flags, pcmk__rsc_removed) && !is_active) { continue; /* Skip active resources if we already displayed them by node */ } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { if (is_active) { continue; } /* Skip primitives already counted in a brief summary */ } else if (pcmk_is_set(show_opts, pcmk_show_brief) && pcmk__is_primitive(rsc)) { continue; /* Skip resources that aren't at least partially active, * unless we're displaying inactive resources */ } else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { continue; } else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) { continue; } if (!printed_header) { PCMK__OUTPUT_SPACER_IF(out, print_spacer); print_resource_header(out, show_opts); printed_header = true; } /* Print this resource */ x = out->message(out, pcmk__map_element_name(rsc->priv->xml), show_opts, rsc, only_node, only_rsc); if (x == pcmk_rc_ok) { rc = pcmk_rc_ok; } } if (print_summary && rc != pcmk_rc_ok) { if (!printed_header) { PCMK__OUTPUT_SPACER_IF(out, print_spacer); print_resource_header(out, show_opts); printed_header = true; } if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { out->list_item(out, NULL, "No inactive resources"); } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { out->list_item(out, NULL, "No resources"); } else { out->list_item(out, NULL, "No active resources"); } } if (printed_header) { out->end_list(out); } return rc; } PCMK__OUTPUT_ARGS("resource-operation-list", "pcmk_scheduler_t *", "pcmk_resource_t *", "pcmk_node_t *", "GList *", "uint32_t") static int resource_operation_list(pcmk__output_t *out, va_list args) { pcmk_scheduler_t *scheduler G_GNUC_UNUSED = va_arg(args, pcmk_scheduler_t *); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); pcmk_node_t *node = va_arg(args, pcmk_node_t *); GList *op_list = va_arg(args, GList *); uint32_t show_opts = va_arg(args, uint32_t); GList *gIter = NULL; int rc = pcmk_rc_no_output; /* Print each operation */ for (gIter = op_list; gIter != NULL; gIter = gIter->next) { xmlNode *xml_op = (xmlNode *) gIter->data; const char *task = crm_element_value(xml_op, PCMK_XA_OPERATION); const char *interval_ms_s = crm_element_value(xml_op, PCMK_META_INTERVAL); const char *op_rc = crm_element_value(xml_op, PCMK__XA_RC_CODE); int op_rc_i; pcmk__scan_min_int(op_rc, &op_rc_i, 0); /* Display 0-interval monitors as "probe" */ if (pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_casei) && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { task = "probe"; } /* If this is the first printed operation, print heading for resource */ if (rc == pcmk_rc_no_output) { time_t last_failure = 0; int failcount = pe_get_failcount(node, rsc, &last_failure, pcmk__fc_default, NULL); out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true, failcount, last_failure, true); rc = pcmk_rc_ok; } /* Print the operation */ out->message(out, "op-history", xml_op, task, interval_ms_s, op_rc_i, show_opts); } /* Free the list we created (no need to free the individual items) */ g_list_free(op_list); PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *", "const char *") static int resource_util(pcmk__output_t *out, va_list args) { pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); pcmk_node_t *node = va_arg(args, pcmk_node_t *); const char *fn = va_arg(args, const char *); char *dump_text = crm_strdup_printf("%s: %s utilization on %s:", fn, rsc->id, pcmk__node_name(node)); g_hash_table_foreach(rsc->priv->utilization, append_dump_text, &dump_text); out->list_item(out, NULL, "%s", dump_text); free(dump_text); return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("resource-util", "pcmk_resource_t *", "pcmk_node_t *", "const char *") static int resource_util_xml(pcmk__output_t *out, va_list args) { pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); pcmk_node_t *node = va_arg(args, pcmk_node_t *); const char *uname = node->priv->name; const char *fn = va_arg(args, const char *); xmlNodePtr xml_node = NULL; xml_node = pcmk__output_create_xml_node(out, PCMK_XE_UTILIZATION, PCMK_XA_RESOURCE, rsc->id, PCMK_XA_NODE, uname, PCMK_XA_FUNCTION, fn, NULL); g_hash_table_foreach(rsc->priv->utilization, add_dump_node, xml_node); return pcmk_rc_ok; } static inline const char * ticket_status(pcmk__ticket_t *ticket) { if (pcmk_is_set(ticket->flags, pcmk__ticket_granted)) { return PCMK_VALUE_GRANTED; } return PCMK_VALUE_REVOKED; } static inline const char * ticket_standby_text(pcmk__ticket_t *ticket) { return pcmk_is_set(ticket->flags, pcmk__ticket_standby)? " [standby]" : ""; } PCMK__OUTPUT_ARGS("ticket", "pcmk__ticket_t *", "bool", "bool") static int ticket_default(pcmk__output_t *out, va_list args) { pcmk__ticket_t *ticket = va_arg(args, pcmk__ticket_t *); bool raw = va_arg(args, int); bool details = va_arg(args, int); GString *detail_str = NULL; if (raw) { out->list_item(out, ticket->id, "%s", ticket->id); return pcmk_rc_ok; } if (details && g_hash_table_size(ticket->state) > 0) { GHashTableIter iter; const char *name = NULL; const char *value = NULL; bool already_added = false; detail_str = g_string_sized_new(100); pcmk__g_strcat(detail_str, "\t(", NULL); g_hash_table_iter_init(&iter, ticket->state); while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) { if (already_added) { g_string_append_printf(detail_str, ", %s=", name); } else { g_string_append_printf(detail_str, "%s=", name); already_added = true; } if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, "expires", NULL)) { char *epoch_str = NULL; long long time_ll; pcmk__scan_ll(value, &time_ll, 0); epoch_str = pcmk__epoch2str((const time_t *) &time_ll, 0); pcmk__g_strcat(detail_str, epoch_str, NULL); free(epoch_str); } else { pcmk__g_strcat(detail_str, value, NULL); } } pcmk__g_strcat(detail_str, ")", NULL); } if (ticket->last_granted > -1) { /* Prior to the introduction of the details & raw arguments to this * function, last-granted would always be added in this block. We need * to preserve that behavior. At the same time, we also need to preserve * the existing behavior from crm_ticket, which would include last-granted * as part of the (...) detail string. * * Luckily we can check detail_str - if it's NULL, either there were no * details, or we are preserving the previous behavior of this function. * If it's not NULL, we are either preserving the previous behavior of * crm_ticket or we were given details=true as an argument. */ if (detail_str == NULL) { char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0); out->list_item(out, NULL, "%s\t%s%s last-granted=\"%s\"", ticket->id, ticket_status(ticket), ticket_standby_text(ticket), pcmk__s(epoch_str, "")); free(epoch_str); } else { out->list_item(out, NULL, "%s\t%s%s %s", ticket->id, ticket_status(ticket), ticket_standby_text(ticket), detail_str->str); } } else { out->list_item(out, NULL, "%s\t%s%s%s", ticket->id, ticket_status(ticket), ticket_standby_text(ticket), detail_str != NULL ? detail_str->str : ""); } if (detail_str != NULL) { g_string_free(detail_str, TRUE); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ticket", "pcmk__ticket_t *", "bool", "bool") static int ticket_xml(pcmk__output_t *out, va_list args) { pcmk__ticket_t *ticket = va_arg(args, pcmk__ticket_t *); bool raw G_GNUC_UNUSED = va_arg(args, int); bool details G_GNUC_UNUSED = va_arg(args, int); const char *standby = pcmk__flag_text(ticket->flags, pcmk__ticket_standby); xmlNodePtr node = NULL; GHashTableIter iter; const char *name = NULL; const char *value = NULL; node = pcmk__output_create_xml_node(out, PCMK_XE_TICKET, PCMK_XA_ID, ticket->id, PCMK_XA_STATUS, ticket_status(ticket), PCMK_XA_STANDBY, standby, NULL); if (ticket->last_granted > -1) { char *buf = pcmk__epoch2str(&ticket->last_granted, 0); crm_xml_add(node, PCMK_XA_LAST_GRANTED, buf); free(buf); } g_hash_table_iter_init(&iter, ticket->state); while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &value)) { /* PCMK_XA_LAST_GRANTED and "expires" are already added by the check * for ticket->last_granted above. */ if (pcmk__str_any_of(name, PCMK_XA_LAST_GRANTED, PCMK_XA_EXPIRES, NULL)) { continue; } crm_xml_add(node, name, value); } return pcmk_rc_ok; } PCMK__OUTPUT_ARGS("ticket-list", "GHashTable *", "bool", "bool", "bool") static int ticket_list(pcmk__output_t *out, va_list args) { GHashTable *tickets = va_arg(args, GHashTable *); bool print_spacer = va_arg(args, int); bool raw = va_arg(args, int); bool details = va_arg(args, int); GHashTableIter iter; gpointer value; if (g_hash_table_size(tickets) == 0) { return pcmk_rc_no_output; } PCMK__OUTPUT_SPACER_IF(out, print_spacer); /* Print section heading */ out->begin_list(out, NULL, NULL, "Tickets"); /* Print each ticket */ g_hash_table_iter_init(&iter, tickets); while (g_hash_table_iter_next(&iter, NULL, &value)) { pcmk__ticket_t *ticket = (pcmk__ticket_t *) value; out->message(out, "ticket", ticket, raw, details); } /* Close section */ out->end_list(out); return pcmk_rc_ok; } static pcmk__message_entry_t fmt_functions[] = { { "ban", "default", ban_text }, { "ban", "html", ban_html }, { "ban", "xml", ban_xml }, { "ban-list", "default", ban_list }, { "bundle", "default", pe__bundle_text }, { "bundle", "xml", pe__bundle_xml }, { "bundle", "html", pe__bundle_html }, { "clone", "default", pe__clone_default }, { "clone", "xml", pe__clone_xml }, { "cluster-counts", "default", cluster_counts_text }, { "cluster-counts", "html", cluster_counts_html }, { "cluster-counts", "xml", cluster_counts_xml }, { "cluster-dc", "default", cluster_dc_text }, { "cluster-dc", "html", cluster_dc_html }, { "cluster-dc", "xml", cluster_dc_xml }, { "cluster-options", "default", cluster_options_text }, { "cluster-options", "html", cluster_options_html }, { "cluster-options", "log", cluster_options_log }, { "cluster-options", "xml", cluster_options_xml }, { "cluster-summary", "default", cluster_summary }, { "cluster-summary", "html", cluster_summary_html }, { "cluster-stack", "default", cluster_stack_text }, { "cluster-stack", "html", cluster_stack_html }, { "cluster-stack", "xml", cluster_stack_xml }, { "cluster-times", "default", cluster_times_text }, { "cluster-times", "html", cluster_times_html }, { "cluster-times", "xml", cluster_times_xml }, { "failed-action", "default", failed_action_default }, { "failed-action", "xml", failed_action_xml }, { "failed-action-list", "default", failed_action_list }, { "group", "default", pe__group_default}, { "group", "xml", pe__group_xml }, { "maint-mode", "text", cluster_maint_mode_text }, { "node", "default", node_text }, { "node", "html", node_html }, { "node", "xml", node_xml }, { "node-and-op", "default", node_and_op }, { "node-and-op", "xml", node_and_op_xml }, { "node-capacity", "default", node_capacity }, { "node-capacity", "xml", node_capacity_xml }, { "node-history-list", "default", node_history_list }, { "node-list", "default", node_list_text }, { "node-list", "html", node_list_html }, { "node-list", "xml", node_list_xml }, { "node-weight", "default", node_weight }, { "node-weight", "xml", node_weight_xml }, { "node-attribute", "default", node_attribute_text }, { "node-attribute", "html", node_attribute_html }, { "node-attribute", "xml", node_attribute_xml }, { "node-attribute-list", "default", node_attribute_list }, { "node-summary", "default", node_summary }, { "op-history", "default", op_history_text }, { "op-history", "xml", op_history_xml }, { "primitive", "default", pe__resource_text }, { "primitive", "xml", pe__resource_xml }, { "primitive", "html", pe__resource_html }, { "promotion-score", "default", promotion_score }, { "promotion-score", "xml", promotion_score_xml }, { "resource-config", "default", resource_config }, { "resource-config", "text", resource_config_text }, { "resource-history", "default", resource_history_text }, { "resource-history", "xml", resource_history_xml }, { "resource-list", "default", resource_list }, { "resource-operation-list", "default", resource_operation_list }, { "resource-util", "default", resource_util }, { "resource-util", "xml", resource_util_xml }, { "ticket", "default", ticket_default }, { "ticket", "xml", ticket_xml }, { "ticket-list", "default", ticket_list }, { NULL, NULL, NULL } }; void pe__register_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } diff --git a/lib/pengine/status.c b/lib/pengine/status.c index 997e4a11c0..875bcf583e 100644 --- a/lib/pengine/status.c +++ b/lib/pengine/status.c @@ -1,529 +1,528 @@ /* * Copyright 2004-2024 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. */ #include #include #include #include #include #include #include #include /*! * \brief Create a new object to hold scheduler data * * \return New, initialized scheduler data on success, else NULL (and set errno) * \note Only pcmk_scheduler_t objects created with this function (as opposed * to statically declared or directly allocated) should be used with the * functions in this library, to allow for future extensions to the * data type. The caller is responsible for freeing the memory with * pe_free_working_set() when the instance is no longer needed. */ pcmk_scheduler_t * pe_new_working_set(void) { pcmk_scheduler_t *scheduler = calloc(1, sizeof(pcmk_scheduler_t)); if (scheduler == NULL) { return NULL; } scheduler->priv = calloc(1, sizeof(pcmk__scheduler_private_t)); if (scheduler->priv == NULL) { free(scheduler); return NULL; } set_working_set_defaults(scheduler); return scheduler; } /*! * \brief Free scheduler data * * \param[in,out] scheduler Scheduler data to free */ void pe_free_working_set(pcmk_scheduler_t *scheduler) { if (scheduler != NULL) { pe_reset_working_set(scheduler); free(scheduler->priv); free(scheduler); } } #define XPATH_DEPRECATED_RULES \ "//" PCMK_XE_OP_DEFAULTS "//" PCMK_XE_EXPRESSION \ "|//" PCMK_XE_OP "//" PCMK_XE_EXPRESSION /*! * \internal * \brief Log a warning for deprecated rule syntax in operations * * \param[in] scheduler Scheduler data */ static void check_for_deprecated_rules(pcmk_scheduler_t *scheduler) { // @COMPAT Drop this function when support for the syntax is dropped xmlNode *deprecated = get_xpath_object(XPATH_DEPRECATED_RULES, scheduler->input, LOG_NEVER); if (deprecated != NULL) { pcmk__warn_once(pcmk__wo_op_attr_expr, "Support for rules with node attribute expressions in " PCMK_XE_OP " or " PCMK_XE_OP_DEFAULTS " is deprecated " "and will be dropped in a future release"); } } /* * Unpack everything * At the end you'll have: * - A list of nodes * - A list of resources (each with any dependencies on other resources) * - A list of constraints between resources and nodes * - A list of constraints between start/stop actions * - A list of nodes that need to be stonith'd * - A list of nodes that need to be shutdown * - A list of the possible stop/start actions (without dependencies) */ gboolean cluster_status(pcmk_scheduler_t * scheduler) { const char *new_version = NULL; xmlNode *section = NULL; if ((scheduler == NULL) || (scheduler->input == NULL)) { return FALSE; } new_version = crm_element_value(scheduler->input, PCMK_XA_CRM_FEATURE_SET); if (pcmk__check_feature_set(new_version) != pcmk_rc_ok) { pcmk__config_err("Can't process CIB with feature set '%s' greater than our own '%s'", new_version, CRM_FEATURE_SET); return FALSE; } crm_trace("Beginning unpack"); if (scheduler->failed != NULL) { pcmk__xml_free(scheduler->failed); } scheduler->failed = pcmk__xe_create(NULL, "failed-ops"); if (scheduler->priv->now == NULL) { scheduler->priv->now = crm_time_new(NULL); } if (pcmk__xe_attr_is_true(scheduler->input, PCMK_XA_HAVE_QUORUM)) { pcmk__set_scheduler_flags(scheduler, pcmk__sched_quorate); } else { pcmk__clear_scheduler_flags(scheduler, pcmk__sched_quorate); } scheduler->op_defaults = get_xpath_object("//" PCMK_XE_OP_DEFAULTS, scheduler->input, LOG_NEVER); check_for_deprecated_rules(scheduler); scheduler->rsc_defaults = get_xpath_object("//" PCMK_XE_RSC_DEFAULTS, scheduler->input, LOG_NEVER); section = get_xpath_object("//" PCMK_XE_CRM_CONFIG, scheduler->input, LOG_TRACE); unpack_config(section, scheduler); if (!pcmk_any_flags_set(scheduler->flags, pcmk__sched_location_only|pcmk__sched_quorate) && (scheduler->no_quorum_policy != pcmk_no_quorum_ignore)) { pcmk__sched_warn(scheduler, "Fencing and resource management disabled " "due to lack of quorum"); } section = get_xpath_object("//" PCMK_XE_NODES, scheduler->input, LOG_TRACE); unpack_nodes(section, scheduler); section = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input, LOG_TRACE); if (!pcmk_is_set(scheduler->flags, pcmk__sched_location_only)) { unpack_remote_nodes(section, scheduler); } unpack_resources(section, scheduler); section = get_xpath_object("//" PCMK_XE_TAGS, scheduler->input, LOG_NEVER); unpack_tags(section, scheduler); if (!pcmk_is_set(scheduler->flags, pcmk__sched_location_only)) { section = get_xpath_object("//" PCMK_XE_STATUS, scheduler->input, LOG_TRACE); unpack_status(section, scheduler); } if (!pcmk_is_set(scheduler->flags, pcmk__sched_no_counts)) { for (GList *item = scheduler->priv->resources; item != NULL; item = item->next) { pcmk_resource_t *rsc = item->data; rsc->priv->fns->count(item->data); } crm_trace("Cluster resource count: %d (%d disabled, %d blocked)", scheduler->ninstances, scheduler->disabled_resources, scheduler->blocked_resources); } pcmk__set_scheduler_flags(scheduler, pcmk__sched_have_status); return TRUE; } /*! * \internal * \brief Free a list of pcmk_resource_t * * \param[in,out] resources List to free * * \note When the scheduler's resource list is freed, that includes the original * storage for the uname and id of any Pacemaker Remote nodes in the * scheduler's node list, so take care not to use those afterward. * \todo Refactor pcmk_node_t to strdup() the node name. */ static void pe_free_resources(GList *resources) { pcmk_resource_t *rsc = NULL; GList *iterator = resources; while (iterator != NULL) { rsc = (pcmk_resource_t *) iterator->data; iterator = iterator->next; rsc->priv->fns->free(rsc); } if (resources != NULL) { g_list_free(resources); } } static void pe_free_actions(GList *actions) { GList *iterator = actions; while (iterator != NULL) { pe_free_action(iterator->data); iterator = iterator->next; } if (actions != NULL) { g_list_free(actions); } } static void pe_free_nodes(GList *nodes) { for (GList *iterator = nodes; iterator != NULL; iterator = iterator->next) { pcmk_node_t *node = (pcmk_node_t *) iterator->data; // Shouldn't be possible, but to be safe ... if (node == NULL) { continue; } if (node->details == NULL) { free(node); continue; } /* This is called after pe_free_resources(), which means that we can't * use node->private->name for Pacemaker Remote nodes. */ crm_trace("Freeing node %s", (pcmk__is_pacemaker_remote_node(node)? "(guest or remote)" : pcmk__node_name(node))); if (node->priv->attrs != NULL) { g_hash_table_destroy(node->priv->attrs); } if (node->priv->utilization != NULL) { g_hash_table_destroy(node->priv->utilization); } if (node->priv->digest_cache != NULL) { g_hash_table_destroy(node->priv->digest_cache); } g_list_free(node->details->running_rsc); g_list_free(node->priv->assigned_resources); free(node->priv); free(node->details); free(node->assign); free(node); } if (nodes != NULL) { g_list_free(nodes); } } static void pe__free_ordering(GList *constraints) { GList *iterator = constraints; while (iterator != NULL) { pcmk__action_relation_t *order = iterator->data; iterator = iterator->next; free(order->task1); free(order->task2); free(order); } if (constraints != NULL) { g_list_free(constraints); } } static void pe__free_location(GList *constraints) { GList *iterator = constraints; while (iterator != NULL) { pcmk__location_t *cons = iterator->data; iterator = iterator->next; g_list_free_full(cons->nodes, free); free(cons->id); free(cons); } if (constraints != NULL) { g_list_free(constraints); } } /*! * \brief Reset scheduler data to defaults without freeing it or constraints * * \param[in,out] scheduler Scheduler data to reset * * \deprecated This function is deprecated as part of the API; * pe_reset_working_set() should be used instead. */ void cleanup_calculations(pcmk_scheduler_t *scheduler) { if (scheduler == NULL) { return; } pcmk__clear_scheduler_flags(scheduler, pcmk__sched_have_status); if (scheduler->priv->options != NULL) { g_hash_table_destroy(scheduler->priv->options); } if (scheduler->priv->singletons != NULL) { g_hash_table_destroy(scheduler->priv->singletons); } if (scheduler->priv->ticket_constraints != NULL) { g_hash_table_destroy(scheduler->priv->ticket_constraints); } if (scheduler->template_rsc_sets) { g_hash_table_destroy(scheduler->template_rsc_sets); } if (scheduler->tags) { g_hash_table_destroy(scheduler->tags); } crm_trace("deleting resources"); pe_free_resources(scheduler->priv->resources); crm_trace("deleting actions"); pe_free_actions(scheduler->priv->actions); crm_trace("deleting nodes"); pe_free_nodes(scheduler->nodes); pe__free_param_checks(scheduler); g_list_free(scheduler->stop_needed); pcmk__xml_free(scheduler->graph); crm_time_free(scheduler->priv->now); pcmk__xml_free(scheduler->input); pcmk__xml_free(scheduler->failed); set_working_set_defaults(scheduler); CRM_CHECK(scheduler->ordering_constraints == NULL,; ); - CRM_CHECK(scheduler->placement_constraints == NULL,; - ); + CRM_LOG_ASSERT(scheduler->priv->location_constraints == NULL); } /*! * \brief Reset scheduler data to default state without freeing it * * \param[in,out] scheduler Scheduler data to reset */ void pe_reset_working_set(pcmk_scheduler_t *scheduler) { if (scheduler == NULL) { return; } crm_trace("Deleting %d ordering constraints", g_list_length(scheduler->ordering_constraints)); pe__free_ordering(scheduler->ordering_constraints); scheduler->ordering_constraints = NULL; crm_trace("Deleting %d location constraints", - g_list_length(scheduler->placement_constraints)); - pe__free_location(scheduler->placement_constraints); - scheduler->placement_constraints = NULL; + g_list_length(scheduler->priv->location_constraints)); + pe__free_location(scheduler->priv->location_constraints); + scheduler->priv->location_constraints = NULL; crm_trace("Deleting %d colocation constraints", g_list_length(scheduler->colocation_constraints)); g_list_free_full(scheduler->colocation_constraints, free); scheduler->colocation_constraints = NULL; crm_trace("Deleting %d ticket constraints", g_list_length(scheduler->ticket_constraints)); g_list_free_full(scheduler->ticket_constraints, free); scheduler->ticket_constraints = NULL; cleanup_calculations(scheduler); } void set_working_set_defaults(pcmk_scheduler_t *scheduler) { // These members must be preserved pcmk__scheduler_private_t *priv = scheduler->priv; pcmk__output_t *out = priv->out; // Wipe the main structs (any other members must have previously been freed) memset(scheduler, 0, sizeof(pcmk_scheduler_t)); memset(priv, 0, sizeof(pcmk__scheduler_private_t)); // Restore the members to preserve scheduler->priv = priv; scheduler->priv->out = out; // Set defaults for everything else scheduler->order_id = 1; scheduler->action_id = 1; scheduler->no_quorum_policy = pcmk_no_quorum_stop; pcmk__set_scheduler_flags(scheduler, pcmk__sched_symmetric_cluster |pcmk__sched_stop_removed_resources |pcmk__sched_cancel_removed_actions); if (!strcmp(PCMK__CONCURRENT_FENCING_DEFAULT, PCMK_VALUE_TRUE)) { pcmk__set_scheduler_flags(scheduler, pcmk__sched_concurrent_fencing); } } pcmk_resource_t * pe_find_resource(GList *rsc_list, const char *id) { return pe_find_resource_with_flags(rsc_list, id, pcmk_rsc_match_history); } pcmk_resource_t * pe_find_resource_with_flags(GList *rsc_list, const char *id, enum pe_find flags) { GList *rIter = NULL; for (rIter = rsc_list; id && rIter; rIter = rIter->next) { pcmk_resource_t *parent = rIter->data; pcmk_resource_t *match = parent->priv->fns->find_rsc(parent, id, NULL, flags); if (match != NULL) { return match; } } crm_trace("No match for %s", id); return NULL; } /*! * \brief Find a node by name or ID in a list of nodes * * \param[in] nodes List of nodes (as pcmk_node_t*) * \param[in] id If not NULL, ID of node to find * \param[in] node_name If not NULL, name of node to find * * \return Node from \p nodes that matches \p id if any, * otherwise node from \p nodes that matches \p uname if any, * otherwise NULL */ pcmk_node_t * pe_find_node_any(const GList *nodes, const char *id, const char *uname) { pcmk_node_t *match = NULL; if (id != NULL) { match = pe_find_node_id(nodes, id); } if ((match == NULL) && (uname != NULL)) { match = pcmk__find_node_in_list(nodes, uname); } return match; } /*! * \brief Find a node by ID in a list of nodes * * \param[in] nodes List of nodes (as pcmk_node_t*) * \param[in] id ID of node to find * * \return Node from \p nodes that matches \p id if any, otherwise NULL */ pcmk_node_t * pe_find_node_id(const GList *nodes, const char *id) { for (const GList *iter = nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = (pcmk_node_t *) iter->data; /* @TODO Whether node IDs should be considered case-sensitive should * probably depend on the node type, so functionizing the comparison * would be worthwhile */ if (pcmk__str_eq(node->priv->id, id, pcmk__str_casei)) { return node; } } return NULL; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include /*! * \brief Find a node by name in a list of nodes * * \param[in] nodes List of nodes (as pcmk_node_t*) * \param[in] node_name Name of node to find * * \return Node from \p nodes that matches \p node_name if any, otherwise NULL */ pcmk_node_t * pe_find_node(const GList *nodes, const char *node_name) { return pcmk__find_node_in_list(nodes, node_name); } // LCOV_EXCL_STOP // End deprecated API