diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index 87439c880e..f3fb1c6ecd 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,362 +1,363 @@ /* * Copyright 2015-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 CRM_COMMON_INTERNAL__H #define CRM_COMMON_INTERNAL__H #include // pid_t, getpid() #include // bool #include // uint8_t, uint64_t #include // guint, GList, GHashTable #include // xmlNode #include // crm_strdup_printf() #include // do_crm_log_unlikely(), etc. #include // mainloop_io_t, struct ipc_client_callbacks #include #include #include #include #include #include #include +#include #include #include /* This says whether the current application is a Pacemaker daemon or not, * and is used to change default logging settings such as whether to log to * stderr, etc., as well as a few other details such as whether blackbox signal * handling is enabled. * * It is set when logging is initialized, and does not need to be set directly. */ extern bool pcmk__is_daemon; //! Node name of the local node extern char *pcmk__our_nodename; // Number of elements in a statically defined array #define PCMK__NELEM(a) ((int) (sizeof(a)/sizeof(a[0])) ) #if SUPPORT_CIBSECRETS /* internal CIB utilities (from cib_secrets.c) */ int pcmk__substitute_secrets(const char *rsc_id, GHashTable *params); #endif /* internal main loop utilities (from mainloop.c) */ int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); guint pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer); /* internal node-related XML utilities (from nodes.c) */ /*! * \internal * \brief Add local node name and ID to an XML node * * \param[in,out] request XML node to modify * \param[in] node The local node's name * \param[in] nodeid The local node's ID (can be 0) */ void pcmk__xe_add_node(xmlNode *xml, const char *node, int nodeid); /* internal name/value utilities (from nvpair.c) */ int pcmk__scan_nvpair(const char *input, char **name, char **value); char *pcmk__format_nvpair(const char *name, const char *value, const char *units); /*! * \internal * \brief Add a boolean attribute to an XML node. * * \param[in,out] node XML node to add attributes to * \param[in] name XML attribute to create * \param[in] value Value to give to the attribute */ void pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value); /*! * \internal * \brief Extract a boolean attribute's value from an XML element * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * * \return True if the given \p name is an attribute on \p node and has * the value \c PCMK_VALUE_TRUE, False in all other cases */ bool pcmk__xe_attr_is_true(const xmlNode *node, const char *name); /*! * \internal * \brief Extract a boolean attribute's value from an XML element, with * error checking * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * \param[out] value Destination for the value of the attribute * * \return EINVAL if \p name or \p value are NULL, ENODATA if \p node is * NULL or the attribute does not exist, pcmk_rc_unknown_format * if the attribute is not a boolean, and pcmk_rc_ok otherwise. * * \note \p value only has any meaning if the return value is pcmk_rc_ok. */ int pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *value); /* internal procfs utilities (from procfs.c) */ pid_t pcmk__procfs_pid_of(const char *name); unsigned int pcmk__procfs_num_cores(void); int pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size); bool pcmk__procfs_has_pids(void); /* internal XML schema functions (from xml.c) */ void crm_schema_init(void); void crm_schema_cleanup(void); void pcmk__load_schemas_from_dir(const char *dir); GList *pcmk__schema_files_later_than(const char *name); void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included); /* internal functions related to process IDs (from pid.c) */ /*! * \internal * \brief Check whether process exists (by PID and optionally executable path) * * \param[in] pid PID of process to check * \param[in] daemon If not NULL, path component to match with procfs entry * * \return Standard Pacemaker return code * \note Particular return codes of interest include pcmk_rc_ok for alive, * ESRCH for process is not alive (verified by kill and/or executable path * match), EACCES for caller unable or not allowed to check. A result of * "alive" is less reliable when \p daemon is not provided or procfs is * not available, since there is no guarantee that the PID has not been * recycled for another process. * \note This function cannot be used to verify \e authenticity of the process. */ int pcmk__pid_active(pid_t pid, const char *daemon); int pcmk__read_pidfile(const char *filename, pid_t *pid); int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid); int pcmk__lock_pidfile(const char *filename, const char *name); // bitwise arithmetic utilities /*! * \internal * \brief Set specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be set * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__set_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group | flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags %#.8llx (%s) for %s set by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } /*! * \internal * \brief Clear specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be cleared * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__clear_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group & ~flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags %#.8llx (%s) for %s cleared by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } /*! * \internal * \brief Get readable string for whether specified flags are set * * \param[in] flag_group Group of flags to check * \param[in] flags Which flags in \p flag_group should be checked * * \return "true" if all \p flags are set in \p flag_group, otherwise "false" */ static inline const char * pcmk__flag_text(uint64_t flag_group, uint64_t flags) { return pcmk__btoa(pcmk_all_flags_set(flag_group, flags)); } // miscellaneous utilities (from utils.c) void pcmk__daemonize(const char *name, const char *pidfile); void pcmk__panic(const char *origin); pid_t pcmk__locate_sbd(void); void pcmk__sleep_ms(unsigned int ms); extern int pcmk__score_red; extern int pcmk__score_green; extern int pcmk__score_yellow; /*! * \internal * \brief Resize a dynamically allocated memory block * * \param[in] ptr Memory block to resize (or NULL to allocate new memory) * \param[in] size New size of memory block in bytes (must be > 0) * * \return Pointer to resized memory block * * \note This asserts on error, so the result is guaranteed to be non-NULL * (which is the main advantage of this over directly using realloc()). */ static inline void * pcmk__realloc(void *ptr, size_t size) { void *new_ptr; // realloc(p, 0) can replace free(p) but this wrapper can't CRM_ASSERT(size > 0); new_ptr = realloc(ptr, size); if (new_ptr == NULL) { free(ptr); abort(); } return new_ptr; } static inline char * pcmk__getpid_s(void) { return crm_strdup_printf("%lu", (unsigned long) getpid()); } // More efficient than g_list_length(list) == 1 static inline bool pcmk__list_of_1(GList *list) { return list && (list->next == NULL); } // More efficient than g_list_length(list) > 1 static inline bool pcmk__list_of_multiple(GList *list) { return list && (list->next != NULL); } /* convenience functions for failure-related node attributes */ #define PCMK__FAIL_COUNT_PREFIX "fail-count" #define PCMK__LAST_FAILURE_PREFIX "last-failure" /*! * \internal * \brief Generate a failure-related node attribute name for a resource * * \param[in] prefix Start of attribute name * \param[in] rsc_id Resource name * \param[in] op Operation name * \param[in] interval_ms Operation interval * * \return Newly allocated string with attribute name * * \note Failure attributes are named like PREFIX-RSC#OP_INTERVAL (for example, * "fail-count-myrsc#monitor_30000"). The '#' is used because it is not * a valid character in a resource ID, to reliably distinguish where the * operation name begins. The '_' is used simply to be more comparable to * action labels like "myrsc_monitor_30000". */ static inline char * pcmk__fail_attr_name(const char *prefix, const char *rsc_id, const char *op, guint interval_ms) { CRM_CHECK(prefix && rsc_id && op, return NULL); return crm_strdup_printf("%s-%s#%s_%u", prefix, rsc_id, op, interval_ms); } static inline char * pcmk__failcount_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__FAIL_COUNT_PREFIX, rsc_id, op, interval_ms); } static inline char * pcmk__lastfailure_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__LAST_FAILURE_PREFIX, rsc_id, op, interval_ms); } // internal resource agent functions (from agents.c) int pcmk__effective_rc(int rc); #endif /* CRM_COMMON_INTERNAL__H */ diff --git a/include/crm/common/nvpair_internal.h b/include/crm/common/nvpair_internal.h new file mode 100644 index 0000000000..8b2f4441ac --- /dev/null +++ b/include/crm/common/nvpair_internal.h @@ -0,0 +1,38 @@ +/* + * 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_NVPAIR_INTERNAL__H +#define PCMK__CRM_COMMON_NVPAIR_INTERNAL__H + +#include // pcmk__str_eq(), etc. + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * \internal + * \brief Insert a meta-attribute into a hash table + * + * \param[in] obj Resource (pe_resource_t) or action (pe_action_t) to add to + * \param[in] name Meta-attribute name + * \param[in] value Value to add + */ +#define pcmk__insert_meta(obj, name, value) do { \ + if (!pcmk__str_eq((value), "#default", \ + pcmk__str_casei|pcmk__str_null_matches)) { \ + pcmk__insert_dup((obj)->meta, (name), (value)); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMMON_NVPAIR_INTERNAL__H diff --git a/lib/pacemaker/pcmk_sched_actions.c b/lib/pacemaker/pcmk_sched_actions.c index 7f90f09035..3469999a08 100644 --- a/lib/pacemaker/pcmk_sched_actions.c +++ b/lib/pacemaker/pcmk_sched_actions.c @@ -1,1933 +1,1933 @@ /* * 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 "libpacemaker_private.h" /*! * \internal * \brief Get the action flags relevant to ordering constraints * * \param[in,out] action Action to check * \param[in] node Node that *other* action in the ordering is on * (used only for clone resource actions) * * \return Action flags that should be used for orderings */ static uint32_t action_flags_for_ordering(pcmk_action_t *action, const pcmk_node_t *node) { bool runnable = false; uint32_t flags; // For non-resource actions, return the action flags if (action->rsc == NULL) { return action->flags; } /* For non-clone resources, or a clone action not assigned to a node, * return the flags as determined by the resource method without a node * specified. */ flags = action->rsc->cmds->action_flags(action, NULL); if ((node == NULL) || !pcmk__is_clone(action->rsc)) { return flags; } /* Otherwise (i.e., for clone resource actions on a specific node), first * remember whether the non-node-specific action is runnable. */ runnable = pcmk_is_set(flags, pcmk_action_runnable); // Then recheck the resource method with the node flags = action->rsc->cmds->action_flags(action, node); /* For clones in ordering constraints, the node-specific "runnable" doesn't * matter, just the non-node-specific setting (i.e., is the action runnable * anywhere). * * This applies only to runnable, and only for ordering constraints. This * function shouldn't be used for other types of constraints without * changes. Not very satisfying, but it's logical and appears to work well. */ if (runnable && !pcmk_is_set(flags, pcmk_action_runnable)) { pcmk__set_raw_action_flags(flags, action->rsc->id, pcmk_action_runnable); } return flags; } /*! * \internal * \brief Get action UUID that should be used with a resource ordering * * When an action is ordered relative to an action for a collective resource * (clone, group, or bundle), it actually needs to be ordered after all * instances of the collective have completed the relevant action (for example, * given "start CLONE then start RSC", RSC must wait until all instances of * CLONE have started). Given the UUID and resource of the first action in an * ordering, this returns the UUID of the action that should actually be used * for ordering (for example, "CLONE_started_0" instead of "CLONE_start_0"). * * \param[in] first_uuid UUID of first action in ordering * \param[in] first_rsc Resource of first action in ordering * * \return Newly allocated copy of UUID to use with ordering * \note It is the caller's responsibility to free the return value. */ static char * action_uuid_for_ordering(const char *first_uuid, const pcmk_resource_t *first_rsc) { guint interval_ms = 0; char *uuid = NULL; char *rid = NULL; char *first_task_str = NULL; enum action_tasks first_task = pcmk_action_unspecified; enum action_tasks remapped_task = pcmk_action_unspecified; // Only non-notify actions for collective resources need remapping if ((strstr(first_uuid, PCMK_ACTION_NOTIFY) != NULL) || (first_rsc->variant < pcmk_rsc_variant_group)) { goto done; } // Only non-recurring actions need remapping CRM_ASSERT(parse_op_key(first_uuid, &rid, &first_task_str, &interval_ms)); if (interval_ms > 0) { goto done; } first_task = pcmk_parse_action(first_task_str); switch (first_task) { case pcmk_action_stop: case pcmk_action_start: case pcmk_action_notify: case pcmk_action_promote: case pcmk_action_demote: remapped_task = first_task + 1; break; case pcmk_action_stopped: case pcmk_action_started: case pcmk_action_notified: case pcmk_action_promoted: case pcmk_action_demoted: remapped_task = first_task; break; case pcmk_action_monitor: case pcmk_action_shutdown: case pcmk_action_fence: break; default: crm_err("Unknown action '%s' in ordering", first_task_str); break; } if (remapped_task != pcmk_action_unspecified) { /* If a clone or bundle has notifications enabled, the ordering will be * relative to when notifications have been sent for the remapped task. */ if (pcmk_is_set(first_rsc->flags, pcmk_rsc_notify) && (pcmk__is_clone(first_rsc) || pcmk__is_bundled(first_rsc))) { uuid = pcmk__notify_key(rid, "confirmed-post", pcmk_action_text(remapped_task)); } else { uuid = pcmk__op_key(rid, pcmk_action_text(remapped_task), 0); } pcmk__rsc_trace(first_rsc, "Remapped action UUID %s to %s for ordering purposes", first_uuid, uuid); } done: if (uuid == NULL) { uuid = strdup(first_uuid); CRM_ASSERT(uuid != NULL); } free(first_task_str); free(rid); return uuid; } /*! * \internal * \brief Get actual action that should be used with an ordering * * When an action is ordered relative to an action for a collective resource * (clone, group, or bundle), it actually needs to be ordered after all * instances of the collective have completed the relevant action (for example, * given "start CLONE then start RSC", RSC must wait until all instances of * CLONE have started). Given the first action in an ordering, this returns the * the action that should actually be used for ordering (for example, the * started action instead of the start action). * * \param[in] action First action in an ordering * * \return Actual action that should be used for the ordering */ static pcmk_action_t * action_for_ordering(pcmk_action_t *action) { pcmk_action_t *result = action; pcmk_resource_t *rsc = action->rsc; if ((rsc != NULL) && (rsc->variant >= pcmk_rsc_variant_group) && (action->uuid != NULL)) { char *uuid = action_uuid_for_ordering(action->uuid, rsc); result = find_first_action(rsc->actions, uuid, NULL, NULL); if (result == NULL) { crm_warn("Not remapping %s to %s because %s does not have " "remapped action", action->uuid, uuid, rsc->id); result = action; } free(uuid); } return result; } /*! * \internal * \brief Wrapper for update_ordered_actions() method for readability * * \param[in,out] rsc Resource to call method for * \param[in,out] first 'First' action in an ordering * \param[in,out] then 'Then' action in an ordering * \param[in] node If not NULL, limit scope of ordering to this * node (only used when interleaving instances) * \param[in] flags Action flags for \p first for ordering purposes * \param[in] filter Action flags to limit scope of certain updates * (may include pcmk_action_optional to affect only * mandatory actions, and pe_action_runnable to * affect only runnable actions) * \param[in] type Group of enum pcmk__action_relation_flags to apply * \param[in,out] scheduler Scheduler data * * \return Group of enum pcmk__updated flags indicating what was updated */ static inline uint32_t update(pcmk_resource_t *rsc, pcmk_action_t *first, pcmk_action_t *then, const pcmk_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pcmk_scheduler_t *scheduler) { return rsc->cmds->update_ordered_actions(first, then, node, flags, filter, type, scheduler); } /*! * \internal * \brief Update flags for ordering's actions appropriately for ordering's flags * * \param[in,out] first First action in an ordering * \param[in,out] then Then action in an ordering * \param[in] first_flags Action flags for \p first for ordering purposes * \param[in] then_flags Action flags for \p then for ordering purposes * \param[in,out] order Action wrapper for \p first in ordering * \param[in,out] scheduler Scheduler data * * \return Group of enum pcmk__updated flags */ static uint32_t update_action_for_ordering_flags(pcmk_action_t *first, pcmk_action_t *then, uint32_t first_flags, uint32_t then_flags, pcmk__related_action_t *order, pcmk_scheduler_t *scheduler) { uint32_t changed = pcmk__updated_none; /* The node will only be used for clones. If interleaved, node will be NULL, * otherwise the ordering scope will be limited to the node. Normally, the * whole 'then' clone should restart if 'first' is restarted, so then->node * is needed. */ pcmk_node_t *node = then->node; if (pcmk_is_set(order->type, pcmk__ar_first_implies_same_node_then)) { /* For unfencing, only instances of 'then' on the same node as 'first' * (the unfencing operation) should restart, so reset node to * first->node, at which point this case is handled like a normal * pcmk__ar_first_implies_then. */ pcmk__clear_relation_flags(order->type, pcmk__ar_first_implies_same_node_then); pcmk__set_relation_flags(order->type, pcmk__ar_first_implies_then); node = first->node; pcmk__rsc_trace(then->rsc, "%s then %s: mapped " "pcmk__ar_first_implies_same_node_then to " "pcmk__ar_first_implies_then on %s", first->uuid, then->uuid, pcmk__node_name(node)); } if (pcmk_is_set(order->type, pcmk__ar_first_implies_then)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags & pcmk_action_optional, pcmk_action_optional, pcmk__ar_first_implies_then, scheduler); } else if (!pcmk_is_set(first_flags, pcmk_action_optional) && pcmk_is_set(then->flags, pcmk_action_optional)) { pcmk__clear_action_flags(then, pcmk_action_optional); pcmk__set_updated_flags(changed, first, pcmk__updated_then); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_first_implies_then", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_intermediate_stop) && (then->rsc != NULL)) { enum pe_action_flags restart = pcmk_action_optional |pcmk_action_runnable; changed |= update(then->rsc, first, then, node, first_flags, restart, pcmk__ar_intermediate_stop, scheduler); pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_intermediate_stop", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_then_implies_first)) { if (first->rsc != NULL) { changed |= update(first->rsc, first, then, node, first_flags, pcmk_action_optional, pcmk__ar_then_implies_first, scheduler); } else if (!pcmk_is_set(first_flags, pcmk_action_optional) && pcmk_is_set(first->flags, pcmk_action_runnable)) { pcmk__clear_action_flags(first, pcmk_action_runnable); pcmk__set_updated_flags(changed, first, pcmk__updated_first); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_then_implies_first", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_promoted_then_implies_first)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags & pcmk_action_optional, pcmk_action_optional, pcmk__ar_promoted_then_implies_first, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after " "pcmk__ar_promoted_then_implies_first", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_min_runnable)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_runnable, pcmk__ar_min_runnable, scheduler); } else if (pcmk_is_set(first_flags, pcmk_action_runnable)) { // We have another runnable instance of "first" then->runnable_before++; /* Mark "then" as runnable if it requires a certain number of * "before" instances to be runnable, and they now are. */ if ((then->runnable_before >= then->required_runnable_before) && !pcmk_is_set(then->flags, pcmk_action_runnable)) { pcmk__set_action_flags(then, pcmk_action_runnable); pcmk__set_updated_flags(changed, first, pcmk__updated_then); } } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_min_runnable", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_nested_remote_probe) && (then->rsc != NULL)) { if (!pcmk_is_set(first_flags, pcmk_action_runnable) && (first->rsc != NULL) && (first->rsc->running_on != NULL)) { pcmk__rsc_trace(then->rsc, "%s then %s: ignoring because first is stopping", first->uuid, then->uuid); order->type = (enum pe_ordering) pcmk__ar_none; } else { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_runnable, pcmk__ar_unrunnable_first_blocks, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_nested_remote_probe", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_unrunnable_first_blocks)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_runnable, pcmk__ar_unrunnable_first_blocks, scheduler); } else if (!pcmk_is_set(first_flags, pcmk_action_runnable) && pcmk_is_set(then->flags, pcmk_action_runnable)) { pcmk__clear_action_flags(then, pcmk_action_runnable); pcmk__set_updated_flags(changed, first, pcmk__updated_then); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_unrunnable_first_blocks", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_unmigratable_then_blocks)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_optional, pcmk__ar_unmigratable_then_blocks, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after " "pcmk__ar_unmigratable_then_blocks", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_first_else_then)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_optional, pcmk__ar_first_else_then, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_first_else_then", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_ordered)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_runnable, pcmk__ar_ordered, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_ordered", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(order->type, pcmk__ar_asymmetric)) { if (then->rsc != NULL) { changed |= update(then->rsc, first, then, node, first_flags, pcmk_action_runnable, pcmk__ar_asymmetric, scheduler); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after pcmk__ar_asymmetric", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } if (pcmk_is_set(first->flags, pcmk_action_runnable) && pcmk_is_set(order->type, pcmk__ar_first_implies_then_graphed) && !pcmk_is_set(first_flags, pcmk_action_optional)) { pcmk__rsc_trace(then->rsc, "%s will be in graph because %s is required", then->uuid, first->uuid); pcmk__set_action_flags(then, pcmk_action_always_in_graph); // Don't bother marking 'then' as changed just for this } if (pcmk_is_set(order->type, pcmk__ar_then_implies_first_graphed) && !pcmk_is_set(then_flags, pcmk_action_optional)) { pcmk__rsc_trace(then->rsc, "%s will be in graph because %s is required", first->uuid, then->uuid); pcmk__set_action_flags(first, pcmk_action_always_in_graph); // Don't bother marking 'first' as changed just for this } if (pcmk_any_flags_set(order->type, pcmk__ar_first_implies_then |pcmk__ar_then_implies_first |pcmk__ar_intermediate_stop) && (first->rsc != NULL) && !pcmk_is_set(first->rsc->flags, pcmk_rsc_managed) && pcmk_is_set(first->rsc->flags, pcmk_rsc_blocked) && !pcmk_is_set(first->flags, pcmk_action_runnable) && pcmk__str_eq(first->task, PCMK_ACTION_STOP, pcmk__str_none)) { if (pcmk_is_set(then->flags, pcmk_action_runnable)) { pcmk__clear_action_flags(then, pcmk_action_runnable); pcmk__set_updated_flags(changed, first, pcmk__updated_then); } pcmk__rsc_trace(then->rsc, "%s then %s: %s after checking whether first " "is blocked, unmanaged, unrunnable stop", first->uuid, then->uuid, (changed? "changed" : "unchanged")); } return changed; } // Convenience macros for logging action properties #define action_type_str(flags) \ (pcmk_is_set((flags), pcmk_action_pseudo)? "pseudo-action" : "action") #define action_optional_str(flags) \ (pcmk_is_set((flags), pcmk_action_optional)? "optional" : "required") #define action_runnable_str(flags) \ (pcmk_is_set((flags), pcmk_action_runnable)? "runnable" : "unrunnable") #define action_node_str(a) \ (((a)->node == NULL)? "no node" : (a)->node->details->uname) /*! * \internal * \brief Update an action's flags for all orderings where it is "then" * * \param[in,out] then Action to update * \param[in,out] scheduler Scheduler data */ void pcmk__update_action_for_orderings(pcmk_action_t *then, pcmk_scheduler_t *scheduler) { GList *lpc = NULL; uint32_t changed = pcmk__updated_none; int last_flags = then->flags; pcmk__rsc_trace(then->rsc, "Updating %s %s (%s %s) on %s", action_type_str(then->flags), then->uuid, action_optional_str(then->flags), action_runnable_str(then->flags), action_node_str(then)); if (pcmk_is_set(then->flags, pcmk_action_min_runnable)) { /* Initialize current known "runnable before" actions. As * update_action_for_ordering_flags() is called for each of then's * before actions, this number will increment as runnable 'first' * actions are encountered. */ then->runnable_before = 0; if (then->required_runnable_before == 0) { /* @COMPAT This ordering constraint uses the deprecated * PCMK_XA_REQUIRE_ALL=PCMK_VALUE_FALSE attribute. Treat it like * PCMK_META_CLONE_MIN=1. */ then->required_runnable_before = 1; } /* The pcmk__ar_min_runnable clause of * update_action_for_ordering_flags() (called below) * will reset runnable if appropriate. */ pcmk__clear_action_flags(then, pcmk_action_runnable); } for (lpc = then->actions_before; lpc != NULL; lpc = lpc->next) { pcmk__related_action_t *other = lpc->data; pcmk_action_t *first = other->action; pcmk_node_t *then_node = then->node; pcmk_node_t *first_node = first->node; if ((first->rsc != NULL) && (first->rsc->variant == pcmk_rsc_variant_group) && pcmk__str_eq(first->task, PCMK_ACTION_START, pcmk__str_none)) { first_node = first->rsc->fns->location(first->rsc, NULL, FALSE); if (first_node != NULL) { pcmk__rsc_trace(first->rsc, "Found %s for 'first' %s", pcmk__node_name(first_node), first->uuid); } } if ((then->rsc != NULL) && (then->rsc->variant == pcmk_rsc_variant_group) && pcmk__str_eq(then->task, PCMK_ACTION_START, pcmk__str_none)) { then_node = then->rsc->fns->location(then->rsc, NULL, FALSE); if (then_node != NULL) { pcmk__rsc_trace(then->rsc, "Found %s for 'then' %s", pcmk__node_name(then_node), then->uuid); } } // Disable constraint if it only applies when on same node, but isn't if (pcmk_is_set(other->type, pcmk__ar_if_on_same_node) && (first_node != NULL) && (then_node != NULL) && !pcmk__same_node(first_node, then_node)) { pcmk__rsc_trace(then->rsc, "Disabled ordering %s on %s then %s on %s: " "not same node", other->action->uuid, pcmk__node_name(first_node), then->uuid, pcmk__node_name(then_node)); other->type = (enum pe_ordering) pcmk__ar_none; continue; } pcmk__clear_updated_flags(changed, then, pcmk__updated_first); if ((first->rsc != NULL) && pcmk_is_set(other->type, pcmk__ar_then_cancels_first) && !pcmk_is_set(then->flags, pcmk_action_optional)) { /* 'then' is required, so we must abandon 'first' * (e.g. a required stop cancels any agent reload). */ pcmk__set_action_flags(other->action, pcmk_action_optional); if (!strcmp(first->task, PCMK_ACTION_RELOAD_AGENT)) { pcmk__clear_rsc_flags(first->rsc, pcmk_rsc_reload); } } if ((first->rsc != NULL) && (then->rsc != NULL) && (first->rsc != then->rsc) && !is_parent(then->rsc, first->rsc)) { first = action_for_ordering(first); } if (first != other->action) { pcmk__rsc_trace(then->rsc, "Ordering %s after %s instead of %s", then->uuid, first->uuid, other->action->uuid); } pcmk__rsc_trace(then->rsc, "%s (%#.6x) then %s (%#.6x): type=%#.6x node=%s", first->uuid, first->flags, then->uuid, then->flags, other->type, action_node_str(first)); if (first == other->action) { /* 'first' was not remapped (e.g. from 'start' to 'running'), which * could mean it is a non-resource action, a primitive resource * action, or already expanded. */ uint32_t first_flags, then_flags; first_flags = action_flags_for_ordering(first, then_node); then_flags = action_flags_for_ordering(then, first_node); changed |= update_action_for_ordering_flags(first, then, first_flags, then_flags, other, scheduler); /* 'first' was for a complex resource (clone, group, etc), * create a new dependency if necessary */ } else if (order_actions(first, then, other->type)) { /* This was the first time 'first' and 'then' were associated, * start again to get the new actions_before list */ pcmk__set_updated_flags(changed, then, pcmk__updated_then); pcmk__rsc_trace(then->rsc, "Disabled ordering %s then %s in favor of %s " "then %s", other->action->uuid, then->uuid, first->uuid, then->uuid); other->type = (enum pe_ordering) pcmk__ar_none; } if (pcmk_is_set(changed, pcmk__updated_first)) { crm_trace("Re-processing %s and its 'after' actions " "because it changed", first->uuid); for (GList *lpc2 = first->actions_after; lpc2 != NULL; lpc2 = lpc2->next) { pcmk__related_action_t *other = lpc2->data; pcmk__update_action_for_orderings(other->action, scheduler); } pcmk__update_action_for_orderings(first, scheduler); } } if (pcmk_is_set(then->flags, pcmk_action_min_runnable)) { if (last_flags == then->flags) { pcmk__clear_updated_flags(changed, then, pcmk__updated_then); } else { pcmk__set_updated_flags(changed, then, pcmk__updated_then); } } if (pcmk_is_set(changed, pcmk__updated_then)) { crm_trace("Re-processing %s and its 'after' actions because it changed", then->uuid); if (pcmk_is_set(last_flags, pcmk_action_runnable) && !pcmk_is_set(then->flags, pcmk_action_runnable)) { pcmk__block_colocation_dependents(then); } pcmk__update_action_for_orderings(then, scheduler); for (lpc = then->actions_after; lpc != NULL; lpc = lpc->next) { pcmk__related_action_t *other = lpc->data; pcmk__update_action_for_orderings(other->action, scheduler); } } } static inline bool is_primitive_action(const pcmk_action_t *action) { return (action != NULL) && (action->rsc != NULL) && (action->rsc->variant == pcmk_rsc_variant_primitive); } /*! * \internal * \brief Clear a single action flag and set reason text * * \param[in,out] action Action whose flag should be cleared * \param[in] flag Action flag that should be cleared * \param[in] reason Action that is the reason why flag is being cleared */ #define clear_action_flag_because(action, flag, reason) do { \ if (pcmk_is_set((action)->flags, (flag))) { \ pcmk__clear_action_flags(action, flag); \ if ((action)->rsc != (reason)->rsc) { \ char *reason_text = pe__action2reason((reason), (flag)); \ pe_action_set_reason((action), reason_text, false); \ free(reason_text); \ } \ } \ } while (0) /*! * \internal * \brief Update actions in an asymmetric ordering * * If the "first" action in an asymmetric ordering is unrunnable, make the * "second" action unrunnable as well, if appropriate. * * \param[in] first 'First' action in an asymmetric ordering * \param[in,out] then 'Then' action in an asymmetric ordering */ static void handle_asymmetric_ordering(const pcmk_action_t *first, pcmk_action_t *then) { /* Only resource actions after an unrunnable 'first' action need updates for * asymmetric ordering. */ if ((then->rsc == NULL) || pcmk_is_set(first->flags, pcmk_action_runnable)) { return; } // Certain optional 'then' actions are unaffected by unrunnable 'first' if (pcmk_is_set(then->flags, pcmk_action_optional)) { enum rsc_role_e then_rsc_role = then->rsc->fns->state(then->rsc, TRUE); if ((then_rsc_role == pcmk_role_stopped) && pcmk__str_eq(then->task, PCMK_ACTION_STOP, pcmk__str_none)) { /* If 'then' should stop after 'first' but is already stopped, the * ordering is irrelevant. */ return; } else if ((then_rsc_role >= pcmk_role_started) && pcmk__str_eq(then->task, PCMK_ACTION_START, pcmk__str_none) && pe__rsc_running_on_only(then->rsc, then->node)) { /* Similarly if 'then' should start after 'first' but is already * started on a single node. */ return; } } // 'First' can't run, so 'then' can't either clear_action_flag_because(then, pcmk_action_optional, first); clear_action_flag_because(then, pcmk_action_runnable, first); } /*! * \internal * \brief Set action bits appropriately when pe_restart_order is used * * \param[in,out] first 'First' action in an ordering with pe_restart_order * \param[in,out] then 'Then' action in an ordering with pe_restart_order * \param[in] filter What action flags to care about * * \note pe_restart_order is set for "stop resource before starting it" and * "stop later group member before stopping earlier group member" */ static void handle_restart_ordering(pcmk_action_t *first, pcmk_action_t *then, uint32_t filter) { const char *reason = NULL; CRM_ASSERT(is_primitive_action(first)); CRM_ASSERT(is_primitive_action(then)); // We need to update the action in two cases: // ... if 'then' is required if (pcmk_is_set(filter, pcmk_action_optional) && !pcmk_is_set(then->flags, pcmk_action_optional)) { reason = "restart"; } /* ... if 'then' is unrunnable action on same resource (if a resource * should restart but can't start, we still want to stop) */ if (pcmk_is_set(filter, pcmk_action_runnable) && !pcmk_is_set(then->flags, pcmk_action_runnable) && pcmk_is_set(then->rsc->flags, pcmk_rsc_managed) && (first->rsc == then->rsc)) { reason = "stop"; } if (reason == NULL) { return; } pcmk__rsc_trace(first->rsc, "Handling %s -> %s for %s", first->uuid, then->uuid, reason); // Make 'first' required if it is runnable if (pcmk_is_set(first->flags, pcmk_action_runnable)) { clear_action_flag_because(first, pcmk_action_optional, then); } // Make 'first' required if 'then' is required if (!pcmk_is_set(then->flags, pcmk_action_optional)) { clear_action_flag_because(first, pcmk_action_optional, then); } // Make 'first' unmigratable if 'then' is unmigratable if (!pcmk_is_set(then->flags, pcmk_action_migratable)) { clear_action_flag_because(first, pcmk_action_migratable, then); } // Make 'then' unrunnable if 'first' is required but unrunnable if (!pcmk_is_set(first->flags, pcmk_action_optional) && !pcmk_is_set(first->flags, pcmk_action_runnable)) { clear_action_flag_because(then, pcmk_action_runnable, first); } } /*! * \internal * \brief Update two actions according to an ordering between them * * Given information about an ordering of two actions, update the actions' flags * (and runnable_before members if appropriate) as appropriate for the ordering. * Effects may cascade to other orderings involving the actions as well. * * \param[in,out] first 'First' action in an ordering * \param[in,out] then 'Then' action in an ordering * \param[in] node If not NULL, limit scope of ordering to this node * (ignored) * \param[in] flags Action flags for \p first for ordering purposes * \param[in] filter Action flags to limit scope of certain updates (may * include pcmk_action_optional to affect only * mandatory actions, and pcmk_action_runnable to * affect only runnable actions) * \param[in] type Group of enum pcmk__action_relation_flags to apply * \param[in,out] scheduler Scheduler data * * \return Group of enum pcmk__updated flags indicating what was updated */ uint32_t pcmk__update_ordered_actions(pcmk_action_t *first, pcmk_action_t *then, const pcmk_node_t *node, uint32_t flags, uint32_t filter, uint32_t type, pcmk_scheduler_t *scheduler) { uint32_t changed = pcmk__updated_none; uint32_t then_flags = 0U; uint32_t first_flags = 0U; CRM_ASSERT((first != NULL) && (then != NULL) && (scheduler != NULL)); then_flags = then->flags; first_flags = first->flags; if (pcmk_is_set(type, pcmk__ar_asymmetric)) { handle_asymmetric_ordering(first, then); } if (pcmk_is_set(type, pcmk__ar_then_implies_first) && !pcmk_is_set(then_flags, pcmk_action_optional)) { // Then is required, and implies first should be, too if (pcmk_is_set(filter, pcmk_action_optional) && !pcmk_is_set(flags, pcmk_action_optional) && pcmk_is_set(first_flags, pcmk_action_optional)) { clear_action_flag_because(first, pcmk_action_optional, then); } if (pcmk_is_set(flags, pcmk_action_migratable) && !pcmk_is_set(then->flags, pcmk_action_migratable)) { clear_action_flag_because(first, pcmk_action_migratable, then); } } if (pcmk_is_set(type, pcmk__ar_promoted_then_implies_first) && (then->rsc != NULL) && (then->rsc->role == pcmk_role_promoted) && pcmk_is_set(filter, pcmk_action_optional) && !pcmk_is_set(then->flags, pcmk_action_optional)) { clear_action_flag_because(first, pcmk_action_optional, then); if (pcmk_is_set(first->flags, pcmk_action_migratable) && !pcmk_is_set(then->flags, pcmk_action_migratable)) { clear_action_flag_because(first, pcmk_action_migratable, then); } } if (pcmk_is_set(type, pcmk__ar_unmigratable_then_blocks) && pcmk_is_set(filter, pcmk_action_optional)) { if (!pcmk_all_flags_set(then->flags, pcmk_action_migratable |pcmk_action_runnable)) { clear_action_flag_because(first, pcmk_action_runnable, then); } if (!pcmk_is_set(then->flags, pcmk_action_optional)) { clear_action_flag_because(first, pcmk_action_optional, then); } } if (pcmk_is_set(type, pcmk__ar_first_else_then) && pcmk_is_set(filter, pcmk_action_optional) && !pcmk_is_set(first->flags, pcmk_action_runnable)) { clear_action_flag_because(then, pcmk_action_migratable, first); pcmk__clear_action_flags(then, pcmk_action_pseudo); } if (pcmk_is_set(type, pcmk__ar_unrunnable_first_blocks) && pcmk_is_set(filter, pcmk_action_runnable) && pcmk_is_set(then->flags, pcmk_action_runnable) && !pcmk_is_set(flags, pcmk_action_runnable)) { clear_action_flag_because(then, pcmk_action_runnable, first); clear_action_flag_because(then, pcmk_action_migratable, first); } if (pcmk_is_set(type, pcmk__ar_first_implies_then) && pcmk_is_set(filter, pcmk_action_optional) && pcmk_is_set(then->flags, pcmk_action_optional) && !pcmk_is_set(flags, pcmk_action_optional) && !pcmk_is_set(first->flags, pcmk_action_migratable)) { clear_action_flag_because(then, pcmk_action_optional, first); } if (pcmk_is_set(type, pcmk__ar_intermediate_stop)) { handle_restart_ordering(first, then, filter); } if (then_flags != then->flags) { pcmk__set_updated_flags(changed, first, pcmk__updated_then); pcmk__rsc_trace(then->rsc, "%s on %s: flags are now %#.6x (was %#.6x) " "because of 'first' %s (%#.6x)", then->uuid, pcmk__node_name(then->node), then->flags, then_flags, first->uuid, first->flags); if ((then->rsc != NULL) && (then->rsc->parent != NULL)) { // Required to handle "X_stop then X_start" for cloned groups pcmk__update_action_for_orderings(then, scheduler); } } if (first_flags != first->flags) { pcmk__set_updated_flags(changed, first, pcmk__updated_first); pcmk__rsc_trace(first->rsc, "%s on %s: flags are now %#.6x (was %#.6x) " "because of 'then' %s (%#.6x)", first->uuid, pcmk__node_name(first->node), first->flags, first_flags, then->uuid, then->flags); } return changed; } /*! * \internal * \brief Trace-log an action (optionally with its dependent actions) * * \param[in] pre_text If not NULL, prefix the log with this plus ": " * \param[in] action Action to log * \param[in] details If true, recursively log dependent actions */ void pcmk__log_action(const char *pre_text, const pcmk_action_t *action, bool details) { const char *node_uname = NULL; const char *node_uuid = NULL; const char *desc = NULL; CRM_CHECK(action != NULL, return); if (!pcmk_is_set(action->flags, pcmk_action_pseudo)) { if (action->node != NULL) { node_uname = action->node->details->uname; node_uuid = action->node->details->id; } else { node_uname = ""; } } switch (pcmk_parse_action(action->task)) { case pcmk_action_fence: case pcmk_action_shutdown: if (pcmk_is_set(action->flags, pcmk_action_pseudo)) { desc = "Pseudo "; } else if (pcmk_is_set(action->flags, pcmk_action_optional)) { desc = "Optional "; } else if (!pcmk_is_set(action->flags, pcmk_action_runnable)) { desc = "!!Non-Startable!! "; } else { desc = "(Provisional) "; } crm_trace("%s%s%sAction %d: %s%s%s%s%s%s", ((pre_text == NULL)? "" : pre_text), ((pre_text == NULL)? "" : ": "), desc, action->id, action->uuid, (node_uname? "\ton " : ""), (node_uname? node_uname : ""), (node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""), (node_uuid? ")" : "")); break; default: if (pcmk_is_set(action->flags, pcmk_action_optional)) { desc = "Optional "; } else if (pcmk_is_set(action->flags, pcmk_action_pseudo)) { desc = "Pseudo "; } else if (!pcmk_is_set(action->flags, pcmk_action_runnable)) { desc = "!!Non-Startable!! "; } else { desc = "(Provisional) "; } crm_trace("%s%s%sAction %d: %s %s%s%s%s%s%s", ((pre_text == NULL)? "" : pre_text), ((pre_text == NULL)? "" : ": "), desc, action->id, action->uuid, (action->rsc? action->rsc->id : ""), (node_uname? "\ton " : ""), (node_uname? node_uname : ""), (node_uuid? "\t\t(" : ""), (node_uuid? node_uuid : ""), (node_uuid? ")" : "")); break; } if (details) { const GList *iter = NULL; const pcmk__related_action_t *other = NULL; crm_trace("\t\t====== Preceding Actions"); for (iter = action->actions_before; iter != NULL; iter = iter->next) { other = (const pcmk__related_action_t *) iter->data; pcmk__log_action("\t\t", other->action, false); } crm_trace("\t\t====== Subsequent Actions"); for (iter = action->actions_after; iter != NULL; iter = iter->next) { other = (const pcmk__related_action_t *) iter->data; pcmk__log_action("\t\t", other->action, false); } crm_trace("\t\t====== End"); } else { crm_trace("\t\t(before=%d, after=%d)", g_list_length(action->actions_before), g_list_length(action->actions_after)); } } /*! * \internal * \brief Create a new shutdown action for a node * * \param[in,out] node Node being shut down * * \return Newly created shutdown action for \p node */ pcmk_action_t * pcmk__new_shutdown_action(pcmk_node_t *node) { char *shutdown_id = NULL; pcmk_action_t *shutdown_op = NULL; CRM_ASSERT(node != NULL); shutdown_id = crm_strdup_printf("%s-%s", PCMK_ACTION_DO_SHUTDOWN, node->details->uname); shutdown_op = custom_action(NULL, shutdown_id, PCMK_ACTION_DO_SHUTDOWN, node, FALSE, node->details->data_set); pcmk__order_stops_before_shutdown(node, shutdown_op); - add_hash_param(shutdown_op->meta, PCMK__META_OP_NO_WAIT, PCMK_VALUE_TRUE); + pcmk__insert_meta(shutdown_op, PCMK__META_OP_NO_WAIT, PCMK_VALUE_TRUE); return shutdown_op; } /*! * \internal * \brief Calculate and add an operation digest to XML * * Calculate an operation digest, which enables us to later determine when a * restart is needed due to the resource's parameters being changed, and add it * to given XML. * * \param[in] op Operation result from executor * \param[in,out] update XML to add digest to */ static void add_op_digest_to_xml(const lrmd_event_data_t *op, xmlNode *update) { char *digest = NULL; xmlNode *args_xml = NULL; if (op->params == NULL) { return; } args_xml = create_xml_node(NULL, PCMK_XE_PARAMETERS); g_hash_table_foreach(op->params, hash2field, args_xml); pcmk__filter_op_for_digest(args_xml); digest = calculate_operation_digest(args_xml, NULL); crm_xml_add(update, PCMK__XA_OP_DIGEST, digest); free_xml(args_xml); free(digest); } #define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" /*! * \internal * \brief Create XML for resource operation history update * * \param[in,out] parent Parent XML node to add to * \param[in,out] op Operation event data * \param[in] caller_version DC feature set * \param[in] target_rc Expected result of operation * \param[in] node Name of node on which operation was performed * \param[in] origin Arbitrary description of update source * * \return Newly created XML node for history update */ xmlNode * pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *op, const char *caller_version, int target_rc, const char *node, const char *origin) { char *key = NULL; char *magic = NULL; char *op_id = NULL; char *op_id_additional = NULL; char *local_user_data = NULL; const char *exit_reason = NULL; xmlNode *xml_op = NULL; const char *task = NULL; CRM_CHECK(op != NULL, return NULL); crm_trace("Creating history XML for %s-interval %s action for %s on %s " "(DC version: %s, origin: %s)", pcmk__readable_interval(op->interval_ms), op->op_type, op->rsc_id, ((node == NULL)? "no node" : node), caller_version, origin); task = op->op_type; /* Record a successful agent reload as a start, and a failed one as a * monitor, to make life easier for the scheduler when determining the * current state. * * @COMPAT We should check "reload" here only if the operation was for a * pre-OCF-1.1 resource agent, but we don't know that here, and we should * only ever get results for actions scheduled by us, so we can reasonably * assume any "reload" is actually a pre-1.1 agent reload. */ if (pcmk__str_any_of(task, PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT, NULL)) { if (op->op_status == PCMK_EXEC_DONE) { task = PCMK_ACTION_START; } else { task = PCMK_ACTION_MONITOR; } } key = pcmk__op_key(op->rsc_id, task, op->interval_ms); if (pcmk__str_eq(task, PCMK_ACTION_NOTIFY, pcmk__str_none)) { const char *n_type = crm_meta_value(op->params, "notify_type"); const char *n_task = crm_meta_value(op->params, "notify_operation"); CRM_LOG_ASSERT(n_type != NULL); CRM_LOG_ASSERT(n_task != NULL); op_id = pcmk__notify_key(op->rsc_id, n_type, n_task); if (op->op_status != PCMK_EXEC_PENDING) { /* Ignore notify errors. * * @TODO It might be better to keep the correct result here, and * ignore it in process_graph_event(). */ lrmd__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL); } /* Migration history is preserved separately, which usually matters for * multiple nodes and is important for future cluster transitions. */ } else if (pcmk__str_any_of(op->op_type, PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, NULL)) { op_id = strdup(key); } else if (did_rsc_op_fail(op, target_rc)) { op_id = pcmk__op_key(op->rsc_id, "last_failure", 0); if (op->interval_ms == 0) { /* Ensure 'last' gets updated, in case PCMK_META_RECORD_PENDING is * true */ op_id_additional = pcmk__op_key(op->rsc_id, "last", 0); } exit_reason = op->exit_reason; } else if (op->interval_ms > 0) { op_id = strdup(key); } else { op_id = pcmk__op_key(op->rsc_id, "last", 0); } again: xml_op = pcmk__xe_match(parent, PCMK__XE_LRM_RSC_OP, PCMK_XA_ID, op_id); if (xml_op == NULL) { xml_op = create_xml_node(parent, PCMK__XE_LRM_RSC_OP); } if (op->user_data == NULL) { crm_debug("Generating fake transition key for: " PCMK__OP_FMT " %d from %s", op->rsc_id, op->op_type, op->interval_ms, op->call_id, origin); local_user_data = pcmk__transition_key(-1, op->call_id, target_rc, FAKE_TE_ID); op->user_data = local_user_data; } if (magic == NULL) { magic = crm_strdup_printf("%d:%d;%s", op->op_status, op->rc, (const char *) op->user_data); } crm_xml_add(xml_op, PCMK_XA_ID, op_id); crm_xml_add(xml_op, PCMK__XA_OPERATION_KEY, key); crm_xml_add(xml_op, PCMK_XA_OPERATION, task); crm_xml_add(xml_op, PCMK_XA_CRM_DEBUG_ORIGIN, origin); crm_xml_add(xml_op, PCMK_XA_CRM_FEATURE_SET, caller_version); crm_xml_add(xml_op, PCMK__XA_TRANSITION_KEY, op->user_data); crm_xml_add(xml_op, PCMK__XA_TRANSITION_MAGIC, magic); crm_xml_add(xml_op, PCMK_XA_EXIT_REASON, pcmk__s(exit_reason, "")); crm_xml_add(xml_op, PCMK__META_ON_NODE, node); // For context during triage crm_xml_add_int(xml_op, PCMK__XA_CALL_ID, op->call_id); crm_xml_add_int(xml_op, PCMK__XA_RC_CODE, op->rc); crm_xml_add_int(xml_op, PCMK__XA_OP_STATUS, op->op_status); crm_xml_add_ms(xml_op, PCMK_META_INTERVAL, op->interval_ms); if (compare_version("2.1", caller_version) <= 0) { if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) { crm_trace("Timing data (" PCMK__OP_FMT "): last=%u change=%u exec=%u queue=%u", op->rsc_id, op->op_type, op->interval_ms, op->t_run, op->t_rcchange, op->exec_time, op->queue_time); if ((op->interval_ms != 0) && (op->t_rcchange != 0)) { // Recurring ops may have changed rc after initial run crm_xml_add_ll(xml_op, PCMK_XA_LAST_RC_CHANGE, (long long) op->t_rcchange); } else { crm_xml_add_ll(xml_op, PCMK_XA_LAST_RC_CHANGE, (long long) op->t_run); } crm_xml_add_int(xml_op, PCMK_XA_EXEC_TIME, op->exec_time); crm_xml_add_int(xml_op, PCMK_XA_QUEUE_TIME, op->queue_time); } } if (pcmk__str_any_of(op->op_type, PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, NULL)) { /* Record PCMK__META_MIGRATE_SOURCE and PCMK__META_MIGRATE_TARGET always * for migrate ops. */ const char *name = PCMK__META_MIGRATE_SOURCE; crm_xml_add(xml_op, name, crm_meta_value(op->params, name)); name = PCMK__META_MIGRATE_TARGET; crm_xml_add(xml_op, name, crm_meta_value(op->params, name)); } add_op_digest_to_xml(op, xml_op); if (op_id_additional) { free(op_id); op_id = op_id_additional; op_id_additional = NULL; goto again; } if (local_user_data) { free(local_user_data); op->user_data = NULL; } free(magic); free(op_id); free(key); return xml_op; } /*! * \internal * \brief Check whether an action shutdown-locks a resource to a node * * If the PCMK_OPT_SHUTDOWN_LOCK cluster property is set, resources will not be * recovered on a different node if cleanly stopped, and may start only on that * same node. This function checks whether that applies to a given action, so * that the transition graph can be marked appropriately. * * \param[in] action Action to check * * \return true if \p action locks its resource to the action's node, * otherwise false */ bool pcmk__action_locks_rsc_to_node(const pcmk_action_t *action) { // Only resource actions taking place on resource's lock node are locked if ((action == NULL) || (action->rsc == NULL) || !pcmk__same_node(action->node, action->rsc->lock_node)) { return false; } /* During shutdown, only stops are locked (otherwise, another action such as * a demote would cause the controller to clear the lock) */ if (action->node->details->shutdown && (action->task != NULL) && (strcmp(action->task, PCMK_ACTION_STOP) != 0)) { return false; } return true; } /* lowest to highest */ static gint sort_action_id(gconstpointer a, gconstpointer b) { const pcmk__related_action_t *action_wrapper2 = a; const pcmk__related_action_t *action_wrapper1 = b; if (a == NULL) { return 1; } if (b == NULL) { return -1; } if (action_wrapper1->action->id < action_wrapper2->action->id) { return 1; } if (action_wrapper1->action->id > action_wrapper2->action->id) { return -1; } return 0; } /*! * \internal * \brief Remove any duplicate action inputs, merging action flags * * \param[in,out] action Action whose inputs should be checked */ void pcmk__deduplicate_action_inputs(pcmk_action_t *action) { GList *item = NULL; GList *next = NULL; pcmk__related_action_t *last_input = NULL; action->actions_before = g_list_sort(action->actions_before, sort_action_id); for (item = action->actions_before; item != NULL; item = next) { pcmk__related_action_t *input = item->data; next = item->next; if ((last_input != NULL) && (input->action->id == last_input->action->id)) { crm_trace("Input %s (%d) duplicate skipped for action %s (%d)", input->action->uuid, input->action->id, action->uuid, action->id); /* For the purposes of scheduling, the ordering flags no longer * matter, but crm_simulate looks at certain ones when creating a * dot graph. Combining the flags is sufficient for that purpose. */ last_input->type |= input->type; if (input->state == pe_link_dumped) { last_input->state = pe_link_dumped; } free(item->data); action->actions_before = g_list_delete_link(action->actions_before, item); } else { last_input = input; input->state = pe_link_not_dumped; } } } /*! * \internal * \brief Output all scheduled actions * * \param[in,out] scheduler Scheduler data */ void pcmk__output_actions(pcmk_scheduler_t *scheduler) { pcmk__output_t *out = scheduler->priv; // Output node (non-resource) actions for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) { char *node_name = NULL; char *task = NULL; pcmk_action_t *action = (pcmk_action_t *) iter->data; if (action->rsc != NULL) { continue; // Resource actions will be output later } else if (pcmk_is_set(action->flags, pcmk_action_optional)) { continue; // This action was not scheduled } if (pcmk__str_eq(action->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none)) { task = strdup("Shutdown"); } else if (pcmk__str_eq(action->task, PCMK_ACTION_STONITH, pcmk__str_none)) { const char *op = g_hash_table_lookup(action->meta, PCMK__META_STONITH_ACTION); task = crm_strdup_printf("Fence (%s)", op); } else { continue; // Don't display other node action types } if (pe__is_guest_node(action->node)) { const pcmk_resource_t *remote = action->node->details->remote_rsc; node_name = crm_strdup_printf("%s (resource: %s)", pcmk__node_name(action->node), remote->container->id); } else if (action->node != NULL) { node_name = crm_strdup_printf("%s", pcmk__node_name(action->node)); } out->message(out, "node-action", task, node_name, action->reason); free(node_name); free(task); } // Output resource actions for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data; rsc->cmds->output_actions(rsc); } } /*! * \internal * \brief Get action name needed to compare digest for configuration changes * * \param[in] task Action name from history * \param[in] interval_ms Action interval (in milliseconds) * * \return Action name whose digest should be compared */ static const char * task_for_digest(const char *task, guint interval_ms) { /* Certain actions need to be compared against the parameters used to start * the resource. */ if ((interval_ms == 0) && pcmk__str_any_of(task, PCMK_ACTION_MONITOR, PCMK_ACTION_MIGRATE_FROM, PCMK_ACTION_PROMOTE, NULL)) { task = PCMK_ACTION_START; } return task; } /*! * \internal * \brief Check whether only sanitized parameters to an action changed * * When collecting CIB files for troubleshooting, crm_report will mask * sensitive resource parameters. If simulations were run using that, affected * resources would appear to need a restart, which would complicate * troubleshooting. To avoid that, we save a "secure digest" of non-sensitive * parameters. This function used that digest to check whether only masked * parameters are different. * * \param[in] xml_op Resource history entry with secure digest * \param[in] digest_data Operation digest information being compared * \param[in] scheduler Scheduler data * * \return true if only sanitized parameters changed, otherwise false */ static bool only_sanitized_changed(const xmlNode *xml_op, const pcmk__op_digest_t *digest_data, const pcmk_scheduler_t *scheduler) { const char *digest_secure = NULL; if (!pcmk_is_set(scheduler->flags, pcmk_sched_sanitized)) { // The scheduler is not being run as a simulation return false; } digest_secure = crm_element_value(xml_op, PCMK__XA_OP_SECURE_DIGEST); return (digest_data->rc != pcmk__digest_match) && (digest_secure != NULL) && (digest_data->digest_secure_calc != NULL) && (strcmp(digest_data->digest_secure_calc, digest_secure) == 0); } /*! * \internal * \brief Force a restart due to a configuration change * * \param[in,out] rsc Resource that action is for * \param[in] task Name of action whose configuration changed * \param[in] interval_ms Action interval (in milliseconds) * \param[in,out] node Node where resource should be restarted */ static void force_restart(pcmk_resource_t *rsc, const char *task, guint interval_ms, pcmk_node_t *node) { char *key = pcmk__op_key(rsc->id, task, interval_ms); pcmk_action_t *required = custom_action(rsc, key, task, NULL, FALSE, rsc->cluster); pe_action_set_reason(required, "resource definition change", true); trigger_unfencing(rsc, node, "Device parameters changed", NULL, rsc->cluster); } /*! * \internal * \brief Schedule a reload of a resource on a node * * \param[in,out] data Resource to reload * \param[in] user_data Where resource should be reloaded */ static void schedule_reload(gpointer data, gpointer user_data) { pcmk_resource_t *rsc = data; const pcmk_node_t *node = user_data; pcmk_action_t *reload = NULL; // For collective resources, just call recursively for children if (rsc->variant > pcmk_rsc_variant_primitive) { g_list_foreach(rsc->children, schedule_reload, user_data); return; } // Skip the reload in certain situations if ((node == NULL) || !pcmk_is_set(rsc->flags, pcmk_rsc_managed) || pcmk_is_set(rsc->flags, pcmk_rsc_failed)) { pcmk__rsc_trace(rsc, "Skip reload of %s:%s%s %s", rsc->id, pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : " unmanaged", pcmk_is_set(rsc->flags, pcmk_rsc_failed)? " failed" : "", (node == NULL)? "inactive" : node->details->uname); return; } /* If a resource's configuration changed while a start was pending, * force a full restart instead of a reload. */ if (pcmk_is_set(rsc->flags, pcmk_rsc_start_pending)) { pcmk__rsc_trace(rsc, "%s: preventing agent reload because start pending", rsc->id); custom_action(rsc, stop_key(rsc), PCMK_ACTION_STOP, node, FALSE, rsc->cluster); return; } // Schedule the reload pcmk__set_rsc_flags(rsc, pcmk_rsc_reload); reload = custom_action(rsc, reload_key(rsc), PCMK_ACTION_RELOAD_AGENT, node, FALSE, rsc->cluster); pe_action_set_reason(reload, "resource definition change", FALSE); // Set orderings so that a required stop or demote cancels the reload pcmk__new_ordering(NULL, NULL, reload, rsc, stop_key(rsc), NULL, pcmk__ar_ordered|pcmk__ar_then_cancels_first, rsc->cluster); pcmk__new_ordering(NULL, NULL, reload, rsc, demote_key(rsc), NULL, pcmk__ar_ordered|pcmk__ar_then_cancels_first, rsc->cluster); } /*! * \internal * \brief Handle any configuration change for an action * * Given an action from resource history, if the resource's configuration * changed since the action was done, schedule any actions needed (restart, * reload, unfencing, rescheduling recurring actions, etc.). * * \param[in,out] rsc Resource that action is for * \param[in,out] node Node that action was on * \param[in] xml_op Action XML from resource history * * \return true if action configuration changed, otherwise false */ bool pcmk__check_action_config(pcmk_resource_t *rsc, pcmk_node_t *node, const xmlNode *xml_op) { guint interval_ms = 0; const char *task = NULL; const pcmk__op_digest_t *digest_data = NULL; CRM_CHECK((rsc != NULL) && (node != NULL) && (xml_op != NULL), return false); task = crm_element_value(xml_op, PCMK_XA_OPERATION); CRM_CHECK(task != NULL, return false); crm_element_value_ms(xml_op, PCMK_META_INTERVAL, &interval_ms); // If this is a recurring action, check whether it has been orphaned if (interval_ms > 0) { if (pcmk__find_action_config(rsc, task, interval_ms, false) != NULL) { pcmk__rsc_trace(rsc, "%s-interval %s for %s on %s is in configuration", pcmk__readable_interval(interval_ms), task, rsc->id, pcmk__node_name(node)); } else if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_cancel_removed_actions)) { pcmk__schedule_cancel(rsc, crm_element_value(xml_op, PCMK__XA_CALL_ID), task, interval_ms, node, "orphan"); return true; } else { pcmk__rsc_debug(rsc, "%s-interval %s for %s on %s is orphaned", pcmk__readable_interval(interval_ms), task, rsc->id, pcmk__node_name(node)); return true; } } crm_trace("Checking %s-interval %s for %s on %s for configuration changes", pcmk__readable_interval(interval_ms), task, rsc->id, pcmk__node_name(node)); task = task_for_digest(task, interval_ms); digest_data = rsc_action_digest_cmp(rsc, xml_op, node, rsc->cluster); if (only_sanitized_changed(xml_op, digest_data, rsc->cluster)) { if (!pcmk__is_daemon && (rsc->cluster->priv != NULL)) { pcmk__output_t *out = rsc->cluster->priv; out->info(out, "Only 'private' parameters to %s-interval %s for %s " "on %s changed: %s", pcmk__readable_interval(interval_ms), task, rsc->id, pcmk__node_name(node), crm_element_value(xml_op, PCMK__XA_TRANSITION_MAGIC)); } return false; } switch (digest_data->rc) { case pcmk__digest_restart: crm_log_xml_debug(digest_data->params_restart, "params:restart"); force_restart(rsc, task, interval_ms, node); return true; case pcmk__digest_unknown: case pcmk__digest_mismatch: // Changes that can potentially be handled by an agent reload if (interval_ms > 0) { /* Recurring actions aren't reloaded per se, they are just * re-scheduled so the next run uses the new parameters. * The old instance will be cancelled automatically. */ crm_log_xml_debug(digest_data->params_all, "params:reschedule"); pcmk__reschedule_recurring(rsc, task, interval_ms, node); } else if (crm_element_value(xml_op, PCMK__XA_OP_RESTART_DIGEST) != NULL) { // Agent supports reload, so use it trigger_unfencing(rsc, node, "Device parameters changed (reload)", NULL, rsc->cluster); crm_log_xml_debug(digest_data->params_all, "params:reload"); schedule_reload((gpointer) rsc, (gpointer) node); } else { pcmk__rsc_trace(rsc, "Restarting %s " "because agent doesn't support reload", rsc->id); crm_log_xml_debug(digest_data->params_restart, "params:restart"); force_restart(rsc, task, interval_ms, node); } return true; default: break; } return false; } /*! * \internal * \brief Create a list of resource's action history entries, sorted by call ID * * \param[in] rsc_entry Resource's \c PCMK__XE_LRM_RSC_OP status XML * \param[out] start_index Where to store index of start-like action, if any * \param[out] stop_index Where to store index of stop action, if any */ static GList * rsc_history_as_list(const xmlNode *rsc_entry, int *start_index, int *stop_index) { GList *ops = NULL; for (xmlNode *rsc_op = first_named_child(rsc_entry, PCMK__XE_LRM_RSC_OP); rsc_op != NULL; rsc_op = crm_next_same_xml(rsc_op)) { ops = g_list_prepend(ops, rsc_op); } ops = g_list_sort(ops, sort_op_by_callid); calculate_active_ops(ops, start_index, stop_index); return ops; } /*! * \internal * \brief Process a resource's action history from the CIB status * * Given a resource's action history, if the resource's configuration * changed since the actions were done, schedule any actions needed (restart, * reload, unfencing, rescheduling recurring actions, clean-up, etc.). * (This also cancels recurring actions for maintenance mode, which is not * entirely related but convenient to do here.) * * \param[in] rsc_entry Resource's \c PCMK__XE_LRM_RSC_OP status XML * \param[in,out] rsc Resource whose history is being processed * \param[in,out] node Node whose history is being processed */ static void process_rsc_history(const xmlNode *rsc_entry, pcmk_resource_t *rsc, pcmk_node_t *node) { int offset = -1; int stop_index = 0; int start_index = 0; GList *sorted_op_list = NULL; if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { if (pcmk__is_anonymous_clone(pe__const_top_resource(rsc, false))) { pcmk__rsc_trace(rsc, "Skipping configuration check " "for orphaned clone instance %s", rsc->id); } else { pcmk__rsc_trace(rsc, "Skipping configuration check and scheduling " "clean-up for orphaned resource %s", rsc->id); pcmk__schedule_cleanup(rsc, node, false); } return; } if (pe_find_node_id(rsc->running_on, node->details->id) == NULL) { if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, false)) { pcmk__schedule_cleanup(rsc, node, false); } pcmk__rsc_trace(rsc, "Skipping configuration check for %s " "because no longer active on %s", rsc->id, pcmk__node_name(node)); return; } pcmk__rsc_trace(rsc, "Checking for configuration changes for %s on %s", rsc->id, pcmk__node_name(node)); if (pcmk__rsc_agent_changed(rsc, node, rsc_entry, true)) { pcmk__schedule_cleanup(rsc, node, false); } sorted_op_list = rsc_history_as_list(rsc_entry, &start_index, &stop_index); if (start_index < stop_index) { return; // Resource is stopped } for (GList *iter = sorted_op_list; iter != NULL; iter = iter->next) { xmlNode *rsc_op = (xmlNode *) iter->data; const char *task = NULL; guint interval_ms = 0; if (++offset < start_index) { // Skip actions that happened before a start continue; } task = crm_element_value(rsc_op, PCMK_XA_OPERATION); crm_element_value_ms(rsc_op, PCMK_META_INTERVAL, &interval_ms); if ((interval_ms > 0) && (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance) || node->details->maintenance)) { // Maintenance mode cancels recurring operations pcmk__schedule_cancel(rsc, crm_element_value(rsc_op, PCMK__XA_CALL_ID), task, interval_ms, node, "maintenance mode"); } else if ((interval_ms > 0) || pcmk__strcase_any_of(task, PCMK_ACTION_MONITOR, PCMK_ACTION_START, PCMK_ACTION_PROMOTE, PCMK_ACTION_MIGRATE_FROM, NULL)) { /* If a resource operation failed, and the operation's definition * has changed, clear any fail count so they can be retried fresh. */ if (pe__bundle_needs_remote_name(rsc)) { /* We haven't assigned resources to nodes yet, so if the * REMOTE_CONTAINER_HACK is used, we may calculate the digest * based on the literal "#uname" value rather than the properly * substituted value. That would mistakenly make the action * definition appear to have been changed. Defer the check until * later in this case. */ pe__add_param_check(rsc_op, rsc, node, pcmk__check_active, rsc->cluster); } else if (pcmk__check_action_config(rsc, node, rsc_op) && (pe_get_failcount(node, rsc, NULL, pcmk__fc_effective, NULL) != 0)) { pe__clear_failcount(rsc, node, "action definition changed", rsc->cluster); } } } g_list_free(sorted_op_list); } /*! * \internal * \brief Process a node's action history from the CIB status * * Given a node's resource history, if the resource's configuration changed * since the actions were done, schedule any actions needed (restart, * reload, unfencing, rescheduling recurring actions, clean-up, etc.). * (This also cancels recurring actions for maintenance mode, which is not * entirely related but convenient to do here.) * * \param[in,out] node Node whose history is being processed * \param[in] lrm_rscs Node's \c PCMK__XE_LRM_RESOURCES from CIB status XML */ static void process_node_history(pcmk_node_t *node, const xmlNode *lrm_rscs) { crm_trace("Processing node history for %s", pcmk__node_name(node)); for (const xmlNode *rsc_entry = first_named_child(lrm_rscs, PCMK__XE_LRM_RESOURCE); rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) { if (rsc_entry->children != NULL) { GList *result = pcmk__rscs_matching_id(pcmk__xe_id(rsc_entry), node->details->data_set); for (GList *iter = result; iter != NULL; iter = iter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data; if (rsc->variant == pcmk_rsc_variant_primitive) { process_rsc_history(rsc_entry, rsc, node); } } g_list_free(result); } } } // XPath to find a node's resource history #define XPATH_NODE_HISTORY "/" PCMK_XE_CIB "/" PCMK_XE_STATUS \ "/" PCMK__XE_NODE_STATE \ "[@" PCMK_XA_UNAME "='%s']" \ "/" PCMK__XE_LRM "/" PCMK__XE_LRM_RESOURCES /*! * \internal * \brief Process any resource configuration changes in the CIB status * * Go through all nodes' resource history, and if a resource's configuration * changed since its actions were done, schedule any actions needed (restart, * reload, unfencing, rescheduling recurring actions, clean-up, etc.). * (This also cancels recurring actions for maintenance mode, which is not * entirely related but convenient to do here.) * * \param[in,out] scheduler Scheduler data */ void pcmk__handle_rsc_config_changes(pcmk_scheduler_t *scheduler) { crm_trace("Check resource and action configuration for changes"); /* Rather than iterate through the status section, iterate through the nodes * and search for the appropriate status subsection for each. This skips * orphaned nodes and lets us eliminate some cases before searching the XML. */ for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = (pcmk_node_t *) iter->data; /* Don't bother checking actions for a node that can't run actions ... * unless it's in maintenance mode, in which case we still need to * cancel any existing recurring monitors. */ if (node->details->maintenance || pcmk__node_available(node, false, false)) { char *xpath = NULL; xmlNode *history = NULL; xpath = crm_strdup_printf(XPATH_NODE_HISTORY, node->details->uname); history = get_xpath_object(xpath, scheduler->input, LOG_NEVER); free(xpath); process_node_history(node, history); } } } diff --git a/lib/pacemaker/pcmk_sched_migration.c b/lib/pacemaker/pcmk_sched_migration.c index d3c90413a5..3fff899e2d 100644 --- a/lib/pacemaker/pcmk_sched_migration.c +++ b/lib/pacemaker/pcmk_sched_migration.c @@ -1,406 +1,406 @@ /* * 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 "libpacemaker_private.h" /*! * \internal * \brief Add migration source and target meta-attributes to an action * * \param[in,out] action Action to add meta-attributes to * \param[in] source Node to add as migration source * \param[in] target Node to add as migration target */ static void add_migration_meta(pcmk_action_t *action, const pcmk_node_t *source, const pcmk_node_t *target) { - add_hash_param(action->meta, PCMK__META_MIGRATE_SOURCE, - source->details->uname); + pcmk__insert_meta(action, PCMK__META_MIGRATE_SOURCE, + source->details->uname); - add_hash_param(action->meta, PCMK__META_MIGRATE_TARGET, - target->details->uname); + pcmk__insert_meta(action, PCMK__META_MIGRATE_TARGET, + target->details->uname); } /*! * \internal * \brief Create internal migration actions for a migrateable resource * * \param[in,out] rsc Resource to create migration actions for * \param[in] current Node that resource is originally active on */ void pcmk__create_migration_actions(pcmk_resource_t *rsc, const pcmk_node_t *current) { pcmk_action_t *migrate_to = NULL; pcmk_action_t *migrate_from = NULL; pcmk_action_t *start = NULL; pcmk_action_t *stop = NULL; pcmk__rsc_trace(rsc, "Creating actions to %smigrate %s from %s to %s", ((rsc->partial_migration_target == NULL)? "" : "partially "), rsc->id, pcmk__node_name(current), pcmk__node_name(rsc->allocated_to)); start = start_action(rsc, rsc->allocated_to, TRUE); stop = stop_action(rsc, current, TRUE); if (rsc->partial_migration_target == NULL) { migrate_to = custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_TO, 0), PCMK_ACTION_MIGRATE_TO, current, TRUE, rsc->cluster); } migrate_from = custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_FROM, 0), PCMK_ACTION_MIGRATE_FROM, rsc->allocated_to, TRUE, rsc->cluster); pcmk__set_action_flags(start, pcmk_action_migratable); pcmk__set_action_flags(stop, pcmk_action_migratable); // This is easier than trying to delete it from the graph pcmk__set_action_flags(start, pcmk_action_pseudo); if (rsc->partial_migration_target == NULL) { pcmk__set_action_flags(migrate_from, pcmk_action_migratable); pcmk__set_action_flags(migrate_to, pcmk_action_migratable); migrate_to->needs = start->needs; // Probe -> migrate_to -> migrate_from pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0), NULL, rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, pcmk__ar_ordered, rsc->cluster); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, pcmk__ar_ordered|pcmk__ar_unmigratable_then_blocks, rsc->cluster); } else { pcmk__set_action_flags(migrate_from, pcmk_action_migratable); migrate_from->needs = start->needs; // Probe -> migrate_from (migrate_to already completed) pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0), NULL, rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, pcmk__ar_ordered, rsc->cluster); } // migrate_from before stop or start pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, rsc, pcmk__op_key(rsc->id, PCMK_ACTION_STOP, 0), NULL, pcmk__ar_ordered|pcmk__ar_unmigratable_then_blocks, rsc->cluster); pcmk__new_ordering(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, rsc, pcmk__op_key(rsc->id, PCMK_ACTION_START, 0), NULL, pcmk__ar_ordered |pcmk__ar_unmigratable_then_blocks |pcmk__ar_first_else_then, rsc->cluster); if (migrate_to != NULL) { add_migration_meta(migrate_to, current, rsc->allocated_to); if (!rsc->is_remote_node) { /* migrate_to takes place on the source node, but can affect the * target node depending on how the agent is written. Because of * this, pending migrate_to actions must be recorded in the CIB, * in case the source node loses membership while the migrate_to * action is still in flight. * * However we know Pacemaker Remote connection resources don't * require this, so we skip this for them. (Although it wouldn't * hurt, and now that PCMK_META_RECORD_PENDING defaults to true, * skipping it matters even less.) */ - add_hash_param(migrate_to->meta, PCMK_META_RECORD_PENDING, - PCMK_VALUE_TRUE); + pcmk__insert_meta(migrate_to, + PCMK_META_RECORD_PENDING, PCMK_VALUE_TRUE); } } add_migration_meta(migrate_from, current, rsc->allocated_to); } /*! * \internal * \brief Abort a dangling migration by scheduling a stop (and possibly cleanup) * * \param[in] data Source node of dangling migration * \param[in,out] user_data Resource involved in dangling migration */ void pcmk__abort_dangling_migration(void *data, void *user_data) { const pcmk_node_t *dangling_source = (const pcmk_node_t *) data; pcmk_resource_t *rsc = (pcmk_resource_t *) user_data; pcmk_action_t *stop = NULL; bool cleanup = pcmk_is_set(rsc->cluster->flags, pcmk_sched_remove_after_stop); pcmk__rsc_trace(rsc, "Scheduling stop%s for %s on %s due to dangling migration", (cleanup? " and cleanup" : ""), rsc->id, pcmk__node_name(dangling_source)); stop = stop_action(rsc, dangling_source, FALSE); pcmk__set_action_flags(stop, pcmk_action_migration_abort); if (cleanup) { pcmk__schedule_cleanup(rsc, dangling_source, false); } } /*! * \internal * \brief Check whether a resource can migrate * * \param[in] rsc Resource to check * \param[in] node Resource's current node * * \return true if \p rsc can migrate, otherwise false */ bool pcmk__rsc_can_migrate(const pcmk_resource_t *rsc, const pcmk_node_t *current) { CRM_CHECK(rsc != NULL, return false); if (!pcmk_is_set(rsc->flags, pcmk_rsc_migratable)) { pcmk__rsc_trace(rsc, "%s cannot migrate because " "the configuration does not allow it", rsc->id); return false; } if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__rsc_trace(rsc, "%s cannot migrate because it is not managed", rsc->id); return false; } if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) { pcmk__rsc_trace(rsc, "%s cannot migrate because it is failed", rsc->id); return false; } if (pcmk_is_set(rsc->flags, pcmk_rsc_start_pending)) { pcmk__rsc_trace(rsc, "%s cannot migrate because it has a start pending", rsc->id); return false; } if ((current == NULL) || current->details->unclean) { pcmk__rsc_trace(rsc, "%s cannot migrate because " "its current node (%s) is unclean", rsc->id, pcmk__node_name(current)); return false; } if ((rsc->allocated_to == NULL) || rsc->allocated_to->details->unclean) { pcmk__rsc_trace(rsc, "%s cannot migrate because " "its next node (%s) is unclean", rsc->id, pcmk__node_name(rsc->allocated_to)); return false; } return true; } /*! * \internal * \brief Get an action name from an action or operation key * * \param[in] action If not NULL, get action name from here * \param[in] key If not NULL, get action name from here * * \return Newly allocated copy of action name (or NULL if none available) */ static char * task_from_action_or_key(const pcmk_action_t *action, const char *key) { char *res = NULL; if (action != NULL) { res = strdup(action->task); CRM_ASSERT(res != NULL); } else if (key != NULL) { parse_op_key(key, NULL, &res, NULL); } return res; } /*! * \internal * \brief Order migration actions equivalent to a given ordering * * Orderings involving start, stop, demote, and promote actions must be honored * during a migration as well, so duplicate any such ordering for the * corresponding migration actions. * * \param[in,out] order Ordering constraint to check */ void pcmk__order_migration_equivalents(pcmk__action_relation_t *order) { char *first_task = NULL; char *then_task = NULL; bool then_migratable; bool first_migratable; // Only orderings between unrelated resources are relevant if ((order->rsc1 == NULL) || (order->rsc2 == NULL) || (order->rsc1 == order->rsc2) || is_parent(order->rsc1, order->rsc2) || is_parent(order->rsc2, order->rsc1)) { return; } // Only orderings involving at least one migratable resource are relevant first_migratable = pcmk_is_set(order->rsc1->flags, pcmk_rsc_migratable); then_migratable = pcmk_is_set(order->rsc2->flags, pcmk_rsc_migratable); if (!first_migratable && !then_migratable) { return; } // Check which actions are involved first_task = task_from_action_or_key(order->action1, order->task1); then_task = task_from_action_or_key(order->action2, order->task2); if (pcmk__str_eq(first_task, PCMK_ACTION_START, pcmk__str_none) && pcmk__str_eq(then_task, PCMK_ACTION_START, pcmk__str_none)) { uint32_t flags = pcmk__ar_ordered; if (first_migratable && then_migratable) { /* A start then B start * -> A migrate_from then B migrate_to */ pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, flags, order->rsc1->cluster); } if (then_migratable) { if (first_migratable) { pcmk__set_relation_flags(flags, pcmk__ar_if_first_unmigratable); } /* A start then B start * -> A start then B migrate_to (if start is not part of a * migration) */ pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_START, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, flags, order->rsc1->cluster); } } else if (then_migratable && pcmk__str_eq(first_task, PCMK_ACTION_STOP, pcmk__str_none) && pcmk__str_eq(then_task, PCMK_ACTION_STOP, pcmk__str_none)) { uint32_t flags = pcmk__ar_ordered; if (first_migratable) { pcmk__set_relation_flags(flags, pcmk__ar_if_first_unmigratable); } /* For an ordering "stop A then stop B", if A is moving via restart, and * B is migrating, enforce that B's migrate_to occurs after A's stop. */ pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_STOP, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, flags, order->rsc1->cluster); // Also order B's migrate_from after A's stop during partial migrations if (order->rsc2->partial_migration_target != NULL) { pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_STOP, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, flags, order->rsc1->cluster); } } else if (pcmk__str_eq(first_task, PCMK_ACTION_PROMOTE, pcmk__str_none) && pcmk__str_eq(then_task, PCMK_ACTION_START, pcmk__str_none)) { uint32_t flags = pcmk__ar_ordered; if (then_migratable) { /* A promote then B start * -> A promote then B migrate_to */ pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_PROMOTE, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, flags, order->rsc1->cluster); } } else if (pcmk__str_eq(first_task, PCMK_ACTION_DEMOTE, pcmk__str_none) && pcmk__str_eq(then_task, PCMK_ACTION_STOP, pcmk__str_none)) { uint32_t flags = pcmk__ar_ordered; if (then_migratable) { /* A demote then B stop * -> A demote then B migrate_to */ pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_DEMOTE, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_TO, 0), NULL, flags, order->rsc1->cluster); // Order B migrate_from after A demote during partial migrations if (order->rsc2->partial_migration_target != NULL) { pcmk__new_ordering(order->rsc1, pcmk__op_key(order->rsc1->id, PCMK_ACTION_DEMOTE, 0), NULL, order->rsc2, pcmk__op_key(order->rsc2->id, PCMK_ACTION_MIGRATE_FROM, 0), NULL, flags, order->rsc1->cluster); } } } free(first_task); free(then_task); } diff --git a/lib/pacemaker/pcmk_sched_probes.c b/lib/pacemaker/pcmk_sched_probes.c index a0fa76ea60..73a042b5ee 100644 --- a/lib/pacemaker/pcmk_sched_probes.c +++ b/lib/pacemaker/pcmk_sched_probes.c @@ -1,906 +1,905 @@ /* * 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 "libpacemaker_private.h" /*! * \internal * \brief Add the expected result to a newly created probe * * \param[in,out] probe Probe action to add expected result to * \param[in] rsc Resource that probe is for * \param[in] node Node that probe will run on */ static void add_expected_result(pcmk_action_t *probe, const pcmk_resource_t *rsc, const pcmk_node_t *node) { // Check whether resource is currently active on node pcmk_node_t *running = pe_find_node_id(rsc->running_on, node->details->id); // The expected result is what we think the resource's current state is if (running == NULL) { pe__add_action_expected_result(probe, CRM_EX_NOT_RUNNING); } else if (rsc->role == pcmk_role_promoted) { pe__add_action_expected_result(probe, CRM_EX_PROMOTED); } } /*! * \internal * \brief Create any needed robes on a node for a list of resources * * \param[in,out] rscs List of resources to create probes for * \param[in,out] node Node to create probes on * * \return true if any probe was created, otherwise false */ bool pcmk__probe_resource_list(GList *rscs, pcmk_node_t *node) { bool any_created = false; for (GList *iter = rscs; iter != NULL; iter = iter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data; if (rsc->cmds->create_probe(rsc, node)) { any_created = true; } } return any_created; } /*! * \internal * \brief Order one resource's start after another's start-up probe * * \param[in,out] rsc1 Resource that might get start-up probe * \param[in] rsc2 Resource that might be started */ static void probe_then_start(pcmk_resource_t *rsc1, pcmk_resource_t *rsc2) { if ((rsc1->allocated_to != NULL) && (g_hash_table_lookup(rsc1->known_on, rsc1->allocated_to->details->id) == NULL)) { pcmk__new_ordering(rsc1, pcmk__op_key(rsc1->id, PCMK_ACTION_MONITOR, 0), NULL, rsc2, pcmk__op_key(rsc2->id, PCMK_ACTION_START, 0), NULL, pcmk__ar_ordered, rsc1->cluster); } } /*! * \internal * \brief Check whether a guest resource will stop * * \param[in] node Guest node to check * * \return true if guest resource will likely stop, otherwise false */ static bool guest_resource_will_stop(const pcmk_node_t *node) { const pcmk_resource_t *guest_rsc = node->details->remote_rsc->container; /* Ideally, we'd check whether the guest has a required stop, but that * information doesn't exist yet, so approximate it ... */ return node->details->remote_requires_reset || node->details->unclean || pcmk_is_set(guest_rsc->flags, pcmk_rsc_failed) || (guest_rsc->next_role == pcmk_role_stopped) // Guest is moving || ((guest_rsc->role > pcmk_role_stopped) && (guest_rsc->allocated_to != NULL) && (pe_find_node(guest_rsc->running_on, guest_rsc->allocated_to->details->uname) == NULL)); } /*! * \internal * \brief Create a probe action for a resource on a node * * \param[in,out] rsc Resource to create probe for * \param[in,out] node Node to create probe on * * \return Newly created probe action */ static pcmk_action_t * probe_action(pcmk_resource_t *rsc, pcmk_node_t *node) { pcmk_action_t *probe = NULL; char *key = pcmk__op_key(rsc->id, PCMK_ACTION_MONITOR, 0); crm_debug("Scheduling probe of %s %s on %s", pcmk_role_text(rsc->role), rsc->id, pcmk__node_name(node)); probe = custom_action(rsc, key, PCMK_ACTION_MONITOR, node, FALSE, rsc->cluster); pcmk__clear_action_flags(probe, pcmk_action_optional); pcmk__order_vs_unfence(rsc, node, probe, pcmk__ar_ordered); add_expected_result(probe, rsc, node); return probe; } /*! * \internal * \brief Create probes for a resource on a node, if needed * * \brief Schedule any probes needed for a resource on a node * * \param[in,out] rsc Resource to create probe for * \param[in,out] node Node to create probe on * * \return true if any probe was created, otherwise false */ bool pcmk__probe_rsc_on_node(pcmk_resource_t *rsc, pcmk_node_t *node) { uint32_t flags = pcmk__ar_ordered; pcmk_action_t *probe = NULL; pcmk_node_t *allowed = NULL; pcmk_resource_t *top = uber_parent(rsc); const char *reason = NULL; CRM_ASSERT((rsc != NULL) && (node != NULL)); if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_probe_resources)) { reason = "start-up probes are disabled"; goto no_probe; } if (pe__is_guest_or_remote_node(node)) { const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS); if (pcmk__str_eq(class, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_none)) { reason = "Pacemaker Remote nodes cannot run stonith agents"; goto no_probe; } else if (pe__is_guest_node(node) && pe__resource_contains_guest_node(rsc->cluster, rsc)) { reason = "guest nodes cannot run resources containing guest nodes"; goto no_probe; } else if (rsc->is_remote_node) { reason = "Pacemaker Remote nodes cannot host remote connections"; goto no_probe; } } // If this is a collective resource, probes are created for its children if (rsc->children != NULL) { return pcmk__probe_resource_list(rsc->children, node); } if ((rsc->container != NULL) && !rsc->is_remote_node) { reason = "resource is inside a container"; goto no_probe; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { reason = "resource is orphaned"; goto no_probe; } else if (g_hash_table_lookup(rsc->known_on, node->details->id) != NULL) { reason = "resource state is already known"; goto no_probe; } allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id); if (rsc->exclusive_discover || top->exclusive_discover) { // Exclusive discovery is enabled ... if (allowed == NULL) { // ... but this node is not allowed to run the resource reason = "resource has exclusive discovery but is not allowed " "on node"; goto no_probe; } else if (allowed->rsc_discover_mode != pcmk_probe_exclusive) { // ... but no constraint marks this node for discovery of resource reason = "resource has exclusive discovery but is not enabled " "on node"; goto no_probe; } } if (allowed == NULL) { allowed = node; } if (allowed->rsc_discover_mode == pcmk_probe_never) { reason = "node has discovery disabled"; goto no_probe; } if (pe__is_guest_node(node)) { pcmk_resource_t *guest = node->details->remote_rsc->container; if (guest->role == pcmk_role_stopped) { // The guest is stopped, so we know no resource is active there reason = "node's guest is stopped"; probe_then_start(guest, top); goto no_probe; } else if (guest_resource_will_stop(node)) { reason = "node's guest will stop"; // Order resource start after guest stop (in case it's restarting) pcmk__new_ordering(guest, pcmk__op_key(guest->id, PCMK_ACTION_STOP, 0), NULL, top, pcmk__op_key(top->id, PCMK_ACTION_START, 0), NULL, pcmk__ar_ordered, rsc->cluster); goto no_probe; } } // We've eliminated all cases where a probe is not needed, so now it is probe = probe_action(rsc, node); /* Below, we will order the probe relative to start or reload. If this is a * clone instance, the start or reload is for the entire clone rather than * just the instance. Otherwise, the start or reload is for the resource * itself. */ if (!pcmk__is_clone(top)) { top = rsc; } /* Prevent a start if the resource can't be probed, but don't cause the * resource or entire clone to stop if already active. */ if (!pcmk_is_set(probe->flags, pcmk_action_runnable) && (top->running_on == NULL)) { pcmk__set_relation_flags(flags, pcmk__ar_unrunnable_first_blocks); } // Start or reload after probing the resource pcmk__new_ordering(rsc, NULL, probe, top, pcmk__op_key(top->id, PCMK_ACTION_START, 0), NULL, flags, rsc->cluster); pcmk__new_ordering(rsc, NULL, probe, top, reload_key(rsc), NULL, pcmk__ar_ordered, rsc->cluster); return true; no_probe: pcmk__rsc_trace(rsc, "Skipping probe for %s on %s because %s", rsc->id, node->details->id, reason); return false; } /*! * \internal * \brief Check whether a probe should be ordered before another action * * \param[in] probe Probe action to check * \param[in] then Other action to check * * \return true if \p probe should be ordered before \p then, otherwise false */ static bool probe_needed_before_action(const pcmk_action_t *probe, const pcmk_action_t *then) { // Probes on a node are performed after unfencing it, not before if (pcmk__str_eq(then->task, PCMK_ACTION_STONITH, pcmk__str_none) && pcmk__same_node(probe->node, then->node)) { const char *op = g_hash_table_lookup(then->meta, PCMK__META_STONITH_ACTION); if (pcmk__str_eq(op, PCMK_ACTION_ON, pcmk__str_casei)) { return false; } } // Probes should be done on a node before shutting it down if (pcmk__str_eq(then->task, PCMK_ACTION_DO_SHUTDOWN, pcmk__str_none) && (probe->node != NULL) && (then->node != NULL) && !pcmk__same_node(probe->node, then->node)) { return false; } // Otherwise probes should always be done before any other action return true; } /*! * \internal * \brief Add implicit "probe then X" orderings for "stop then X" orderings * * If the state of a resource is not known yet, a probe will be scheduled, * expecting a "not running" result. If the probe fails, a stop will not be * scheduled until the next transition. Thus, if there are ordering constraints * like "stop this resource then do something else that's not for the same * resource", add implicit "probe this resource then do something" equivalents * so the relation is upheld until we know whether a stop is needed. * * \param[in,out] scheduler Scheduler data */ static void add_probe_orderings_for_stops(pcmk_scheduler_t *scheduler) { for (GList *iter = scheduler->ordering_constraints; iter != NULL; iter = iter->next) { pcmk__action_relation_t *order = iter->data; uint32_t order_flags = pcmk__ar_ordered; GList *probes = NULL; GList *then_actions = NULL; pcmk_action_t *first = NULL; pcmk_action_t *then = NULL; // Skip disabled orderings if (order->flags == pcmk__ar_none) { continue; } // Skip non-resource orderings, and orderings for the same resource if ((order->rsc1 == NULL) || (order->rsc1 == order->rsc2)) { continue; } // Skip invalid orderings (shouldn't be possible) first = order->action1; then = order->action2; if (((first == NULL) && (order->task1 == NULL)) || ((then == NULL) && (order->task2 == NULL))) { continue; } // Skip orderings for first actions other than stop if ((first != NULL) && !pcmk__str_eq(first->task, PCMK_ACTION_STOP, pcmk__str_none)) { continue; } else if ((first == NULL) && !pcmk__ends_with(order->task1, "_" PCMK_ACTION_STOP "_0")) { continue; } /* Do not imply a probe ordering for a resource inside of a stopping * container. Otherwise, it might introduce a transition loop, since a * probe could be scheduled after the container starts again. */ if ((order->rsc2 != NULL) && (order->rsc1->container == order->rsc2)) { if ((then != NULL) && pcmk__str_eq(then->task, PCMK_ACTION_STOP, pcmk__str_none)) { continue; } else if ((then == NULL) && pcmk__ends_with(order->task2, "_" PCMK_ACTION_STOP "_0")) { continue; } } // Preserve certain order options for future filtering if (pcmk_is_set(order->flags, pcmk__ar_if_first_unmigratable)) { pcmk__set_relation_flags(order_flags, pcmk__ar_if_first_unmigratable); } if (pcmk_is_set(order->flags, pcmk__ar_if_on_same_node)) { pcmk__set_relation_flags(order_flags, pcmk__ar_if_on_same_node); } // Preserve certain order types for future filtering if ((order->flags == pcmk__ar_if_required_on_same_node) || (order->flags == pcmk__ar_if_on_same_node_or_target)) { order_flags = order->flags; } // List all scheduled probes for the first resource probes = pe__resource_actions(order->rsc1, NULL, PCMK_ACTION_MONITOR, FALSE); if (probes == NULL) { // There aren't any continue; } // List all relevant "then" actions if (then != NULL) { then_actions = g_list_prepend(NULL, then); } else if (order->rsc2 != NULL) { then_actions = find_actions(order->rsc2->actions, order->task2, NULL); if (then_actions == NULL) { // There aren't any g_list_free(probes); continue; } } crm_trace("Implying 'probe then' orderings for '%s then %s' " "(id=%d, type=%.6x)", ((first == NULL)? order->task1 : first->uuid), ((then == NULL)? order->task2 : then->uuid), order->id, order->flags); for (GList *probe_iter = probes; probe_iter != NULL; probe_iter = probe_iter->next) { pcmk_action_t *probe = (pcmk_action_t *) probe_iter->data; for (GList *then_iter = then_actions; then_iter != NULL; then_iter = then_iter->next) { pcmk_action_t *then = (pcmk_action_t *) then_iter->data; if (probe_needed_before_action(probe, then)) { order_actions(probe, then, order_flags); } } } g_list_free(then_actions); g_list_free(probes); } } /*! * \internal * \brief Add necessary orderings between probe and starts of clone instances * * , in additon to the ordering with the parent resource added upon creating * the probe. * * \param[in,out] probe Probe as 'first' action in an ordering * \param[in,out] after 'then' action wrapper in the ordering */ static void add_start_orderings_for_probe(pcmk_action_t *probe, pcmk__related_action_t *after) { uint32_t flags = pcmk__ar_ordered|pcmk__ar_unrunnable_first_blocks; /* Although the ordering between the probe of the clone instance and the * start of its parent has been added in pcmk__probe_rsc_on_node(), we * avoided enforcing `pcmk__ar_unrunnable_first_blocks` order type for that * as long as any of the clone instances are running to prevent them from * being unexpectedly stopped. * * On the other hand, we still need to prevent any inactive instances from * starting unless the probe is runnable so that we don't risk starting too * many instances before we know the state on all nodes. */ if ((after->action->rsc->variant <= pcmk_rsc_variant_group) || pcmk_is_set(probe->flags, pcmk_action_runnable) // The order type is already enforced for its parent. || pcmk_is_set(after->type, pcmk__ar_unrunnable_first_blocks) || (pe__const_top_resource(probe->rsc, false) != after->action->rsc) || !pcmk__str_eq(after->action->task, PCMK_ACTION_START, pcmk__str_none)) { return; } crm_trace("Adding probe start orderings for 'unrunnable %s@%s " "then instances of %s@%s'", probe->uuid, pcmk__node_name(probe->node), after->action->uuid, pcmk__node_name(after->action->node)); for (GList *then_iter = after->action->actions_after; then_iter != NULL; then_iter = then_iter->next) { pcmk__related_action_t *then = then_iter->data; if (then->action->rsc->running_on || (pe__const_top_resource(then->action->rsc, false) != after->action->rsc) || !pcmk__str_eq(then->action->task, PCMK_ACTION_START, pcmk__str_none)) { continue; } crm_trace("Adding probe start ordering for 'unrunnable %s@%s " "then %s@%s' (type=%#.6x)", probe->uuid, pcmk__node_name(probe->node), then->action->uuid, pcmk__node_name(then->action->node), flags); /* Prevent the instance from starting if the instance can't, but don't * cause any other intances to stop if already active. */ order_actions(probe, then->action, flags); } return; } /*! * \internal * \brief Order probes before restarts and re-promotes * * If a given ordering is a "probe then start" or "probe then promote" ordering, * add an implicit "probe then stop/demote" ordering in case the action is part * of a restart/re-promote, and do the same recursively for all actions ordered * after the "then" action. * * \param[in,out] probe Probe as 'first' action in an ordering * \param[in,out] after 'then' action in the ordering */ static void add_restart_orderings_for_probe(pcmk_action_t *probe, pcmk_action_t *after) { GList *iter = NULL; bool interleave = false; pcmk_resource_t *compatible_rsc = NULL; // Validate that this is a resource probe followed by some action if ((after == NULL) || (probe == NULL) || (probe->rsc == NULL) || (probe->rsc->variant != pcmk_rsc_variant_primitive) || !pcmk__str_eq(probe->task, PCMK_ACTION_MONITOR, pcmk__str_none)) { return; } // Avoid running into any possible loop if (pcmk_is_set(after->flags, pcmk_action_detect_loop)) { return; } pcmk__set_action_flags(after, pcmk_action_detect_loop); crm_trace("Adding probe restart orderings for '%s@%s then %s@%s'", probe->uuid, pcmk__node_name(probe->node), after->uuid, pcmk__node_name(after->node)); /* Add restart orderings if "then" is for a different primitive. * Orderings for collective resources will be added later. */ if ((after->rsc != NULL) && (after->rsc->variant == pcmk_rsc_variant_primitive) && (probe->rsc != after->rsc)) { GList *then_actions = NULL; if (pcmk__str_eq(after->task, PCMK_ACTION_START, pcmk__str_none)) { then_actions = pe__resource_actions(after->rsc, NULL, PCMK_ACTION_STOP, FALSE); } else if (pcmk__str_eq(after->task, PCMK_ACTION_PROMOTE, pcmk__str_none)) { then_actions = pe__resource_actions(after->rsc, NULL, PCMK_ACTION_DEMOTE, FALSE); } for (iter = then_actions; iter != NULL; iter = iter->next) { pcmk_action_t *then = (pcmk_action_t *) iter->data; // Skip pseudo-actions (for example, those implied by fencing) if (!pcmk_is_set(then->flags, pcmk_action_pseudo)) { order_actions(probe, then, pcmk__ar_ordered); } } g_list_free(then_actions); } /* Detect whether "then" is an interleaved clone action. For these, we want * to add orderings only for the relevant instance. */ if ((after->rsc != NULL) && (after->rsc->variant > pcmk_rsc_variant_group)) { const char *interleave_s = g_hash_table_lookup(after->rsc->meta, PCMK_META_INTERLEAVE); interleave = crm_is_true(interleave_s); if (interleave) { compatible_rsc = pcmk__find_compatible_instance(probe->rsc, after->rsc, pcmk_role_unknown, false); } } /* Now recursively do the same for all actions ordered after "then". This * also handles collective resources since the collective action will be * ordered before its individual instances' actions. */ for (iter = after->actions_after; iter != NULL; iter = iter->next) { pcmk__related_action_t *after_wrapper = iter->data; /* pcmk__ar_first_implies_then is the reason why a required A.start * implies/enforces B.start to be required too, which is the cause of * B.restart/re-promote. * * Not sure about pcmk__ar_first_implies_same_node_then though. It's now * only used for unfencing case, which tends to introduce transition * loops... */ if (!pcmk_is_set(after_wrapper->type, pcmk__ar_first_implies_then)) { /* The order type between a group/clone and its child such as * B.start-> B_child.start is: * pcmk__ar_then_implies_first_graphed * |pcmk__ar_unrunnable_first_blocks * * Proceed through the ordering chain and build dependencies with * its children. */ if ((after->rsc == NULL) || (after->rsc->variant < pcmk_rsc_variant_group) || (probe->rsc->parent == after->rsc) || (after_wrapper->action->rsc == NULL) || (after_wrapper->action->rsc->variant > pcmk_rsc_variant_group) || (after->rsc != after_wrapper->action->rsc->parent)) { continue; } /* Proceed to the children of a group or a non-interleaved clone. * For an interleaved clone, proceed only to the relevant child. */ if ((after->rsc->variant > pcmk_rsc_variant_group) && interleave && ((compatible_rsc == NULL) || (compatible_rsc != after_wrapper->action->rsc))) { continue; } } crm_trace("Recursively adding probe restart orderings for " "'%s@%s then %s@%s' (type=%#.6x)", after->uuid, pcmk__node_name(after->node), after_wrapper->action->uuid, pcmk__node_name(after_wrapper->action->node), after_wrapper->type); add_restart_orderings_for_probe(probe, after_wrapper->action); } } /*! * \internal * \brief Clear the tracking flag on all scheduled actions * * \param[in,out] scheduler Scheduler data */ static void clear_actions_tracking_flag(pcmk_scheduler_t *scheduler) { for (GList *iter = scheduler->actions; iter != NULL; iter = iter->next) { pcmk_action_t *action = iter->data; pcmk__clear_action_flags(action, pcmk_action_detect_loop); } } /*! * \internal * \brief Add start and restart orderings for probes scheduled for a resource * * \param[in,out] data Resource whose probes should be ordered * \param[in] user_data Unused */ static void add_start_restart_orderings_for_rsc(gpointer data, gpointer user_data) { pcmk_resource_t *rsc = data; GList *probes = NULL; // For collective resources, order each instance recursively if (rsc->variant != pcmk_rsc_variant_primitive) { g_list_foreach(rsc->children, add_start_restart_orderings_for_rsc, NULL); return; } // Find all probes for given resource probes = pe__resource_actions(rsc, NULL, PCMK_ACTION_MONITOR, FALSE); // Add probe restart orderings for each probe found for (GList *iter = probes; iter != NULL; iter = iter->next) { pcmk_action_t *probe = (pcmk_action_t *) iter->data; for (GList *then_iter = probe->actions_after; then_iter != NULL; then_iter = then_iter->next) { pcmk__related_action_t *then = then_iter->data; add_start_orderings_for_probe(probe, then); add_restart_orderings_for_probe(probe, then->action); clear_actions_tracking_flag(rsc->cluster); } } g_list_free(probes); } /*! * \internal * \brief Add "A then probe B" orderings for "A then B" orderings * * \param[in,out] scheduler Scheduler data * * \note This function is currently disabled (see next comment). */ static void order_then_probes(pcmk_scheduler_t *scheduler) { #if 0 /* Given an ordering "A then B", we would prefer to wait for A to be started * before probing B. * * For example, if A is a filesystem which B can't even run without, it * would be helpful if the author of B's agent could assume that A is * running before B.monitor will be called. * * However, we can't _only_ probe after A is running, otherwise we wouldn't * detect the state of B if A could not be started. We can't even do an * opportunistic version of this, because B may be moving: * * A.stop -> A.start -> B.probe -> B.stop -> B.start * * and if we add B.stop -> A.stop here, we get a loop: * * A.stop -> A.start -> B.probe -> B.stop -> A.stop * * We could kill the "B.probe -> B.stop" dependency, but that could mean * stopping B "too" soon, because B.start must wait for the probe, and * we don't want to stop B if we can't start it. * * We could add the ordering only if A is an anonymous clone with * clone-max == node-max (since we'll never be moving it). However, we could * still be stopping one instance at the same time as starting another. * * The complexity of checking for allowed conditions combined with the ever * narrowing use case suggests that this code should remain disabled until * someone gets smarter. */ for (GList *iter = scheduler->resources; iter != NULL; iter = iter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) iter->data; pcmk_action_t *start = NULL; GList *actions = NULL; GList *probes = NULL; actions = pe__resource_actions(rsc, NULL, PCMK_ACTION_START, FALSE); if (actions) { start = actions->data; g_list_free(actions); } if (start == NULL) { crm_debug("No start action for %s", rsc->id); continue; } probes = pe__resource_actions(rsc, NULL, PCMK_ACTION_MONITOR, FALSE); for (actions = start->actions_before; actions != NULL; actions = actions->next) { pcmk__related_action_t *before = actions->data; pcmk_action_t *first = before->action; pcmk_resource_t *first_rsc = first->rsc; if (first->required_runnable_before) { for (GList *clone_actions = first->actions_before; clone_actions != NULL; clone_actions = clone_actions->next) { before = clone_actions->data; crm_trace("Testing '%s then %s' for %s", first->uuid, before->action->uuid, start->uuid); CRM_ASSERT(before->action->rsc != NULL); first_rsc = before->action->rsc; break; } } else if (!pcmk__str_eq(first->task, PCMK_ACTION_START, pcmk__str_none)) { crm_trace("Not a start op %s for %s", first->uuid, start->uuid); } if (first_rsc == NULL) { continue; } else if (pe__const_top_resource(first_rsc, false) == pe__const_top_resource(start->rsc, false)) { crm_trace("Same parent %s for %s", first_rsc->id, start->uuid); continue; } else if (!pcmk__is_clone(pe__const_top_resource(first_rsc, false))) { crm_trace("Not a clone %s for %s", first_rsc->id, start->uuid); continue; } crm_debug("Applying %s before %s %d", first->uuid, start->uuid, pe__const_top_resource(first_rsc, false)->variant); for (GList *probe_iter = probes; probe_iter != NULL; probe_iter = probe_iter->next) { pcmk_action_t *probe = (pcmk_action_t *) probe_iter->data; crm_debug("Ordering %s before %s", first->uuid, probe->uuid); order_actions(first, probe, pcmk__ar_ordered); } } } #endif } void pcmk__order_probes(pcmk_scheduler_t *scheduler) { // Add orderings for "probe then X" g_list_foreach(scheduler->resources, add_start_restart_orderings_for_rsc, NULL); add_probe_orderings_for_stops(scheduler); order_then_probes(scheduler); } /*! * \internal * \brief Schedule any probes needed * * \param[in,out] scheduler Scheduler data * * \note This may also schedule fencing of failed remote nodes. */ void pcmk__schedule_probes(pcmk_scheduler_t *scheduler) { // Schedule probes on each node in the cluster as needed for (GList *iter = scheduler->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *node = (pcmk_node_t *) iter->data; const char *probed = NULL; if (!node->details->online) { // Don't probe offline nodes if (pcmk__is_failed_remote_node(node)) { pe_fence_node(scheduler, node, "the connection is unrecoverable", FALSE); } continue; } else if (node->details->unclean) { // ... or nodes that need fencing continue; } else if (!node->details->rsc_discovery_enabled) { // The user requested that probes not be done on this node continue; } /* This is no longer needed for live clusters, since the probe_complete * node attribute will never be in the CIB. However this is still useful * for processing old saved CIBs (< 1.1.14), including the * reprobe-target_rc regression test. */ probed = pe_node_attribute_raw(node, CRM_OP_PROBED); if (probed != NULL && crm_is_true(probed) == FALSE) { pcmk_action_t *probe_op = NULL; probe_op = custom_action(NULL, crm_strdup_printf("%s-%s", CRM_OP_REPROBE, node->details->uname), CRM_OP_REPROBE, node, FALSE, scheduler); - add_hash_param(probe_op->meta, PCMK__META_OP_NO_WAIT, - PCMK_VALUE_TRUE); + pcmk__insert_meta(probe_op, PCMK__META_OP_NO_WAIT, PCMK_VALUE_TRUE); continue; } // Probe each resource in the cluster on this node, as needed pcmk__probe_resource_list(scheduler->resources, node); } } diff --git a/lib/pacemaker/pcmk_sched_recurring.c b/lib/pacemaker/pcmk_sched_recurring.c index fcfff3614c..36a47d0e64 100644 --- a/lib/pacemaker/pcmk_sched_recurring.c +++ b/lib/pacemaker/pcmk_sched_recurring.c @@ -1,743 +1,743 @@ /* * 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 "libpacemaker_private.h" // Information parsed from an operation history entry in the CIB struct op_history { // XML attributes const char *id; // ID of history entry const char *name; // Action name // Parsed information char *key; // Operation key for action enum rsc_role_e role; // Action role (or pcmk_role_unknown for default) guint interval_ms; // Action interval }; /*! * \internal * \brief Parse an interval from XML * * \param[in] xml XML containing an interval attribute * * \return Interval parsed from XML (or 0 as default) */ static guint xe_interval(const xmlNode *xml) { guint interval_ms = 0U; pcmk_parse_interval_spec(crm_element_value(xml, PCMK_META_INTERVAL), &interval_ms); return interval_ms; } /*! * \internal * \brief Check whether an operation exists multiple times in resource history * * \param[in] rsc Resource with history to search * \param[in] name Name of action to search for * \param[in] interval_ms Interval (in milliseconds) of action to search for * * \return true if an operation with \p name and \p interval_ms exists more than * once in the operation history of \p rsc, otherwise false */ static bool is_op_dup(const pcmk_resource_t *rsc, const char *name, guint interval_ms) { const char *id = NULL; for (xmlNode *op = first_named_child(rsc->ops_xml, PCMK_XE_OP); op != NULL; op = crm_next_same_xml(op)) { // Check whether action name and interval match if (!pcmk__str_eq(crm_element_value(op, PCMK_XA_NAME), name, pcmk__str_none) || (xe_interval(op) != interval_ms)) { continue; } if (pcmk__xe_id(op) == NULL) { continue; // Shouldn't be possible } if (id == NULL) { id = pcmk__xe_id(op); // First matching op } else { pcmk__config_err("Operation %s is duplicate of %s (do not use " "same name and interval combination more " "than once per resource)", pcmk__xe_id(op), id); return true; } } return false; } /*! * \internal * \brief Check whether an action name is one that can be recurring * * \param[in] name Action name to check * * \return true if \p name is an action known to be unsuitable as a recurring * operation, otherwise false * * \note Pacemaker's current philosophy is to allow users to configure recurring * operations except for a short list of actions known not to be suitable * for that (as opposed to allowing only actions known to be suitable, * which includes only monitor). Among other things, this approach allows * users to define their own custom operations and make them recurring, * though that use case is not well tested. */ static bool op_cannot_recur(const char *name) { return pcmk__str_any_of(name, PCMK_ACTION_STOP, PCMK_ACTION_START, PCMK_ACTION_DEMOTE, PCMK_ACTION_PROMOTE, PCMK_ACTION_RELOAD_AGENT, PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, NULL); } /*! * \internal * \brief Check whether a resource history entry is for a recurring action * * \param[in] rsc Resource that history entry is for * \param[in] xml XML of resource history entry to check * \param[out] op Where to store parsed info if recurring * * \return true if \p xml is for a recurring action, otherwise false */ static bool is_recurring_history(const pcmk_resource_t *rsc, const xmlNode *xml, struct op_history *op) { const char *role = NULL; op->interval_ms = xe_interval(xml); if (op->interval_ms == 0) { return false; // Not recurring } op->id = pcmk__xe_id(xml); if (pcmk__str_empty(op->id)) { pcmk__config_err("Ignoring resource history entry without ID"); return false; // Shouldn't be possible (unless CIB was manually edited) } op->name = crm_element_value(xml, PCMK_XA_NAME); if (op_cannot_recur(op->name)) { pcmk__config_err("Ignoring %s because %s action cannot be recurring", op->id, pcmk__s(op->name, "unnamed")); return false; } // There should only be one recurring operation per action/interval if (is_op_dup(rsc, op->name, op->interval_ms)) { return false; } // Ensure role is valid if specified role = crm_element_value(xml, PCMK_XA_ROLE); if (role == NULL) { op->role = pcmk_role_unknown; } else { op->role = pcmk_parse_role(role); if (op->role == pcmk_role_unknown) { pcmk__config_err("Ignoring %s role because %s is not a valid role", op->id, role); return false; } } // Only actions that are still configured and enabled matter if (pcmk__find_action_config(rsc, op->name, op->interval_ms, false) == NULL) { pcmk__rsc_trace(rsc, "Ignoring %s (%s-interval %s for %s) because it is " "disabled or no longer in configuration", op->id, pcmk__readable_interval(op->interval_ms), op->name, rsc->id); return false; } op->key = pcmk__op_key(rsc->id, op->name, op->interval_ms); return true; } /*! * \internal * \brief Check whether a recurring action for an active role should be optional * * \param[in] rsc Resource that recurring action is for * \param[in] node Node that \p rsc will be active on (if any) * \param[in] key Operation key for recurring action to check * \param[in,out] start Start action for \p rsc * * \return true if recurring action should be optional, otherwise false */ static bool active_recurring_should_be_optional(const pcmk_resource_t *rsc, const pcmk_node_t *node, const char *key, pcmk_action_t *start) { GList *possible_matches = NULL; if (node == NULL) { // Should only be possible if unmanaged and stopped pcmk__rsc_trace(rsc, "%s will be mandatory because resource is unmanaged", key); return false; } if (!pcmk_is_set(rsc->cmds->action_flags(start, NULL), pcmk_action_optional)) { pcmk__rsc_trace(rsc, "%s will be mandatory because %s is", key, start->uuid); return false; } possible_matches = find_actions_exact(rsc->actions, key, node); if (possible_matches == NULL) { pcmk__rsc_trace(rsc, "%s will be mandatory because it is not active on %s", key, pcmk__node_name(node)); return false; } for (const GList *iter = possible_matches; iter != NULL; iter = iter->next) { const pcmk_action_t *op = (const pcmk_action_t *) iter->data; if (pcmk_is_set(op->flags, pcmk_action_reschedule)) { pcmk__rsc_trace(rsc, "%s will be mandatory because " "it needs to be rescheduled", key); g_list_free(possible_matches); return false; } } g_list_free(possible_matches); return true; } /*! * \internal * \brief Create recurring action from resource history entry for an active role * * \param[in,out] rsc Resource that resource history is for * \param[in,out] start Start action for \p rsc on \p node * \param[in] node Node that resource will be active on (if any) * \param[in] op Resource history entry */ static void recurring_op_for_active(pcmk_resource_t *rsc, pcmk_action_t *start, const pcmk_node_t *node, const struct op_history *op) { pcmk_action_t *mon = NULL; bool is_optional = true; bool role_match = false; enum rsc_role_e monitor_role = op->role; // We're only interested in recurring actions for active roles if (monitor_role == pcmk_role_stopped) { return; } is_optional = active_recurring_should_be_optional(rsc, node, op->key, start); // Check whether monitor's role matches role resource will have if (monitor_role == pcmk_role_unknown) { monitor_role = pcmk_role_unpromoted; role_match = (rsc->next_role != pcmk_role_promoted); } else { role_match = (rsc->next_role == monitor_role); } if (!role_match) { if (is_optional) { // It's running, so cancel it char *after_key = NULL; pcmk_action_t *cancel_op = pcmk__new_cancel_action(rsc, op->name, op->interval_ms, node); switch (rsc->role) { case pcmk_role_unpromoted: case pcmk_role_started: if (rsc->next_role == pcmk_role_promoted) { after_key = promote_key(rsc); } else if (rsc->next_role == pcmk_role_stopped) { after_key = stop_key(rsc); } break; case pcmk_role_promoted: after_key = demote_key(rsc); break; default: break; } if (after_key) { pcmk__new_ordering(rsc, NULL, cancel_op, rsc, after_key, NULL, pcmk__ar_unrunnable_first_blocks, rsc->cluster); } } do_crm_log((is_optional? LOG_INFO : LOG_TRACE), "%s recurring action %s because %s configured for %s role " "(not %s)", (is_optional? "Cancelling" : "Ignoring"), op->key, op->id, pcmk_role_text(monitor_role), pcmk_role_text(rsc->next_role)); return; } pcmk__rsc_trace(rsc, "Creating %s recurring action %s for %s (%s %s on %s)", (is_optional? "optional" : "mandatory"), op->key, op->id, rsc->id, pcmk_role_text(rsc->next_role), pcmk__node_name(node)); mon = custom_action(rsc, strdup(op->key), op->name, node, is_optional, rsc->cluster); if (!pcmk_is_set(start->flags, pcmk_action_runnable)) { pcmk__rsc_trace(rsc, "%s is unrunnable because start is", mon->uuid); pcmk__clear_action_flags(mon, pcmk_action_runnable); } else if ((node == NULL) || !node->details->online || node->details->unclean) { pcmk__rsc_trace(rsc, "%s is unrunnable because no node is available", mon->uuid); pcmk__clear_action_flags(mon, pcmk_action_runnable); } else if (!pcmk_is_set(mon->flags, pcmk_action_optional)) { pcmk__rsc_info(rsc, "Start %s-interval %s for %s on %s", pcmk__readable_interval(op->interval_ms), mon->task, rsc->id, pcmk__node_name(node)); } if (rsc->next_role == pcmk_role_promoted) { pe__add_action_expected_result(mon, CRM_EX_PROMOTED); } // Order monitor relative to other actions if ((node == NULL) || pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__new_ordering(rsc, start_key(rsc), NULL, NULL, strdup(mon->uuid), mon, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks, rsc->cluster); pcmk__new_ordering(rsc, reload_key(rsc), NULL, NULL, strdup(mon->uuid), mon, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks, rsc->cluster); if (rsc->next_role == pcmk_role_promoted) { pcmk__new_ordering(rsc, promote_key(rsc), NULL, rsc, NULL, mon, pcmk__ar_ordered |pcmk__ar_unrunnable_first_blocks, rsc->cluster); } else if (rsc->role == pcmk_role_promoted) { pcmk__new_ordering(rsc, demote_key(rsc), NULL, rsc, NULL, mon, pcmk__ar_ordered |pcmk__ar_unrunnable_first_blocks, rsc->cluster); } } } /*! * \internal * \brief Cancel a recurring action if running on a node * * \param[in,out] rsc Resource that action is for * \param[in] node Node to cancel action on * \param[in] key Operation key for action * \param[in] name Action name * \param[in] interval_ms Action interval (in milliseconds) */ static void cancel_if_running(pcmk_resource_t *rsc, const pcmk_node_t *node, const char *key, const char *name, guint interval_ms) { GList *possible_matches = find_actions_exact(rsc->actions, key, node); pcmk_action_t *cancel_op = NULL; if (possible_matches == NULL) { return; // Recurring action isn't running on this node } g_list_free(possible_matches); cancel_op = pcmk__new_cancel_action(rsc, name, interval_ms, node); switch (rsc->next_role) { case pcmk_role_started: case pcmk_role_unpromoted: /* Order starts after cancel. If the current role is * stopped, this cancels the monitor before the resource * starts; if the current role is started, then this cancels * the monitor on a migration target before starting there. */ pcmk__new_ordering(rsc, NULL, cancel_op, rsc, start_key(rsc), NULL, pcmk__ar_unrunnable_first_blocks, rsc->cluster); break; default: break; } pcmk__rsc_info(rsc, "Cancelling %s-interval %s action for %s on %s because " "configured for " PCMK__ROLE_STOPPED " role (not %s)", pcmk__readable_interval(interval_ms), name, rsc->id, pcmk__node_name(node), pcmk_role_text(rsc->next_role)); } /*! * \internal * \brief Order an action after all probes of a resource on a node * * \param[in,out] rsc Resource to check for probes * \param[in] node Node to check for probes of \p rsc * \param[in,out] action Action to order after probes of \p rsc on \p node */ static void order_after_probes(pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_action_t *action) { GList *probes = pe__resource_actions(rsc, node, PCMK_ACTION_MONITOR, FALSE); for (GList *iter = probes; iter != NULL; iter = iter->next) { order_actions((pcmk_action_t *) iter->data, action, pcmk__ar_unrunnable_first_blocks); } g_list_free(probes); } /*! * \internal * \brief Order an action after all stops of a resource on a node * * \param[in,out] rsc Resource to check for stops * \param[in] node Node to check for stops of \p rsc * \param[in,out] action Action to order after stops of \p rsc on \p node */ static void order_after_stops(pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_action_t *action) { GList *stop_ops = pe__resource_actions(rsc, node, PCMK_ACTION_STOP, TRUE); for (GList *iter = stop_ops; iter != NULL; iter = iter->next) { pcmk_action_t *stop = (pcmk_action_t *) iter->data; if (!pcmk_is_set(stop->flags, pcmk_action_optional) && !pcmk_is_set(action->flags, pcmk_action_optional) && !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__rsc_trace(rsc, "%s optional on %s: unmanaged", action->uuid, pcmk__node_name(node)); pcmk__set_action_flags(action, pcmk_action_optional); } if (!pcmk_is_set(stop->flags, pcmk_action_runnable)) { crm_debug("%s unrunnable on %s: stop is unrunnable", action->uuid, pcmk__node_name(node)); pcmk__clear_action_flags(action, pcmk_action_runnable); } if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__new_ordering(rsc, stop_key(rsc), stop, NULL, NULL, action, pcmk__ar_first_implies_then |pcmk__ar_unrunnable_first_blocks, rsc->cluster); } } g_list_free(stop_ops); } /*! * \internal * \brief Create recurring action from resource history entry for inactive role * * \param[in,out] rsc Resource that resource history is for * \param[in] node Node that resource will be active on (if any) * \param[in] op Resource history entry */ static void recurring_op_for_inactive(pcmk_resource_t *rsc, const pcmk_node_t *node, const struct op_history *op) { GList *possible_matches = NULL; // We're only interested in recurring actions for the inactive role if (op->role != pcmk_role_stopped) { return; } if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique)) { crm_notice("Ignoring %s (recurring monitors for " PCMK__ROLE_STOPPED " role are not supported for anonymous clones)", op->id); return; // @TODO add support } pcmk__rsc_trace(rsc, "Creating recurring action %s for %s on nodes " "where it should not be running", op->id, rsc->id); for (GList *iter = rsc->cluster->nodes; iter != NULL; iter = iter->next) { pcmk_node_t *stop_node = (pcmk_node_t *) iter->data; bool is_optional = true; pcmk_action_t *stopped_mon = NULL; // Cancel action on node where resource will be active if ((node != NULL) && pcmk__str_eq(stop_node->details->uname, node->details->uname, pcmk__str_casei)) { cancel_if_running(rsc, node, op->key, op->name, op->interval_ms); continue; } // Recurring action on this node is optional if it's already active here possible_matches = find_actions_exact(rsc->actions, op->key, stop_node); is_optional = (possible_matches != NULL); g_list_free(possible_matches); pcmk__rsc_trace(rsc, "Creating %s recurring action %s for %s (%s " PCMK__ROLE_STOPPED " on %s)", (is_optional? "optional" : "mandatory"), op->key, op->id, rsc->id, pcmk__node_name(stop_node)); stopped_mon = custom_action(rsc, strdup(op->key), op->name, stop_node, is_optional, rsc->cluster); pe__add_action_expected_result(stopped_mon, CRM_EX_NOT_RUNNING); if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { order_after_probes(rsc, stop_node, stopped_mon); } /* The recurring action is for the inactive role, so it shouldn't be * performed until the resource is inactive. */ order_after_stops(rsc, stop_node, stopped_mon); if (!stop_node->details->online || stop_node->details->unclean) { pcmk__rsc_debug(rsc, "%s unrunnable on %s: node unavailable)", stopped_mon->uuid, pcmk__node_name(stop_node)); pcmk__clear_action_flags(stopped_mon, pcmk_action_runnable); } if (pcmk_is_set(stopped_mon->flags, pcmk_action_runnable) && !pcmk_is_set(stopped_mon->flags, pcmk_action_optional)) { crm_notice("Start recurring %s-interval %s for " PCMK__ROLE_STOPPED " %s on %s", pcmk__readable_interval(op->interval_ms), stopped_mon->task, rsc->id, pcmk__node_name(stop_node)); } } } /*! * \internal * \brief Create recurring actions for a resource * * \param[in,out] rsc Resource to create recurring actions for */ void pcmk__create_recurring_actions(pcmk_resource_t *rsc) { pcmk_action_t *start = NULL; if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) { pcmk__rsc_trace(rsc, "Skipping recurring actions for blocked resource %s", rsc->id); return; } if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) { pcmk__rsc_trace(rsc, "Skipping recurring actions for %s " "in maintenance mode", rsc->id); return; } if (rsc->allocated_to == NULL) { // Recurring actions for active roles not needed } else if (rsc->allocated_to->details->maintenance) { pcmk__rsc_trace(rsc, "Skipping recurring actions for %s on %s " "in maintenance mode", rsc->id, pcmk__node_name(rsc->allocated_to)); } else if ((rsc->next_role != pcmk_role_stopped) || !pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { // Recurring actions for active roles needed start = start_action(rsc, rsc->allocated_to, TRUE); } pcmk__rsc_trace(rsc, "Creating any recurring actions needed for %s", rsc->id); for (xmlNode *op = first_named_child(rsc->ops_xml, PCMK_XE_OP); op != NULL; op = crm_next_same_xml(op)) { struct op_history op_history = { NULL, }; if (!is_recurring_history(rsc, op, &op_history)) { continue; } if (start != NULL) { recurring_op_for_active(rsc, start, rsc->allocated_to, &op_history); } recurring_op_for_inactive(rsc, rsc->allocated_to, &op_history); free(op_history.key); } } /*! * \internal * \brief Create an executor cancel action * * \param[in,out] rsc Resource of action to cancel * \param[in] task Name of action to cancel * \param[in] interval_ms Interval of action to cancel * \param[in] node Node of action to cancel * * \return Created op */ pcmk_action_t * pcmk__new_cancel_action(pcmk_resource_t *rsc, const char *task, guint interval_ms, const pcmk_node_t *node) { pcmk_action_t *cancel_op = NULL; char *key = NULL; char *interval_ms_s = NULL; CRM_ASSERT((rsc != NULL) && (task != NULL) && (node != NULL)); // @TODO dangerous if possible to schedule another action with this key key = pcmk__op_key(rsc->id, task, interval_ms); cancel_op = custom_action(rsc, key, PCMK_ACTION_CANCEL, node, FALSE, rsc->cluster); pcmk__str_update(&cancel_op->task, PCMK_ACTION_CANCEL); pcmk__str_update(&cancel_op->cancel_task, task); interval_ms_s = crm_strdup_printf("%u", interval_ms); - add_hash_param(cancel_op->meta, PCMK_XA_OPERATION, task); - add_hash_param(cancel_op->meta, PCMK_META_INTERVAL, interval_ms_s); + pcmk__insert_meta(cancel_op, PCMK_XA_OPERATION, task); + pcmk__insert_meta(cancel_op, PCMK_META_INTERVAL, interval_ms_s); free(interval_ms_s); return cancel_op; } /*! * \internal * \brief Schedule cancellation of a recurring action * * \param[in,out] rsc Resource that action is for * \param[in] call_id Action's call ID from history * \param[in] task Action name * \param[in] interval_ms Action interval * \param[in] node Node that history entry is for * \param[in] reason Short description of why action is cancelled */ void pcmk__schedule_cancel(pcmk_resource_t *rsc, const char *call_id, const char *task, guint interval_ms, const pcmk_node_t *node, const char *reason) { pcmk_action_t *cancel = NULL; CRM_CHECK((rsc != NULL) && (task != NULL) && (node != NULL) && (reason != NULL), return); crm_info("Recurring %s-interval %s for %s will be stopped on %s: %s", pcmk__readable_interval(interval_ms), task, rsc->id, pcmk__node_name(node), reason); cancel = pcmk__new_cancel_action(rsc, task, interval_ms, node); - add_hash_param(cancel->meta, PCMK__XA_CALL_ID, call_id); + pcmk__insert_meta(cancel, PCMK__XA_CALL_ID, call_id); // Cancellations happen after stops pcmk__new_ordering(rsc, stop_key(rsc), NULL, rsc, NULL, cancel, pcmk__ar_ordered, rsc->cluster); } /*! * \internal * \brief Create a recurring action marked as needing rescheduling if active * * \param[in,out] rsc Resource that action is for * \param[in] task Name of action being rescheduled * \param[in] interval_ms Action interval (in milliseconds) * \param[in,out] node Node where action should be rescheduled */ void pcmk__reschedule_recurring(pcmk_resource_t *rsc, const char *task, guint interval_ms, pcmk_node_t *node) { pcmk_action_t *op = NULL; trigger_unfencing(rsc, node, "Device parameters changed (reschedule)", NULL, rsc->cluster); op = custom_action(rsc, pcmk__op_key(rsc->id, task, interval_ms), task, node, TRUE, rsc->cluster); pcmk__set_action_flags(op, pcmk_action_reschedule); } /*! * \internal * \brief Check whether an action is recurring * * \param[in] action Action to check * * \return true if \p action has a nonzero interval, otherwise false */ bool pcmk__action_is_recurring(const pcmk_action_t *action) { guint interval_ms = 0; if (pcmk__guint_from_hash(action->meta, PCMK_META_INTERVAL, 0, &interval_ms) != pcmk_rc_ok) { return false; } return (interval_ms > 0); } diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index b53e5a15e8..e719179792 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1,2255 +1,2255 @@ /* * 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 #include #include enum pe__bundle_mount_flags { pe__bundle_mount_none = 0x00, // mount instance-specific subdirectory rather than source directly pe__bundle_mount_subdir = 0x01 }; typedef struct { char *source; char *target; char *options; uint32_t flags; // bitmask of pe__bundle_mount_flags } pe__bundle_mount_t; typedef struct { char *source; char *target; } pe__bundle_port_t; enum pe__container_agent { PE__CONTAINER_AGENT_UNKNOWN, PE__CONTAINER_AGENT_DOCKER, PE__CONTAINER_AGENT_RKT, PE__CONTAINER_AGENT_PODMAN, }; #define PE__CONTAINER_AGENT_UNKNOWN_S "unknown" #define PE__CONTAINER_AGENT_DOCKER_S "docker" #define PE__CONTAINER_AGENT_RKT_S "rkt" #define PE__CONTAINER_AGENT_PODMAN_S "podman" typedef struct pe__bundle_variant_data_s { int promoted_max; int nreplicas; int nreplicas_per_host; char *prefix; char *image; const char *ip_last; char *host_network; char *host_netmask; char *control_port; char *container_network; char *ip_range_start; gboolean add_host; gchar *container_host_options; char *container_command; char *launcher_options; const char *attribute_target; pcmk_resource_t *child; GList *replicas; // pcmk__bundle_replica_t * GList *ports; // pe__bundle_port_t * GList *mounts; // pe__bundle_mount_t * enum pe__container_agent agent_type; } pe__bundle_variant_data_t; #define get_bundle_variant_data(data, rsc) \ CRM_ASSERT(rsc != NULL); \ CRM_ASSERT(rsc->variant == pcmk_rsc_variant_bundle); \ CRM_ASSERT(rsc->variant_opaque != NULL); \ data = (pe__bundle_variant_data_t *) rsc->variant_opaque; /*! * \internal * \brief Get maximum number of bundle replicas allowed to run * * \param[in] rsc Bundle or bundled resource to check * * \return Maximum replicas for bundle corresponding to \p rsc */ int pe__bundle_max(const pcmk_resource_t *rsc) { const pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); return bundle_data->nreplicas; } /*! * \internal * \brief Get the resource inside a bundle * * \param[in] bundle Bundle to check * * \return Resource inside \p bundle if any, otherwise NULL */ pcmk_resource_t * pe__bundled_resource(const pcmk_resource_t *rsc) { const pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); return bundle_data->child; } /*! * \internal * \brief Get containerized resource corresponding to a given bundle container * * \param[in] instance Collective instance that might be a bundle container * * \return Bundled resource instance inside \p instance if it is a bundle * container instance, otherwise NULL */ const pcmk_resource_t * pe__get_rsc_in_container(const pcmk_resource_t *instance) { const pe__bundle_variant_data_t *data = NULL; const pcmk_resource_t *top = pe__const_top_resource(instance, true); if ((top == NULL) || (top->variant != pcmk_rsc_variant_bundle)) { return NULL; } get_bundle_variant_data(data, top); for (const GList *iter = data->replicas; iter != NULL; iter = iter->next) { const pcmk__bundle_replica_t *replica = iter->data; if (instance == replica->container) { return replica->child; } } return NULL; } /*! * \internal * \brief Check whether a given node is created by a bundle * * \param[in] bundle Bundle resource to check * \param[in] node Node to check * * \return true if \p node is an instance of \p bundle, otherwise false */ bool pe__node_is_bundle_instance(const pcmk_resource_t *bundle, const pcmk_node_t *node) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, bundle); for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) { pcmk__bundle_replica_t *replica = iter->data; if (pcmk__same_node(node, replica->node)) { return true; } } return false; } /*! * \internal * \brief Get the container of a bundle's first replica * * \param[in] bundle Bundle resource to get container for * * \return Container resource from first replica of \p bundle if any, * otherwise NULL */ pcmk_resource_t * pe__first_container(const pcmk_resource_t *bundle) { const pe__bundle_variant_data_t *bundle_data = NULL; const pcmk__bundle_replica_t *replica = NULL; get_bundle_variant_data(bundle_data, bundle); if (bundle_data->replicas == NULL) { return NULL; } replica = bundle_data->replicas->data; return replica->container; } /*! * \internal * \brief Iterate over bundle replicas * * \param[in,out] bundle Bundle to iterate over * \param[in] fn Function to call for each replica (its return value * indicates whether to continue iterating) * \param[in,out] user_data Pointer to pass to \p fn */ void pe__foreach_bundle_replica(pcmk_resource_t *bundle, bool (*fn)(pcmk__bundle_replica_t *, void *), void *user_data) { const pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, bundle); for (GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) { if (!fn((pcmk__bundle_replica_t *) iter->data, user_data)) { break; } } } /*! * \internal * \brief Iterate over const bundle replicas * * \param[in] bundle Bundle to iterate over * \param[in] fn Function to call for each replica (its return value * indicates whether to continue iterating) * \param[in,out] user_data Pointer to pass to \p fn */ void pe__foreach_const_bundle_replica(const pcmk_resource_t *bundle, bool (*fn)(const pcmk__bundle_replica_t *, void *), void *user_data) { const pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, bundle); for (const GList *iter = bundle_data->replicas; iter != NULL; iter = iter->next) { if (!fn((const pcmk__bundle_replica_t *) iter->data, user_data)) { break; } } } static char * next_ip(const char *last_ip) { unsigned int oct1 = 0; unsigned int oct2 = 0; unsigned int oct3 = 0; unsigned int oct4 = 0; int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4); if (rc != 4) { /*@ TODO check for IPv6 */ return NULL; } else if (oct3 > 253) { return NULL; } else if (oct4 > 253) { ++oct3; oct4 = 1; } else { ++oct4; } return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4); } static void allocate_ip(pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica, GString *buffer) { if(data->ip_range_start == NULL) { return; } else if(data->ip_last) { replica->ipaddr = next_ip(data->ip_last); } else { replica->ipaddr = strdup(data->ip_range_start); } data->ip_last = replica->ipaddr; switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: if (data->add_host) { g_string_append_printf(buffer, " --add-host=%s-%d:%s", data->prefix, replica->offset, replica->ipaddr); } else { g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); } break; case PE__CONTAINER_AGENT_RKT: g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); break; default: // PE__CONTAINER_AGENT_UNKNOWN break; } } static xmlNode * create_resource(const char *name, const char *provider, const char *kind) { xmlNode *rsc = create_xml_node(NULL, PCMK_XE_PRIMITIVE); crm_xml_add(rsc, PCMK_XA_ID, name); crm_xml_add(rsc, PCMK_XA_CLASS, PCMK_RESOURCE_CLASS_OCF); crm_xml_add(rsc, PCMK_XA_PROVIDER, provider); crm_xml_add(rsc, PCMK_XA_TYPE, kind); return rsc; } /*! * \internal * \brief Check whether cluster can manage resource inside container * * \param[in,out] data Container variant data * * \return TRUE if networking configuration is acceptable, FALSE otherwise * * \note The resource is manageable if an IP range or control port has been * specified. If a control port is used without an IP range, replicas per * host must be 1. */ static bool valid_network(pe__bundle_variant_data_t *data) { if(data->ip_range_start) { return TRUE; } if(data->control_port) { if(data->nreplicas_per_host > 1) { pcmk__config_err("Specifying the '" PCMK_XA_CONTROL_PORT "' for %s " "requires '" PCMK_XA_REPLICAS_PER_HOST "=1'", data->prefix); data->nreplicas_per_host = 1; // @TODO to be sure: // pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique); } return TRUE; } return FALSE; } static int create_ip_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica) { if(data->ip_range_start) { char *id = NULL; xmlNode *xml_ip = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr); crm_xml_sanitize_id(id); xml_ip = create_resource(id, "heartbeat", "IPaddr2"); free(id); xml_obj = create_xml_node(xml_ip, PCMK_XE_INSTANCE_ATTRIBUTES); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr); if(data->host_network) { crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network); } if(data->host_netmask) { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", data->host_netmask); } else { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32"); } xml_obj = create_xml_node(xml_ip, PCMK_XE_OPERATIONS); crm_create_op_xml(xml_obj, pcmk__xe_id(xml_ip), PCMK_ACTION_MONITOR, "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (pe__unpack_resource(xml_ip, &replica->ip, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } parent->children = g_list_append(parent->children, replica->ip); } return pcmk_rc_ok; } static const char* container_agent_str(enum pe__container_agent t) { switch (t) { case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S; case PE__CONTAINER_AGENT_RKT: return PE__CONTAINER_AGENT_RKT_S; case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S; default: // PE__CONTAINER_AGENT_UNKNOWN break; } return PE__CONTAINER_AGENT_UNKNOWN_S; } static int create_container_resource(pcmk_resource_t *parent, const pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica) { char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; // Agent-specific const char *hostname_opt = NULL; const char *env_opt = NULL; const char *agent_str = NULL; int volid = 0; // rkt-only GString *buffer = NULL; GString *dbuffer = NULL; // Where syntax differences are drop-in replacements, set them now switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: hostname_opt = "-h "; env_opt = "-e "; break; case PE__CONTAINER_AGENT_RKT: hostname_opt = "--hostname="; env_opt = "--environment="; break; default: // PE__CONTAINER_AGENT_UNKNOWN return pcmk_rc_unpack_error; } agent_str = container_agent_str(data->agent_type); buffer = g_string_sized_new(4096); id = crm_strdup_printf("%s-%s-%d", data->prefix, agent_str, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", agent_str); free(id); xml_obj = create_xml_node(xml_container, PCMK_XE_INSTANCE_ATTRIBUTES); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", PCMK_VALUE_TRUE); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", PCMK_VALUE_FALSE); crm_create_nvpair_xml(xml_obj, NULL, "reuse", PCMK_VALUE_FALSE); if (data->agent_type == PE__CONTAINER_AGENT_DOCKER) { g_string_append(buffer, " --restart=no"); } /* Set a container hostname only if we have an IP to map it to. The user can * set -h or --uts=host themselves if they want a nicer name for logs, but * this makes applications happy who need their hostname to match the IP * they bind to. */ if (data->ip_range_start != NULL) { g_string_append_printf(buffer, " %s%s-%d", hostname_opt, data->prefix, replica->offset); } pcmk__g_strcat(buffer, " ", env_opt, "PCMK_stderr=1", NULL); if (data->container_network != NULL) { pcmk__g_strcat(buffer, " --net=", data->container_network, NULL); } if (data->control_port != NULL) { pcmk__g_strcat(buffer, " ", env_opt, "PCMK_" PCMK__ENV_REMOTE_PORT "=", data->control_port, NULL); } else { g_string_append_printf(buffer, " %sPCMK_" PCMK__ENV_REMOTE_PORT "=%d", env_opt, DEFAULT_REMOTE_PORT); } for (GList *iter = data->mounts; iter != NULL; iter = iter->next) { pe__bundle_mount_t *mount = (pe__bundle_mount_t *) iter->data; char *source = NULL; if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) { source = crm_strdup_printf("%s/%s-%d", mount->source, data->prefix, replica->offset); pcmk__add_separated_word(&dbuffer, 1024, source, ","); } switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: pcmk__g_strcat(buffer, " -v ", pcmk__s(source, mount->source), ":", mount->target, NULL); if (mount->options != NULL) { pcmk__g_strcat(buffer, ":", mount->options, NULL); } break; case PE__CONTAINER_AGENT_RKT: g_string_append_printf(buffer, " --volume vol%d,kind=host," "source=%s%s%s " "--mount volume=vol%d,target=%s", volid, pcmk__s(source, mount->source), (mount->options != NULL)? "," : "", pcmk__s(mount->options, ""), volid, mount->target); volid++; break; default: break; } free(source); } for (GList *iter = data->ports; iter != NULL; iter = iter->next) { pe__bundle_port_t *port = (pe__bundle_port_t *) iter->data; switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: if (replica->ipaddr != NULL) { pcmk__g_strcat(buffer, " -p ", replica->ipaddr, ":", port->source, ":", port->target, NULL); } else if (!pcmk__str_eq(data->container_network, PCMK_VALUE_HOST, pcmk__str_none)) { // No need to do port mapping if net == host pcmk__g_strcat(buffer, " -p ", port->source, ":", port->target, NULL); } break; case PE__CONTAINER_AGENT_RKT: if (replica->ipaddr != NULL) { pcmk__g_strcat(buffer, " --port=", port->target, ":", replica->ipaddr, ":", port->source, NULL); } else { pcmk__g_strcat(buffer, " --port=", port->target, ":", port->source, NULL); } break; default: break; } } /* @COMPAT: We should use pcmk__add_word() here, but we can't yet, because * it would cause restarts during rolling upgrades. * * In a previous version of the container resource creation logic, if * data->launcher_options is not NULL, we append * (" %s", data->launcher_options) even if data->launcher_options is an * empty string. Likewise for data->container_host_options. Using * * pcmk__add_word(buffer, 0, data->launcher_options) * * removes that extra trailing space, causing a resource definition change. */ if (data->launcher_options != NULL) { pcmk__g_strcat(buffer, " ", data->launcher_options, NULL); } if (data->container_host_options != NULL) { pcmk__g_strcat(buffer, " ", data->container_host_options, NULL); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", (const char *) buffer->str); g_string_free(buffer, TRUE); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", (dbuffer != NULL)? (const char *) dbuffer->str : ""); if (dbuffer != NULL) { g_string_free(dbuffer, TRUE); } if (replica->child != NULL) { if (data->container_command != NULL) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } else { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", SBIN_DIR "/pacemaker-remoted"); } /* TODO: Allow users to specify their own? * * We just want to know if the container is alive; we'll monitor the * child independently. */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); #if 0 /* @TODO Consider supporting the use case where we can start and stop * resources, but not proxy local commands (such as setting node * attributes), by running the local executor in stand-alone mode. * However, this would probably be better done via ACLs as with other * Pacemaker Remote nodes. */ } else if ((child != NULL) && data->untrusted) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", CRM_DAEMON_DIR "/pacemaker-execd"); crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke"); #endif } else { if (data->container_command != NULL) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } /* TODO: Allow users to specify their own? * * We don't know what's in the container, so we just want to know if it * is alive. */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); } xml_obj = create_xml_node(xml_container, PCMK_XE_OPERATIONS); crm_create_op_xml(xml_obj, pcmk__xe_id(xml_container), PCMK_ACTION_MONITOR, "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (pe__unpack_resource(xml_container, &replica->container, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } pcmk__set_rsc_flags(replica->container, pcmk_rsc_replica_container); parent->children = g_list_append(parent->children, replica->container); return pcmk_rc_ok; } /*! * \brief Ban a node from a resource's (and its children's) allowed nodes list * * \param[in,out] rsc Resource to modify * \param[in] uname Name of node to ban */ static void disallow_node(pcmk_resource_t *rsc, const char *uname) { gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname); if (match) { ((pcmk_node_t *) match)->weight = -INFINITY; ((pcmk_node_t *) match)->rsc_discover_mode = pcmk_probe_never; } if (rsc->children) { g_list_foreach(rsc->children, (GFunc) disallow_node, (gpointer) uname); } } static int create_remote_resource(pcmk_resource_t *parent, pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica) { if (replica->child && valid_network(data)) { GHashTableIter gIter; pcmk_node_t *node = NULL; xmlNode *xml_remote = NULL; char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset); char *port_s = NULL; const char *uname = NULL; const char *connect_name = NULL; if (pe_find_resource(parent->cluster->resources, id) != NULL) { free(id); // The biggest hammer we have id = crm_strdup_printf("pcmk-internal-%s-remote-%d", replica->child->id, replica->offset); //@TODO return error instead of asserting? CRM_ASSERT(pe_find_resource(parent->cluster->resources, id) == NULL); } /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the * connection does not have its own IP is a magic string that we use to * support nested remotes (i.e. a bundle running on a remote node). */ connect_name = (replica->ipaddr? replica->ipaddr : "#uname"); if (data->control_port == NULL) { port_s = pcmk__itoa(DEFAULT_REMOTE_PORT); } /* This sets replica->container as replica->remote's container, which is * similar to what happens with guest nodes. This is how the scheduler * knows that the bundle node is fenced by recovering the container, and * that remote should be ordered relative to the container. */ xml_remote = pe_create_remote_xml(NULL, id, replica->container->id, NULL, NULL, NULL, connect_name, (data->control_port? data->control_port : port_s)); free(port_s); /* Abandon our created ID, and pull the copy from the XML, because we * need something that will get freed during scheduler data cleanup to * use as the node ID and uname. */ free(id); id = NULL; uname = pcmk__xe_id(xml_remote); /* Ensure a node has been created for the guest (it may have already * been, if it has a permanent node attribute), and ensure its weight is * -INFINITY so no other resources can run on it. */ node = pe_find_node(parent->cluster->nodes, uname); if (node == NULL) { node = pe_create_node(uname, uname, PCMK_VALUE_REMOTE, CRM_MINUS_INFINITY_S, parent->cluster); } else { node->weight = -INFINITY; } node->rsc_discover_mode = pcmk_probe_never; /* unpack_remote_nodes() ensures that each remote node and guest node * has a pcmk_node_t entry. Ideally, it would do the same for bundle * nodes. Unfortunately, a bundle has to be mostly unpacked before it's * obvious what nodes will be needed, so we do it just above. * * Worse, that means that the node may have been utilized while * unpacking other resources, without our weight correction. The most * likely place for this to happen is when pe__unpack_resource() calls * resource_location() to set a default score in symmetric clusters. * This adds a node *copy* to each resource's allowed nodes, and these * copies will have the wrong weight. * * As a hacky workaround, fix those copies here. * * @TODO Possible alternative: ensure bundles are unpacked before other * resources, so the weight is correct before any copies are made. */ g_list_foreach(parent->cluster->resources, (GFunc) disallow_node, (gpointer) uname); replica->node = pe__copy_node(node); replica->node->weight = 500; replica->node->rsc_discover_mode = pcmk_probe_exclusive; /* Ensure the node shows up as allowed and with the correct discovery set */ if (replica->child->allowed_nodes != NULL) { g_hash_table_destroy(replica->child->allowed_nodes); } replica->child->allowed_nodes = pcmk__strkey_table(NULL, free); g_hash_table_insert(replica->child->allowed_nodes, (gpointer) replica->node->details->id, pe__copy_node(replica->node)); { pcmk_node_t *copy = pe__copy_node(replica->node); copy->weight = -INFINITY; g_hash_table_insert(replica->child->parent->allowed_nodes, (gpointer) replica->node->details->id, copy); } if (pe__unpack_resource(xml_remote, &replica->remote, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) { if (pe__is_guest_or_remote_node(node)) { /* Remote resources can only run on 'normal' cluster node */ node->weight = -INFINITY; } } replica->node->details->remote_rsc = replica->remote; // Ensure pe__is_guest_node() functions correctly immediately replica->remote->container = replica->container; /* A bundle's #kind is closer to "container" (guest node) than the * "remote" set by pe_create_node(). */ pcmk__insert_dup(replica->node->details->attrs, CRM_ATTR_KIND, "container"); /* One effect of this is that setup_container() will add * replica->remote to replica->container's fillers, which will make * pe__resource_contains_guest_node() true for replica->container. * * replica->child does NOT get added to replica->container's fillers. * The only noticeable effect if it did would be for its fail count to * be taken into account when checking replica->container's migration * threshold. */ parent->children = g_list_append(parent->children, replica->remote); } return pcmk_rc_ok; } static int create_replica_resources(pcmk_resource_t *parent, pe__bundle_variant_data_t *data, pcmk__bundle_replica_t *replica) { int rc = pcmk_rc_ok; rc = create_container_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } rc = create_ip_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } rc = create_remote_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } if ((replica->child != NULL) && (replica->ipaddr != NULL)) { - add_hash_param(replica->child->meta, "external-ip", replica->ipaddr); + pcmk__insert_meta(replica->child, "external-ip", replica->ipaddr); } if (replica->remote != NULL) { /* * Allow the remote connection resource to be allocated to a * different node than the one on which the container is active. * * This makes it possible to have Pacemaker Remote nodes running * containers with pacemaker-remoted inside in order to start * services inside those containers. */ pcmk__set_rsc_flags(replica->remote, pcmk_rsc_remote_nesting_allowed); } return rc; } static void mount_add(pe__bundle_variant_data_t *bundle_data, const char *source, const char *target, const char *options, uint32_t flags) { pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t)); CRM_ASSERT(mount != NULL); mount->source = strdup(source); mount->target = strdup(target); pcmk__str_update(&mount->options, options); mount->flags = flags; bundle_data->mounts = g_list_append(bundle_data->mounts, mount); } static void mount_free(pe__bundle_mount_t *mount) { free(mount->source); free(mount->target); free(mount->options); free(mount); } static void port_free(pe__bundle_port_t *port) { free(port->source); free(port->target); free(port); } static pcmk__bundle_replica_t * replica_for_remote(pcmk_resource_t *remote) { pcmk_resource_t *top = remote; pe__bundle_variant_data_t *bundle_data = NULL; if (top == NULL) { return NULL; } while (top->parent != NULL) { top = top->parent; } get_bundle_variant_data(bundle_data, top); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; if (replica->remote == remote) { return replica; } } CRM_LOG_ASSERT(FALSE); return NULL; } bool pe__bundle_needs_remote_name(pcmk_resource_t *rsc) { const char *value; GHashTable *params = NULL; if (rsc == NULL) { return false; } // Use NULL node since pcmk__bundle_expand() uses that to set value params = pe_rsc_params(rsc, NULL, rsc->cluster); value = g_hash_table_lookup(params, PCMK_REMOTE_RA_ADDR); return pcmk__str_eq(value, "#uname", pcmk__str_casei) && xml_contains_remote_node(rsc->xml); } const char * pe__add_bundle_remote_name(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler, xmlNode *xml, const char *field) { // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside pcmk_node_t *node = NULL; pcmk__bundle_replica_t *replica = NULL; if (!pe__bundle_needs_remote_name(rsc)) { return NULL; } replica = replica_for_remote(rsc); if (replica == NULL) { return NULL; } node = replica->container->allocated_to; if (node == NULL) { /* If it won't be running anywhere after the * transition, go with where it's running now. */ node = pcmk__current_node(replica->container); } if(node == NULL) { crm_trace("Cannot determine address for bundle connection %s", rsc->id); return NULL; } crm_trace("Setting address for bundle connection %s to bundle host %s", rsc->id, pcmk__node_name(node)); if(xml != NULL && field != NULL) { crm_xml_add(xml, field, node->details->uname); } return node->details->uname; } #define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do { \ flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Bundle mount", pcmk__xe_id(mount_xml), \ flags, (flags_to_set), #flags_to_set); \ } while (0) gboolean pe__unpack_bundle(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { const char *value = NULL; xmlNode *xml_obj = NULL; const xmlNode *xml_child = NULL; xmlNode *xml_resource = NULL; pe__bundle_variant_data_t *bundle_data = NULL; bool need_log_mount = TRUE; CRM_ASSERT(rsc != NULL); pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id); bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t)); rsc->variant_opaque = bundle_data; bundle_data->prefix = strdup(rsc->id); xml_obj = first_named_child(rsc->xml, PCMK_XE_DOCKER); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER; } else { xml_obj = first_named_child(rsc->xml, PCMK__XE_RKT); if (xml_obj != NULL) { pcmk__warn_once(pcmk__wo_rkt, "Support for " PCMK__XE_RKT " in bundles " "(such as %s) is deprecated and will be " "removed in a future release", rsc->id); bundle_data->agent_type = PE__CONTAINER_AGENT_RKT; } else { xml_obj = first_named_child(rsc->xml, PCMK_XE_PODMAN); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN; } else { return FALSE; } } } // Use 0 for default, minimum, and invalid PCMK_XA_PROMOTED_MAX value = crm_element_value(xml_obj, PCMK_XA_PROMOTED_MAX); if (value == NULL) { // @COMPAT deprecated since 2.0.0 value = crm_element_value(xml_obj, PCMK__XA_PROMOTED_MAX_LEGACY); } pcmk__scan_min_int(value, &bundle_data->promoted_max, 0); /* Default replicas to PCMK_XA_PROMOTED_MAX if it was specified and 1 * otherwise */ value = crm_element_value(xml_obj, PCMK_XA_REPLICAS); if ((value == NULL) && (bundle_data->promoted_max > 0)) { bundle_data->nreplicas = bundle_data->promoted_max; } else { pcmk__scan_min_int(value, &bundle_data->nreplicas, 1); } /* * Communication between containers on the same host via the * floating IPs only works if the container is started with: * --userland-proxy=false --ip-masq=false */ value = crm_element_value(xml_obj, PCMK_XA_REPLICAS_PER_HOST); pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1); if (bundle_data->nreplicas_per_host == 1) { pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique); } bundle_data->container_command = crm_element_value_copy(xml_obj, PCMK_XA_RUN_COMMAND); bundle_data->launcher_options = crm_element_value_copy(xml_obj, PCMK_XA_OPTIONS); bundle_data->image = crm_element_value_copy(xml_obj, PCMK_XA_IMAGE); bundle_data->container_network = crm_element_value_copy(xml_obj, PCMK_XA_NETWORK); xml_obj = first_named_child(rsc->xml, PCMK_XE_NETWORK); if(xml_obj) { bundle_data->ip_range_start = crm_element_value_copy(xml_obj, PCMK_XA_IP_RANGE_START); bundle_data->host_netmask = crm_element_value_copy(xml_obj, PCMK_XA_HOST_NETMASK); bundle_data->host_network = crm_element_value_copy(xml_obj, PCMK_XA_HOST_INTERFACE); bundle_data->control_port = crm_element_value_copy(xml_obj, PCMK_XA_CONTROL_PORT); value = crm_element_value(xml_obj, PCMK_XA_ADD_HOST); if (crm_str_to_boolean(value, &bundle_data->add_host) != 1) { bundle_data->add_host = TRUE; } for (xml_child = first_named_child(xml_obj, PCMK_XE_PORT_MAPPING); xml_child != NULL; xml_child = crm_next_same_xml(xml_child)) { pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t)); port->source = crm_element_value_copy(xml_child, PCMK_XA_PORT); if(port->source == NULL) { port->source = crm_element_value_copy(xml_child, PCMK_XA_RANGE); } else { port->target = crm_element_value_copy(xml_child, PCMK_XA_INTERNAL_PORT); } if(port->source != NULL && strlen(port->source) > 0) { if(port->target == NULL) { port->target = strdup(port->source); } bundle_data->ports = g_list_append(bundle_data->ports, port); } else { pcmk__config_err("Invalid " PCMK_XA_PORT " directive %s", pcmk__xe_id(xml_child)); port_free(port); } } } xml_obj = first_named_child(rsc->xml, PCMK_XE_STORAGE); for (xml_child = first_named_child(xml_obj, PCMK_XE_STORAGE_MAPPING); xml_child != NULL; xml_child = crm_next_same_xml(xml_child)) { const char *source = crm_element_value(xml_child, PCMK_XA_SOURCE_DIR); const char *target = crm_element_value(xml_child, PCMK_XA_TARGET_DIR); const char *options = crm_element_value(xml_child, PCMK_XA_OPTIONS); int flags = pe__bundle_mount_none; if (source == NULL) { source = crm_element_value(xml_child, PCMK_XA_SOURCE_DIR_ROOT); pe__set_bundle_mount_flags(xml_child, flags, pe__bundle_mount_subdir); } if (source && target) { mount_add(bundle_data, source, target, options, flags); if (strcmp(target, "/var/log") == 0) { need_log_mount = FALSE; } } else { pcmk__config_err("Invalid mount directive %s", pcmk__xe_id(xml_child)); } } xml_obj = first_named_child(rsc->xml, PCMK_XE_PRIMITIVE); if (xml_obj && valid_network(bundle_data)) { char *value = NULL; xmlNode *xml_set = NULL; xml_resource = create_xml_node(NULL, PCMK_XE_CLONE); /* @COMPAT We no longer use the tag, but we need to keep it as * part of the resource name, so that bundles don't restart in a rolling * upgrade. (It also avoids needing to change regression tests.) */ crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix, (bundle_data->promoted_max? "master" : (const char *)xml_resource->name)); xml_set = create_xml_node(xml_resource, PCMK_XE_META_ATTRIBUTES); crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name); crm_create_nvpair_xml(xml_set, NULL, PCMK_META_ORDERED, PCMK_VALUE_TRUE); value = pcmk__itoa(bundle_data->nreplicas); crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_MAX, value); free(value); value = pcmk__itoa(bundle_data->nreplicas_per_host); crm_create_nvpair_xml(xml_set, NULL, PCMK_META_CLONE_NODE_MAX, value); free(value); crm_create_nvpair_xml(xml_set, NULL, PCMK_META_GLOBALLY_UNIQUE, pcmk__btoa(bundle_data->nreplicas_per_host > 1)); if (bundle_data->promoted_max) { crm_create_nvpair_xml(xml_set, NULL, PCMK_META_PROMOTABLE, PCMK_VALUE_TRUE); value = pcmk__itoa(bundle_data->promoted_max); crm_create_nvpair_xml(xml_set, NULL, PCMK_META_PROMOTED_MAX, value); free(value); } //crm_xml_add(xml_obj, PCMK_XA_ID, bundle_data->prefix); add_node_copy(xml_resource, xml_obj); } else if(xml_obj) { pcmk__config_err("Cannot control %s inside %s without either " PCMK_XA_IP_RANGE_START " or " PCMK_XA_CONTROL_PORT, rsc->id, pcmk__xe_id(xml_obj)); return FALSE; } if(xml_resource) { int lpc = 0; GList *childIter = NULL; pe__bundle_port_t *port = NULL; GString *buffer = NULL; if (pe__unpack_resource(xml_resource, &(bundle_data->child), rsc, scheduler) != pcmk_rc_ok) { return FALSE; } /* Currently, we always map the default authentication key location * into the same location inside the container. * * Ideally, we would respect the host's PCMK_authkey_location, but: * - it may be different on different nodes; * - the actual connection will do extra checking to make sure the key * file exists and is readable, that we can't do here on the DC * - tools such as crm_resource and crm_simulate may not have the same * environment variables as the cluster, causing operation digests to * differ * * Always using the default location inside the container is fine, * because we control the pacemaker_remote environment, and it avoids * having to pass another environment variable to the container. * * @TODO A better solution may be to have only pacemaker_remote use the * environment variable, and have the cluster nodes use a new * cluster option for key location. This would introduce the limitation * of the location being the same on all cluster nodes, but that's * reasonable. */ mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION, DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none); if (need_log_mount) { mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL, pe__bundle_mount_subdir); } port = calloc(1, sizeof(pe__bundle_port_t)); if(bundle_data->control_port) { port->source = strdup(bundle_data->control_port); } else { /* If we wanted to respect PCMK_remote_port, we could use * crm_default_remote_port() here and elsewhere in this file instead * of DEFAULT_REMOTE_PORT. * * However, it gains nothing, since we control both the container * environment and the connection resource parameters, and the user * can use a different port if desired by setting * PCMK_XA_CONTROL_PORT. */ port->source = pcmk__itoa(DEFAULT_REMOTE_PORT); } port->target = strdup(port->source); bundle_data->ports = g_list_append(bundle_data->ports, port); buffer = g_string_sized_new(1024); for (childIter = bundle_data->child->children; childIter != NULL; childIter = childIter->next) { pcmk__bundle_replica_t *replica = NULL; replica = calloc(1, sizeof(pcmk__bundle_replica_t)); replica->child = childIter->data; replica->child->exclusive_discover = TRUE; replica->offset = lpc++; // Ensure the child's notify gets set based on the underlying primitive's value if (pcmk_is_set(replica->child->flags, pcmk_rsc_notify)) { pcmk__set_rsc_flags(bundle_data->child, pcmk_rsc_notify); } allocate_ip(bundle_data, replica, buffer); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); bundle_data->attribute_target = g_hash_table_lookup(replica->child->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET); } bundle_data->container_host_options = g_string_free(buffer, FALSE); if (bundle_data->attribute_target) { pcmk__insert_dup(rsc->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET, bundle_data->attribute_target); pcmk__insert_dup(bundle_data->child->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET, bundle_data->attribute_target); } } else { // Just a naked container, no pacemaker-remote GString *buffer = g_string_sized_new(1024); for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) { pcmk__bundle_replica_t *replica = NULL; replica = calloc(1, sizeof(pcmk__bundle_replica_t)); replica->offset = lpc; allocate_ip(bundle_data, replica, buffer); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); } bundle_data->container_host_options = g_string_free(buffer, FALSE); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; if (create_replica_resources(rsc, bundle_data, replica) != pcmk_rc_ok) { pcmk__config_err("Failed unpacking resource %s", rsc->id); rsc->fns->free(rsc); return FALSE; } /* Utilization needs special handling for bundles. It makes no sense for * the inner primitive to have utilization, because it is tied * one-to-one to the guest node created by the container resource -- and * there's no way to set capacities for that guest node anyway. * * What the user really wants is to configure utilization for the * container. However, the schema only allows utilization for * primitives, and the container resource is implicit anyway, so the * user can *only* configure utilization for the inner primitive. If * they do, move the primitive's utilization values to the container. * * @TODO This means that bundles without an inner primitive can't have * utilization. An alternative might be to allow utilization values in * the top-level bundle XML in the schema, and copy those to each * container. */ if (replica->child != NULL) { GHashTable *empty = replica->container->utilization; replica->container->utilization = replica->child->utilization; replica->child->utilization = empty; } } if (bundle_data->child) { rsc->children = g_list_append(rsc->children, bundle_data->child); } return TRUE; } static int replica_resource_active(pcmk_resource_t *rsc, gboolean all) { if (rsc) { gboolean child_active = rsc->fns->active(rsc, all); if (child_active && !all) { return TRUE; } else if (!child_active && all) { return FALSE; } } return -1; } gboolean pe__bundle_active(pcmk_resource_t *rsc, gboolean all) { pe__bundle_variant_data_t *bundle_data = NULL; GList *iter = NULL; get_bundle_variant_data(bundle_data, rsc); for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) { pcmk__bundle_replica_t *replica = iter->data; int rsc_active; rsc_active = replica_resource_active(replica->ip, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->child, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->container, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->remote, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } } /* If "all" is TRUE, we've already checked that no resources were inactive, * so return TRUE; if "all" is FALSE, we didn't find any active resources, * so return FALSE. */ return all; } /*! * \internal * \brief Find the bundle replica corresponding to a given node * * \param[in] bundle Top-level bundle resource * \param[in] node Node to search for * * \return Bundle replica if found, NULL otherwise */ pcmk_resource_t * pe__find_bundle_replica(const pcmk_resource_t *bundle, const pcmk_node_t *node) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_ASSERT(bundle && node); get_bundle_variant_data(bundle_data, bundle); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica && replica->node); if (pcmk__same_node(replica->node, node)) { return replica->child; } } return NULL; } /*! * \internal * \deprecated This function will be removed in a future release */ static void print_rsc_in_list(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { if (rsc != NULL) { if (options & pe_print_html) { status_print("
  • "); } rsc->fns->print(rsc, pre_text, options, print_data); if (options & pe_print_html) { status_print("
  • \n"); } } } /*! * \internal * \deprecated This function will be removed in a future release */ static void bundle_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (pre_text == NULL) { pre_text = ""; } child_text = crm_strdup_printf("%s ", pre_text); get_bundle_variant_data(bundle_data, rsc); status_print("%sid); status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type)); status_print("image=\"%s\" ", bundle_data->image); status_print("unique=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_unique)); status_print("managed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_managed)); status_print("failed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_failed)); status_print(">\n"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); status_print("%s \n", pre_text, replica->offset); print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); status_print("%s \n", pre_text); } status_print("%s\n", pre_text); free(child_text); } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__bundle_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean printed_header = FALSE; gboolean print_everything = TRUE; const char *desc = NULL; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; char *id = NULL; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) { continue; } if (!printed_header) { const char *type = container_agent_str(bundle_data->agent_type); const char *unique = pcmk__flag_text(rsc->flags, pcmk_rsc_unique); const char *maintenance = pcmk__flag_text(rsc->flags, pcmk_rsc_maintenance); const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed); const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed); printed_header = TRUE; desc = pe__resource_description(rsc, show_opts); rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_BUNDLE, 8, PCMK_XA_ID, rsc->id, PCMK_XA_TYPE, type, PCMK_XA_IMAGE, bundle_data->image, PCMK_XA_UNIQUE, unique, PCMK_XA_MAINTENANCE, maintenance, PCMK_XA_MANAGED, managed, PCMK_XA_FAILED, failed, PCMK_XA_DESCRIPTION, desc); CRM_ASSERT(rc == pcmk_rc_ok); } id = pcmk__itoa(replica->offset); rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_REPLICA, 1, PCMK_XA_ID, id); free(id); CRM_ASSERT(rc == pcmk_rc_ok); if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), show_opts, replica->remote, only_node, only_rsc); } pcmk__output_xml_pop_parent(out); // replica } if (printed_header) { pcmk__output_xml_pop_parent(out); // bundle } return rc; } static void pe__bundle_replica_output_html(pcmk__output_t *out, pcmk__bundle_replica_t *replica, pcmk_node_t *node, uint32_t show_opts) { pcmk_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } pe__common_output_html(out, rsc, buffer, node, show_opts); } /*! * \internal * \brief Get a string describing a resource's unmanaged state or lack thereof * * \param[in] rsc Resource to describe * * \return A string indicating that a resource is in maintenance mode or * otherwise unmanaged, or an empty string otherwise */ static const char * get_unmanaged_str(const pcmk_resource_t *rsc) { if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) { return " (maintenance)"; } if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { return " (unmanaged)"; } return ""; } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__bundle_html(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); desc = pe__resource_description(rsc, show_opts); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { /* The text output messages used below require pe_print_implicit to * be set to do anything. */ uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); if (pcmk__list_of_multiple(bundle_data->replicas)) { out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset); } if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), new_show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), new_show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), new_show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), new_show_opts, replica->remote, only_node, only_rsc); } if (pcmk__list_of_multiple(bundle_data->replicas)) { out->end_list(out); } } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { continue; } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); pe__bundle_replica_output_html(out, replica, pcmk__current_node(replica->container), show_opts); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } static void pe__bundle_replica_output_text(pcmk__output_t *out, pcmk__bundle_replica_t *replica, pcmk_node_t *node, uint32_t show_opts) { const pcmk_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } pe__common_output_text(out, rsc, buffer, node, show_opts); } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__bundle_text(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; desc = pe__resource_description(rsc, show_opts); get_bundle_variant_data(bundle_data, rsc); CRM_ASSERT(rsc != NULL); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { /* The text output messages used below require pe_print_implicit to * be set to do anything. */ uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); if (pcmk__list_of_multiple(bundle_data->replicas)) { out->list_item(out, NULL, "Replica[%d]", replica->offset); } out->begin_list(out, NULL, NULL, NULL); if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), new_show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), new_show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), new_show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), new_show_opts, replica->remote, only_node, only_rsc); } out->end_list(out); } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { continue; } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); pe__bundle_replica_output_text(out, replica, pcmk__current_node(replica->container), show_opts); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } /*! * \internal * \deprecated This function will be removed in a future release */ static void print_bundle_replica(pcmk__bundle_replica_t *replica, const char *pre_text, long options, void *print_data) { pcmk_node_t *node = NULL; pcmk_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } node = pcmk__current_node(replica->container); common_print(rsc, pre_text, buffer, node, options, print_data); } /*! * \internal * \deprecated This function will be removed in a future release */ void pe__print_bundle(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (options & pe_print_xml) { bundle_print_xml(rsc, pre_text, options, print_data); return; } get_bundle_variant_data(bundle_data, rsc); if (pre_text == NULL) { pre_text = " "; } status_print("%sContainer bundle%s: %s [%s]%s%s\n", pre_text, ((bundle_data->nreplicas > 1)? " set" : ""), rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : " (unmanaged)"); if (options & pe_print_html) { status_print("
    \n
      \n"); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (options & pe_print_html) { status_print("
    • "); } if (pcmk_is_set(options, pe_print_implicit)) { child_text = crm_strdup_printf(" %s", pre_text); if (pcmk__list_of_multiple(bundle_data->replicas)) { status_print(" %sReplica[%d]\n", pre_text, replica->offset); } if (options & pe_print_html) { status_print("
      \n
        \n"); } print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); if (options & pe_print_html) { status_print("
      \n"); } } else { child_text = crm_strdup_printf("%s ", pre_text); print_bundle_replica(replica, child_text, options, print_data); } free(child_text); if (options & pe_print_html) { status_print("
    • \n"); } } if (options & pe_print_html) { status_print("
    \n"); } } static void free_bundle_replica(pcmk__bundle_replica_t *replica) { if (replica == NULL) { return; } if (replica->node) { free(replica->node); replica->node = NULL; } if (replica->ip) { free_xml(replica->ip->xml); replica->ip->xml = NULL; replica->ip->fns->free(replica->ip); replica->ip = NULL; } if (replica->container) { free_xml(replica->container->xml); replica->container->xml = NULL; replica->container->fns->free(replica->container); replica->container = NULL; } if (replica->remote) { free_xml(replica->remote->xml); replica->remote->xml = NULL; replica->remote->fns->free(replica->remote); replica->remote = NULL; } free(replica->ipaddr); free(replica); } void pe__free_bundle(pcmk_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); pcmk__rsc_trace(rsc, "Freeing %s", rsc->id); free(bundle_data->prefix); free(bundle_data->image); free(bundle_data->control_port); free(bundle_data->host_network); free(bundle_data->host_netmask); free(bundle_data->ip_range_start); free(bundle_data->container_network); free(bundle_data->launcher_options); free(bundle_data->container_command); g_free(bundle_data->container_host_options); g_list_free_full(bundle_data->replicas, (GDestroyNotify) free_bundle_replica); g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free); g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free); g_list_free(rsc->children); if(bundle_data->child) { free_xml(bundle_data->child->xml); bundle_data->child->xml = NULL; bundle_data->child->fns->free(bundle_data->child); } common_free(rsc); } enum rsc_role_e pe__bundle_resource_state(const pcmk_resource_t *rsc, gboolean current) { enum rsc_role_e container_role = pcmk_role_unknown; return container_role; } /*! * \brief Get the number of configured replicas in a bundle * * \param[in] rsc Bundle resource * * \return Number of configured replicas, or 0 on error */ int pe_bundle_replicas(const pcmk_resource_t *rsc) { if ((rsc == NULL) || (rsc->variant != pcmk_rsc_variant_bundle)) { return 0; } else { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); return bundle_data->nreplicas; } } void pe__count_bundle(pcmk_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); for (GList *item = bundle_data->replicas; item != NULL; item = item->next) { pcmk__bundle_replica_t *replica = item->data; if (replica->ip) { replica->ip->fns->count(replica->ip); } if (replica->child) { replica->child->fns->count(replica->child); } if (replica->container) { replica->container->fns->count(replica->container); } if (replica->remote) { replica->remote->fns->count(replica->remote); } } } gboolean pe__bundle_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc, gboolean check_parent) { gboolean passes = FALSE; pe__bundle_variant_data_t *bundle_data = NULL; if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else { get_bundle_variant_data(bundle_data, rsc); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pcmk__bundle_replica_t *replica = gIter->data; if (replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, FALSE)) { passes = TRUE; break; } else if (replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, FALSE)) { passes = TRUE; break; } else if (!replica->container->fns->is_filtered(replica->container, only_rsc, FALSE)) { passes = TRUE; break; } else if (replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, FALSE)) { passes = TRUE; break; } } } return !passes; } /*! * \internal * \brief Get a list of a bundle's containers * * \param[in] bundle Bundle resource * * \return Newly created list of \p bundle's containers * \note It is the caller's responsibility to free the result with * g_list_free(). */ GList * pe__bundle_containers(const pcmk_resource_t *bundle) { GList *containers = NULL; const pe__bundle_variant_data_t *data = NULL; get_bundle_variant_data(data, bundle); for (GList *iter = data->replicas; iter != NULL; iter = iter->next) { pcmk__bundle_replica_t *replica = iter->data; containers = g_list_append(containers, replica->container); } return containers; } // Bundle implementation of pcmk_rsc_methods_t:active_node() pcmk_node_t * pe__bundle_active_node(const pcmk_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean) { pcmk_node_t *active = NULL; pcmk_node_t *node = NULL; pcmk_resource_t *container = NULL; GList *containers = NULL; GList *iter = NULL; GHashTable *nodes = NULL; const pe__bundle_variant_data_t *data = NULL; if (count_all != NULL) { *count_all = 0; } if (count_clean != NULL) { *count_clean = 0; } if (rsc == NULL) { return NULL; } /* For the purposes of this method, we only care about where the bundle's * containers are active, so build a list of active containers. */ get_bundle_variant_data(data, rsc); for (iter = data->replicas; iter != NULL; iter = iter->next) { pcmk__bundle_replica_t *replica = iter->data; if (replica->container->running_on != NULL) { containers = g_list_append(containers, replica->container); } } if (containers == NULL) { return NULL; } /* If the bundle has only a single active container, just use that * container's method. If live migration is ever supported for bundle * containers, this will allow us to prefer the migration source when there * is only one container and it is migrating. For now, this just lets us * avoid creating the nodes table. */ if (pcmk__list_of_1(containers)) { container = containers->data; node = container->fns->active_node(container, count_all, count_clean); g_list_free(containers); return node; } // Add all containers' active nodes to a hash table (for uniqueness) nodes = g_hash_table_new(NULL, NULL); for (iter = containers; iter != NULL; iter = iter->next) { container = iter->data; for (GList *node_iter = container->running_on; node_iter != NULL; node_iter = node_iter->next) { node = node_iter->data; // If insert returns true, we haven't counted this node yet if (g_hash_table_insert(nodes, (gpointer) node->details, (gpointer) node) && !pe__count_active_node(rsc, node, &active, count_all, count_clean)) { goto done; } } } done: g_list_free(containers); g_hash_table_destroy(nodes); return active; } /*! * \internal * \brief Get maximum bundle resource instances per node * * \param[in] rsc Bundle resource to check * * \return Maximum number of \p rsc instances that can be active on one node */ unsigned int pe__bundle_max_per_node(const pcmk_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); CRM_ASSERT(bundle_data->nreplicas_per_host >= 0); return (unsigned int) bundle_data->nreplicas_per_host; } diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index cb2f4af8be..54c3bb09c7 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -1,1540 +1,1540 @@ /* * 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 #include #include #ifdef PCMK__COMPAT_2_0 #define PROMOTED_INSTANCES PCMK__ROLE_PROMOTED_LEGACY "s" #define UNPROMOTED_INSTANCES PCMK__ROLE_UNPROMOTED_LEGACY "s" #else #define PROMOTED_INSTANCES PCMK__ROLE_PROMOTED #define UNPROMOTED_INSTANCES PCMK__ROLE_UNPROMOTED #endif typedef struct clone_variant_data_s { int clone_max; int clone_node_max; int promoted_max; int promoted_node_max; int total_clones; uint32_t flags; // Group of enum pcmk__clone_flags notify_data_t *stop_notify; notify_data_t *start_notify; notify_data_t *demote_notify; notify_data_t *promote_notify; xmlNode *xml_obj_child; } clone_variant_data_t; #define get_clone_variant_data(data, rsc) \ CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_clone)); \ data = (clone_variant_data_t *) rsc->variant_opaque; /*! * \internal * \brief Return the maximum number of clone instances allowed to be run * * \param[in] clone Clone or clone instance to check * * \return Maximum instances for \p clone */ int pe__clone_max(const pcmk_resource_t *clone) { const clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->clone_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed per node * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum allowed instances per node for \p clone */ int pe__clone_node_max(const pcmk_resource_t *clone) { const clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->clone_node_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed to be promoted * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum promoted instances for \p clone */ int pe__clone_promoted_max(const pcmk_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->promoted_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed to be promoted * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum promoted instances for \p clone */ int pe__clone_promoted_node_max(const pcmk_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->promoted_node_max; } static GList * sorted_hash_table_values(GHashTable *table) { GList *retval = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) { retval = g_list_prepend(retval, (char *) value); } } retval = g_list_sort(retval, (GCompareFunc) strcmp); return retval; } static GList * nodes_with_status(GHashTable *table, const char *status) { GList *retval = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!strcmp((char *) value, status)) { retval = g_list_prepend(retval, key); } } retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp); return retval; } static GString * node_list_to_str(const GList *list) { GString *retval = NULL; for (const GList *iter = list; iter != NULL; iter = iter->next) { pcmk__add_word(&retval, 1024, (const char *) iter->data); } return retval; } static void clone_header(pcmk__output_t *out, int *rc, const pcmk_resource_t *rsc, clone_variant_data_t *clone_data, const char *desc) { GString *attrs = NULL; if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) { pcmk__add_separated_word(&attrs, 64, "promotable", ", "); } if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) { pcmk__add_separated_word(&attrs, 64, "unique", ", "); } if (pe__resource_is_disabled(rsc)) { pcmk__add_separated_word(&attrs, 64, "disabled", ", "); } if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) { pcmk__add_separated_word(&attrs, 64, "maintenance", ", "); } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__add_separated_word(&attrs, 64, "unmanaged", ", "); } if (attrs != NULL) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s", rsc->id, pcmk__xe_id(clone_data->xml_obj_child), (const char *) attrs->str, desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); g_string_free(attrs, TRUE); } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s", rsc->id, pcmk__xe_id(clone_data->xml_obj_child), desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); } } void pe__force_anon(const char *standard, pcmk_resource_t *rsc, const char *rid, pcmk_scheduler_t *scheduler) { if (pcmk__is_clone(rsc)) { clone_variant_data_t *clone_data = rsc->variant_opaque; pcmk__config_warn("Ignoring " PCMK_META_GLOBALLY_UNIQUE " for %s " "because %s resources such as %s can be used only as " "anonymous clones", rsc->id, standard, rid); clone_data->clone_node_max = 1; clone_data->clone_max = QB_MIN(clone_data->clone_max, g_list_length(scheduler->nodes)); } } pcmk_resource_t * find_clone_instance(const pcmk_resource_t *rsc, const char *sub_id) { char *child_id = NULL; pcmk_resource_t *child = NULL; const char *child_base = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); child_base = pcmk__xe_id(clone_data->xml_obj_child); child_id = crm_strdup_printf("%s:%s", child_base, sub_id); child = pe_find_resource(rsc->children, child_id); free(child_id); return child; } pcmk_resource_t * pe__create_clone_child(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { gboolean as_orphan = FALSE; char *inc_num = NULL; char *inc_max = NULL; pcmk_resource_t *child_rsc = NULL; xmlNode *child_copy = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE); if (clone_data->total_clones >= clone_data->clone_max) { // If we've already used all available instances, this is an orphan as_orphan = TRUE; } // Allocate instance numbers in numerical order (starting at 0) inc_num = pcmk__itoa(clone_data->total_clones); inc_max = pcmk__itoa(clone_data->clone_max); child_copy = copy_xml(clone_data->xml_obj_child); crm_xml_add(child_copy, PCMK__META_CLONE, inc_num); if (pe__unpack_resource(child_copy, &child_rsc, rsc, scheduler) != pcmk_rc_ok) { goto bail; } /* child_rsc->globally_unique = rsc->globally_unique; */ CRM_ASSERT(child_rsc); clone_data->total_clones += 1; pcmk__rsc_trace(child_rsc, "Setting clone attributes for: %s", child_rsc->id); rsc->children = g_list_append(rsc->children, child_rsc); if (as_orphan) { pe__set_resource_flags_recursive(child_rsc, pcmk_rsc_removed); } - add_hash_param(child_rsc->meta, PCMK_META_CLONE_MAX, inc_max); + pcmk__insert_meta(child_rsc, PCMK_META_CLONE_MAX, inc_max); pcmk__rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id); bail: free(inc_num); free(inc_max); return child_rsc; } /*! * \internal * \brief Unpack a nonnegative integer value from a resource meta-attribute * * \param[in] rsc Resource with meta-attribute * \param[in] meta_name Name of meta-attribute to unpack * \param[in] deprecated_name If not NULL, try unpacking this * if \p meta_name is unset * \param[in] default_value Value to use if unset * * \return Integer parsed from resource's specified meta-attribute if a valid * nonnegative integer, \p default_value if unset, or 0 if invalid */ static int unpack_meta_int(const pcmk_resource_t *rsc, const char *meta_name, const char *deprecated_name, int default_value) { int integer = default_value; const char *value = g_hash_table_lookup(rsc->meta, meta_name); if ((value == NULL) && (deprecated_name != NULL)) { value = g_hash_table_lookup(rsc->meta, deprecated_name); } if (value != NULL) { pcmk__scan_min_int(value, &integer, 0); } return integer; } gboolean clone_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { int lpc = 0; xmlNode *a_child = NULL; xmlNode *xml_obj = rsc->xml; clone_variant_data_t *clone_data = NULL; pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id); clone_data = calloc(1, sizeof(clone_variant_data_t)); rsc->variant_opaque = clone_data; if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) { // Use 1 as default but 0 for minimum and invalid // @COMPAT PCMK__META_PROMOTED_MAX_LEGACY deprecated since 2.0.0 clone_data->promoted_max = unpack_meta_int(rsc, PCMK_META_PROMOTED_MAX, PCMK__META_PROMOTED_MAX_LEGACY, 1); // Use 1 as default but 0 for minimum and invalid // @COMPAT PCMK__META_PROMOTED_NODE_MAX_LEGACY deprecated since 2.0.0 clone_data->promoted_node_max = unpack_meta_int(rsc, PCMK_META_PROMOTED_NODE_MAX, PCMK__META_PROMOTED_NODE_MAX_LEGACY, 1); } // Implied by calloc() /* clone_data->xml_obj_child = NULL; */ // Use 1 as default but 0 for minimum and invalid clone_data->clone_node_max = unpack_meta_int(rsc, PCMK_META_CLONE_NODE_MAX, NULL, 1); /* Use number of nodes (but always at least 1, which is handy for crm_verify * for a CIB without nodes) as default, but 0 for minimum and invalid */ clone_data->clone_max = unpack_meta_int(rsc, PCMK_META_CLONE_MAX, NULL, QB_MAX(1, g_list_length(scheduler->nodes))); if (crm_is_true(g_hash_table_lookup(rsc->meta, PCMK_META_ORDERED))) { clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Clone", rsc->id, clone_data->flags, pcmk__clone_ordered, "pcmk__clone_ordered"); } if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique) && (clone_data->clone_node_max > 1)) { pcmk__config_err("Ignoring " PCMK_META_CLONE_NODE_MAX " of %d for %s " "because anonymous clones support only one instance " "per node", clone_data->clone_node_max, rsc->id); clone_data->clone_node_max = 1; } pcmk__rsc_trace(rsc, "Options for %s", rsc->id); pcmk__rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max); pcmk__rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max); pcmk__rsc_trace(rsc, "\tClone is unique: %s", pcmk__flag_text(rsc->flags, pcmk_rsc_unique)); pcmk__rsc_trace(rsc, "\tClone is promotable: %s", pcmk__flag_text(rsc->flags, pcmk_rsc_promotable)); // Clones may contain a single group or primitive for (a_child = pcmk__xe_first_child(xml_obj); a_child != NULL; a_child = pcmk__xe_next(a_child)) { if (pcmk__str_any_of((const char *) a_child->name, PCMK_XE_PRIMITIVE, PCMK_XE_GROUP, NULL)) { clone_data->xml_obj_child = a_child; break; } } if (clone_data->xml_obj_child == NULL) { pcmk__config_err("%s has nothing to clone", rsc->id); return FALSE; } /* * Make clones ever so slightly sticky by default * * This helps ensure clone instances are not shuffled around the cluster * for no benefit in situations when pre-allocation is not appropriate */ if (g_hash_table_lookup(rsc->meta, PCMK_META_RESOURCE_STICKINESS) == NULL) { - add_hash_param(rsc->meta, PCMK_META_RESOURCE_STICKINESS, "1"); + pcmk__insert_meta(rsc, PCMK_META_RESOURCE_STICKINESS, "1"); } /* This ensures that the PCMK_META_GLOBALLY_UNIQUE value always exists for * children to inherit when being unpacked, as well as in resource agents' * environment. */ - add_hash_param(rsc->meta, PCMK_META_GLOBALLY_UNIQUE, - pcmk__flag_text(rsc->flags, pcmk_rsc_unique)); + pcmk__insert_meta(rsc, PCMK_META_GLOBALLY_UNIQUE, + pcmk__flag_text(rsc->flags, pcmk_rsc_unique)); if (clone_data->clone_max <= 0) { /* Create one child instance so that unpack_find_resource() will hook up * any orphans up to the parent correctly. */ if (pe__create_clone_child(rsc, scheduler) == NULL) { return FALSE; } } else { // Create a child instance for each available instance number for (lpc = 0; lpc < clone_data->clone_max; lpc++) { if (pe__create_clone_child(rsc, scheduler) == NULL) { return FALSE; } } } pcmk__rsc_trace(rsc, "Added %d children to resource %s...", clone_data->clone_max, rsc->id); return TRUE; } gboolean clone_active(pcmk_resource_t * rsc, gboolean all) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; gboolean child_active = child_rsc->fns->active(child_rsc, all); if (all == FALSE && child_active) { return TRUE; } else if (all && child_active == FALSE) { return FALSE; } } if (all) { return TRUE; } else { return FALSE; } } /*! * \internal * \deprecated This function will be removed in a future release */ static void short_print(const char *list, const char *prefix, const char *type, const char *suffix, long options, void *print_data) { if(suffix == NULL) { suffix = ""; } if (!pcmk__str_empty(list)) { if (options & pe_print_html) { status_print("
  • "); } status_print("%s%s: [ %s ]%s", prefix, type, list, suffix); if (options & pe_print_html) { status_print("
  • \n"); } else if (options & pe_print_suppres_nl) { /* nothing */ } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("\n"); } } } static const char * configured_role_str(pcmk_resource_t * rsc) { const char *target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); if ((target_role == NULL) && rsc->children && rsc->children->data) { pcmk_resource_t *instance = rsc->children->data; // Any instance will do target_role = g_hash_table_lookup(instance->meta, PCMK_META_TARGET_ROLE); } return target_role; } static enum rsc_role_e configured_role(pcmk_resource_t *rsc) { enum rsc_role_e role = pcmk_role_unknown; const char *target_role = configured_role_str(rsc); if (target_role != NULL) { role = pcmk_parse_role(target_role); if (role == pcmk_role_unknown) { pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE " for resource %s", rsc->id); } } return role; } /*! * \internal * \deprecated This function will be removed in a future release */ static void clone_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { char *child_text = crm_strdup_printf("%s ", pre_text); const char *target_role = configured_role_str(rsc); GList *gIter = rsc->children; status_print("%sid); status_print("multi_state=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_promotable)); status_print("unique=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_unique)); status_print("managed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_managed)); status_print("failed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_failed)); status_print("failure_ignored=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure)); if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; child_rsc->fns->print(child_rsc, child_text, options, print_data); } status_print("%s\n", pre_text); free(child_text); } bool is_set_recursive(const pcmk_resource_t *rsc, long long flag, bool any) { GList *gIter; bool all = !any; if (pcmk_is_set(rsc->flags, flag)) { if(any) { return TRUE; } } else if(all) { return FALSE; } for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { if(is_set_recursive(gIter->data, flag, any)) { if(any) { return TRUE; } } else if(all) { return FALSE; } } if(all) { return TRUE; } return FALSE; } /*! * \internal * \deprecated This function will be removed in a future release */ void clone_print(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { GString *list_text = NULL; char *child_text = NULL; GString *stopped_list = NULL; GList *promoted_list = NULL; GList *started_list = NULL; GList *gIter = rsc->children; clone_variant_data_t *clone_data = NULL; int active_instances = 0; if (pre_text == NULL) { pre_text = " "; } if (options & pe_print_xml) { clone_print_xml(rsc, pre_text, options, print_data); return; } get_clone_variant_data(clone_data, rsc); child_text = crm_strdup_printf("%s ", pre_text); status_print("%sClone Set: %s [%s]%s%s%s", pcmk__s(pre_text, ""), rsc->id, pcmk__xe_id(clone_data->xml_obj_child), pcmk_is_set(rsc->flags, pcmk_rsc_promotable)? " (promotable)" : "", pcmk_is_set(rsc->flags, pcmk_rsc_unique)? " (unique)" : "", pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : " (unmanaged)"); if (options & pe_print_html) { status_print("\n
      \n"); } else if ((options & pe_print_log) == 0) { status_print("\n"); } for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (options & pe_print_clone_details) { print_full = TRUE; } if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || !pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (pcmk_is_set(options, pe_print_pending) && (child_rsc->pending_task != NULL) && strcmp(child_rsc->pending_task, "probe")) { // Print individual instance when non-probe action is pending print_full = TRUE; } else if (partially_active == FALSE) { // List stopped instances when requested (except orphans) if (!pcmk_is_set(child_rsc->flags, pcmk_rsc_removed) && !pcmk_is_set(options, pe_print_clone_active)) { pcmk__add_word(&stopped_list, 1024, child_rsc->id); } } else if (is_set_recursive(child_rsc, pcmk_rsc_removed, TRUE) || !is_set_recursive(child_rsc, pcmk_rsc_managed, FALSE) || is_set_recursive(child_rsc, pcmk_rsc_failed, TRUE)) { // Print individual instance when active orphaned/unmanaged/failed print_full = TRUE; } else if (child_rsc->fns->active(child_rsc, TRUE)) { // Instance of fully active anonymous clone pcmk_node_t *location = NULL; location = child_rsc->fns->location(child_rsc, NULL, TRUE); if (location) { // Instance is active on a single node enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE); if (location->details->online == FALSE && location->details->unclean) { print_full = TRUE; } else if (a_role > pcmk_role_unpromoted) { promoted_list = g_list_append(promoted_list, location); } else { started_list = g_list_append(started_list, location); } } else { /* uncolocated group - bleh */ print_full = TRUE; } } else { // Instance of partially active anonymous clone print_full = TRUE; } if (print_full) { if (options & pe_print_html) { status_print("
    • \n"); } child_rsc->fns->print(child_rsc, child_text, options, print_data); if (options & pe_print_html) { status_print("
    • \n"); } } } /* Promoted */ promoted_list = g_list_sort(promoted_list, pe__cmp_node_name); for (gIter = promoted_list; gIter; gIter = gIter->next) { pcmk_node_t *host = gIter->data; pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } if (list_text != NULL) { short_print((const char *) list_text->str, child_text, PROMOTED_INSTANCES, NULL, options, print_data); g_string_truncate(list_text, 0); } g_list_free(promoted_list); /* Started/Unpromoted */ started_list = g_list_sort(started_list, pe__cmp_node_name); for (gIter = started_list; gIter; gIter = gIter->next) { pcmk_node_t *host = gIter->data; pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } if (list_text != NULL) { if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if (role == pcmk_role_unpromoted) { short_print((const char *) list_text->str, child_text, UNPROMOTED_INSTANCES " (" PCMK_META_TARGET_ROLE ")", NULL, options, print_data); } else { short_print((const char *) list_text->str, child_text, UNPROMOTED_INSTANCES, NULL, options, print_data); } } else { short_print((const char *) list_text->str, child_text, "Started", NULL, options, print_data); } } g_list_free(started_list); if (!pcmk_is_set(options, pe_print_clone_active)) { const char *state = "Stopped"; enum rsc_role_e role = configured_role(rsc); if (role == pcmk_role_stopped) { state = "Stopped (disabled)"; } if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique) && (clone_data->clone_max > active_instances)) { GList *nIter; GList *list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped list for non-unique clones */ if (stopped_list != NULL) { g_string_truncate(stopped_list, 0); } if (list == NULL) { /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't * calculated allowed_nodes yet. If we've not probed for them * yet, the Stopped list will be empty. */ list = g_hash_table_get_values(rsc->known_on); } list = g_list_sort(list, pe__cmp_node_name); for (nIter = list; nIter != NULL; nIter = nIter->next) { pcmk_node_t *node = (pcmk_node_t *) nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL) { pcmk__add_word(&stopped_list, 1024, node->details->uname); } } g_list_free(list); } if (stopped_list != NULL) { short_print((const char *) stopped_list->str, child_text, state, NULL, options, print_data); } } if (options & pe_print_html) { status_print("
    \n"); } if (list_text != NULL) { g_string_free(list_text, TRUE); } if (stopped_list != NULL) { g_string_free(stopped_list, TRUE); } free(child_text); } PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__clone_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); GList *gIter = rsc->children; GList *all = NULL; int rc = pcmk_rc_no_output; gboolean printed_header = FALSE; gboolean print_everything = TRUE; if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); all = g_list_prepend(all, (gpointer) "*"); for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) { continue; } if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) { continue; } if (!printed_header) { const char *multi_state = pcmk__flag_text(rsc->flags, pcmk_rsc_promotable); const char *unique = pcmk__flag_text(rsc->flags, pcmk_rsc_unique); const char *maintenance = pcmk__flag_text(rsc->flags, pcmk_rsc_maintenance); const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed); const char *disabled = pcmk__btoa(pe__resource_is_disabled(rsc)); const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed); const char *ignored = pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure); const char *target_role = configured_role_str(rsc); const char *desc = pe__resource_description(rsc, show_opts); printed_header = TRUE; rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_CLONE, 10, PCMK_XA_ID, rsc->id, PCMK_XA_MULTI_STATE, multi_state, PCMK_XA_UNIQUE, unique, PCMK_XA_MAINTENANCE, maintenance, PCMK_XA_MANAGED, managed, PCMK_XA_DISABLED, disabled, PCMK_XA_FAILED, failed, PCMK_XA_FAILURE_IGNORED, ignored, PCMK_XA_TARGET_ROLE, target_role, PCMK_XA_DESCRIPTION, desc); CRM_ASSERT(rc == pcmk_rc_ok); } out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, all); } if (printed_header) { pcmk__output_xml_pop_parent(out); } g_list_free(all); return rc; } PCMK__OUTPUT_ARGS("clone", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__clone_default(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); GHashTable *stopped = NULL; GString *list_text = NULL; GList *promoted_list = NULL; GList *started_list = NULL; GList *gIter = rsc->children; const char *desc = NULL; clone_variant_data_t *clone_data = NULL; int active_instances = 0; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; desc = pe__resource_description(rsc, show_opts); get_clone_variant_data(clone_data, rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) { continue; } if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) { continue; } if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) { print_full = TRUE; } if (pcmk_is_set(rsc->flags, pcmk_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || !pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (pcmk_is_set(show_opts, pcmk_show_pending) && (child_rsc->pending_task != NULL) && strcmp(child_rsc->pending_task, "probe")) { // Print individual instance when non-probe action is pending print_full = TRUE; } else if (partially_active == FALSE) { // List stopped instances when requested (except orphans) if (!pcmk_is_set(child_rsc->flags, pcmk_rsc_removed) && !pcmk_is_set(show_opts, pcmk_show_clone_detail) && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { if (stopped == NULL) { stopped = pcmk__strkey_table(free, free); } pcmk__insert_dup(stopped, child_rsc->id, "Stopped"); } } else if (is_set_recursive(child_rsc, pcmk_rsc_removed, TRUE) || !is_set_recursive(child_rsc, pcmk_rsc_managed, FALSE) || is_set_recursive(child_rsc, pcmk_rsc_failed, TRUE)) { // Print individual instance when active orphaned/unmanaged/failed print_full = TRUE; } else if (child_rsc->fns->active(child_rsc, TRUE)) { // Instance of fully active anonymous clone pcmk_node_t *location = NULL; location = child_rsc->fns->location(child_rsc, NULL, TRUE); if (location) { // Instance is active on a single node enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE); if (location->details->online == FALSE && location->details->unclean) { print_full = TRUE; } else if (a_role > pcmk_role_unpromoted) { promoted_list = g_list_append(promoted_list, location); } else { started_list = g_list_append(started_list, location); } } else { /* uncolocated group - bleh */ print_full = TRUE; } } else { // Instance of partially active anonymous clone print_full = TRUE; } if (print_full) { GList *all = NULL; clone_header(out, &rc, rsc, clone_data, desc); /* Print every resource that's a child of this clone. */ all = g_list_prepend(all, (gpointer) "*"); out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, all); g_list_free(all); } } if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) { PCMK__OUTPUT_LIST_FOOTER(out, rc); return pcmk_rc_ok; } /* Promoted */ promoted_list = g_list_sort(promoted_list, pe__cmp_node_name); for (gIter = promoted_list; gIter; gIter = gIter->next) { pcmk_node_t *host = gIter->data; if (!pcmk__str_in_list(host->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } g_list_free(promoted_list); if ((list_text != NULL) && (list_text->len > 0)) { clone_header(out, &rc, rsc, clone_data, desc); out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]", (const char *) list_text->str); g_string_truncate(list_text, 0); } /* Started/Unpromoted */ started_list = g_list_sort(started_list, pe__cmp_node_name); for (gIter = started_list; gIter; gIter = gIter->next) { pcmk_node_t *host = gIter->data; if (!pcmk__str_in_list(host->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } g_list_free(started_list); if ((list_text != NULL) && (list_text->len > 0)) { clone_header(out, &rc, rsc, clone_data, desc); if (pcmk_is_set(rsc->flags, pcmk_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if (role == pcmk_role_unpromoted) { out->list_item(out, NULL, UNPROMOTED_INSTANCES " (" PCMK_META_TARGET_ROLE "): [ %s ]", (const char *) list_text->str); } else { out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]", (const char *) list_text->str); } } else { out->list_item(out, NULL, "Started: [ %s ]", (const char *) list_text->str); } } if (list_text != NULL) { g_string_free(list_text, TRUE); } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { if (!pcmk_is_set(rsc->flags, pcmk_rsc_unique) && (clone_data->clone_max > active_instances)) { GList *nIter; GList *list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped table for non-unique clones */ if (stopped != NULL) { g_hash_table_destroy(stopped); stopped = NULL; } if (list == NULL) { /* Clusters with PCMK_OPT_SYMMETRIC_CLUSTER=false haven't * calculated allowed_nodes yet. If we've not probed for them * yet, the Stopped list will be empty. */ list = g_hash_table_get_values(rsc->known_on); } list = g_list_sort(list, pe__cmp_node_name); for (nIter = list; nIter != NULL; nIter = nIter->next) { pcmk_node_t *node = (pcmk_node_t *) nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL && pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname); const char *state = "Stopped"; if (configured_role(rsc) == pcmk_role_stopped) { state = "Stopped (disabled)"; } if (stopped == NULL) { stopped = pcmk__strkey_table(free, free); } if (probe_op != NULL) { int rc; pcmk__scan_min_int(crm_element_value(probe_op, PCMK__XA_RC_CODE), &rc, 0); g_hash_table_insert(stopped, strdup(node->details->uname), crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc))); } else { pcmk__insert_dup(stopped, node->details->uname, state); } } } g_list_free(list); } if (stopped != NULL) { GList *list = sorted_hash_table_values(stopped); clone_header(out, &rc, rsc, clone_data, desc); for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) { const char *status = status_iter->data; GList *nodes = nodes_with_status(stopped, status); GString *nodes_str = node_list_to_str(nodes); if (nodes_str != NULL) { if (nodes_str->len > 0) { out->list_item(out, NULL, "%s: [ %s ]", status, (const char *) nodes_str->str); } g_string_free(nodes_str, TRUE); } g_list_free(nodes); } g_list_free(list); g_hash_table_destroy(stopped); /* If there are no instances of this clone (perhaps because there are no * nodes configured), simply output the clone header by itself. This can * come up in PCS testing. */ } else if (active_instances == 0) { clone_header(out, &rc, rsc, clone_data, desc); PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } void clone_free(pcmk_resource_t * rsc) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); pcmk__rsc_trace(rsc, "Freeing %s", rsc->id); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; CRM_ASSERT(child_rsc); pcmk__rsc_trace(child_rsc, "Freeing child %s", child_rsc->id); free_xml(child_rsc->xml); child_rsc->xml = NULL; /* There could be a saved unexpanded xml */ free_xml(child_rsc->orig_xml); child_rsc->orig_xml = NULL; child_rsc->fns->free(child_rsc); } g_list_free(rsc->children); if (clone_data) { CRM_ASSERT(clone_data->demote_notify == NULL); CRM_ASSERT(clone_data->stop_notify == NULL); CRM_ASSERT(clone_data->start_notify == NULL); CRM_ASSERT(clone_data->promote_notify == NULL); } common_free(rsc); } enum rsc_role_e clone_resource_state(const pcmk_resource_t * rsc, gboolean current) { enum rsc_role_e clone_role = pcmk_role_unknown; GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child_rsc = (pcmk_resource_t *) gIter->data; enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, current); if (a_role > clone_role) { clone_role = a_role; } } pcmk__rsc_trace(rsc, "%s role: %s", rsc->id, pcmk_role_text(clone_role)); return clone_role; } /*! * \internal * \brief Check whether a clone has an instance for every node * * \param[in] rsc Clone to check * \param[in] scheduler Scheduler data */ bool pe__is_universal_clone(const pcmk_resource_t *rsc, const pcmk_scheduler_t *scheduler) { if (pcmk__is_clone(rsc)) { clone_variant_data_t *clone_data = rsc->variant_opaque; if (clone_data->clone_max == g_list_length(scheduler->nodes)) { return TRUE; } } return FALSE; } gboolean pe__clone_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc, gboolean check_parent) { gboolean passes = FALSE; clone_variant_data_t *clone_data = NULL; if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else { get_clone_variant_data(clone_data, rsc); passes = pcmk__str_in_list(pcmk__xe_id(clone_data->xml_obj_child), only_rsc, pcmk__str_star_matches); if (!passes) { for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *child_rsc = NULL; child_rsc = (const pcmk_resource_t *) iter->data; if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) { passes = TRUE; break; } } } } return !passes; } const char * pe__clone_child_id(const pcmk_resource_t *rsc) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); return pcmk__xe_id(clone_data->xml_obj_child); } /*! * \internal * \brief Check whether a clone is ordered * * \param[in] clone Clone resource to check * * \return true if clone is ordered, otherwise false */ bool pe__clone_is_ordered(const pcmk_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); return pcmk_is_set(clone_data->flags, pcmk__clone_ordered); } /*! * \internal * \brief Set a clone flag * * \param[in,out] clone Clone resource to set flag for * \param[in] flag Clone flag to set * * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not * already set or pcmk_rc_already if it was) */ int pe__set_clone_flag(pcmk_resource_t *clone, enum pcmk__clone_flags flag) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); if (pcmk_is_set(clone_data->flags, flag)) { return pcmk_rc_already; } clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Clone", clone->id, clone_data->flags, flag, "flag"); return pcmk_rc_ok; } /*! * \internal * \brief Check whether a clone flag is set * * \param[in] group Clone resource to check * \param[in] flags Flag or flags to check * * \return \c true if all \p flags are set for \p clone, otherwise \c false */ bool pe__clone_flag_is_set(const pcmk_resource_t *clone, uint32_t flags) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); CRM_ASSERT(clone_data != NULL); return pcmk_all_flags_set(clone_data->flags, flags); } /*! * \internal * \brief Create pseudo-actions needed for promotable clones * * \param[in,out] clone Promotable clone to create actions for * \param[in] any_promoting Whether any instances will be promoted * \param[in] any_demoting Whether any instance will be demoted */ void pe__create_promotable_pseudo_ops(pcmk_resource_t *clone, bool any_promoting, bool any_demoting) { pcmk_action_t *action = NULL; pcmk_action_t *action_complete = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); // Create a "promote" action for the clone itself action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTE, !any_promoting, true); // Create a "promoted" action for when all promotions are done action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_PROMOTED, !any_promoting, true); action_complete->priority = INFINITY; // Create notification pseudo-actions for promotion if (clone_data->promote_notify == NULL) { clone_data->promote_notify = pe__action_notif_pseudo_ops(clone, PCMK_ACTION_PROMOTE, action, action_complete); } // Create a "demote" action for the clone itself action = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTE, !any_demoting, true); // Create a "demoted" action for when all demotions are done action_complete = pe__new_rsc_pseudo_action(clone, PCMK_ACTION_DEMOTED, !any_demoting, true); action_complete->priority = INFINITY; // Create notification pseudo-actions for demotion if (clone_data->demote_notify == NULL) { clone_data->demote_notify = pe__action_notif_pseudo_ops(clone, PCMK_ACTION_DEMOTE, action, action_complete); if (clone_data->promote_notify != NULL) { order_actions(clone_data->stop_notify->post_done, clone_data->promote_notify->pre, pcmk__ar_ordered); order_actions(clone_data->start_notify->post_done, clone_data->promote_notify->pre, pcmk__ar_ordered); order_actions(clone_data->demote_notify->post_done, clone_data->promote_notify->pre, pcmk__ar_ordered); order_actions(clone_data->demote_notify->post_done, clone_data->start_notify->pre, pcmk__ar_ordered); order_actions(clone_data->demote_notify->post_done, clone_data->stop_notify->pre, pcmk__ar_ordered); } } } /*! * \internal * \brief Create all notification data and actions for a clone * * \param[in,out] clone Clone to create notifications for */ void pe__create_clone_notifications(pcmk_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); pe__create_action_notifications(clone, clone_data->start_notify); pe__create_action_notifications(clone, clone_data->stop_notify); pe__create_action_notifications(clone, clone_data->promote_notify); pe__create_action_notifications(clone, clone_data->demote_notify); } /*! * \internal * \brief Free all notification data for a clone * * \param[in,out] clone Clone to free notification data for */ void pe__free_clone_notification_data(pcmk_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); pe__free_action_notification_data(clone_data->demote_notify); clone_data->demote_notify = NULL; pe__free_action_notification_data(clone_data->stop_notify); clone_data->stop_notify = NULL; pe__free_action_notification_data(clone_data->start_notify); clone_data->start_notify = NULL; pe__free_action_notification_data(clone_data->promote_notify); clone_data->promote_notify = NULL; } /*! * \internal * \brief Create pseudo-actions for clone start/stop notifications * * \param[in,out] clone Clone to create pseudo-actions for * \param[in,out] start Start action for \p clone * \param[in,out] stop Stop action for \p clone * \param[in,out] started Started action for \p clone * \param[in,out] stopped Stopped action for \p clone */ void pe__create_clone_notif_pseudo_ops(pcmk_resource_t *clone, pcmk_action_t *start, pcmk_action_t *started, pcmk_action_t *stop, pcmk_action_t *stopped) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); if (clone_data->start_notify == NULL) { clone_data->start_notify = pe__action_notif_pseudo_ops(clone, PCMK_ACTION_START, start, started); } if (clone_data->stop_notify == NULL) { clone_data->stop_notify = pe__action_notif_pseudo_ops(clone, PCMK_ACTION_STOP, stop, stopped); if ((clone_data->start_notify != NULL) && (clone_data->stop_notify != NULL)) { order_actions(clone_data->stop_notify->post_done, clone_data->start_notify->pre, pcmk__ar_ordered); } } } /*! * \internal * \brief Get maximum clone resource instances per node * * \param[in] rsc Clone resource to check * * \return Maximum number of \p rsc instances that can be active on one node */ unsigned int pe__clone_max_per_node(const pcmk_resource_t *rsc) { const clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); return clone_data->clone_node_max; } diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index 989452c7dc..6855f3c9f4 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -1,1215 +1,1215 @@ /* * 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 "pe_status_private.h" void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length); static pcmk_node_t *active_node(const pcmk_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); pcmk_rsc_methods_t resource_class_functions[] = { { native_unpack, native_find_rsc, native_parameter, native_print, native_active, native_resource_state, native_location, native_free, pe__count_common, pe__native_is_filtered, active_node, pe__primitive_max_per_node, }, { group_unpack, native_find_rsc, native_parameter, group_print, group_active, group_resource_state, native_location, group_free, pe__count_common, pe__group_is_filtered, active_node, pe__group_max_per_node, }, { clone_unpack, native_find_rsc, native_parameter, clone_print, clone_active, clone_resource_state, native_location, clone_free, pe__count_common, pe__clone_is_filtered, active_node, pe__clone_max_per_node, }, { pe__unpack_bundle, native_find_rsc, native_parameter, pe__print_bundle, pe__bundle_active, pe__bundle_resource_state, native_location, pe__free_bundle, pe__count_bundle, pe__bundle_is_filtered, pe__bundle_active_node, pe__bundle_max_per_node, } }; static enum pe_obj_types get_resource_type(const char *name) { if (pcmk__str_eq(name, PCMK_XE_PRIMITIVE, pcmk__str_casei)) { return pcmk_rsc_variant_primitive; } else if (pcmk__str_eq(name, PCMK_XE_GROUP, pcmk__str_casei)) { return pcmk_rsc_variant_group; } else if (pcmk__str_eq(name, PCMK_XE_CLONE, pcmk__str_casei)) { return pcmk_rsc_variant_clone; } else if (pcmk__str_eq(name, PCMK__XE_PROMOTABLE_LEGACY, pcmk__str_casei)) { // @COMPAT deprecated since 2.0.0 return pcmk_rsc_variant_clone; } else if (pcmk__str_eq(name, PCMK_XE_BUNDLE, pcmk__str_casei)) { return pcmk_rsc_variant_bundle; } return pcmk_rsc_variant_unknown; } static void dup_attr(gpointer key, gpointer value, gpointer user_data) { add_hash_param(user_data, key, value); } static void expand_parents_fixed_nvpairs(pcmk_resource_t *rsc, pe_rule_eval_data_t *rule_data, GHashTable *meta_hash, pcmk_scheduler_t *scheduler) { GHashTable *parent_orig_meta = pcmk__strkey_table(free, free); pcmk_resource_t *p = rsc->parent; if (p == NULL) { return ; } /* Search all parent resources, get the fixed value of * PCMK_XE_META_ATTRIBUTES set only in the original xml, and stack it in the * hash table. The fixed value of the lower parent resource takes precedence * and is not overwritten. */ while(p != NULL) { /* A hash table for comparison is generated, including the id-ref. */ pe__unpack_dataset_nvpairs(p->xml, PCMK_XE_META_ATTRIBUTES, rule_data, parent_orig_meta, NULL, FALSE, scheduler); p = p->parent; } /* If there is a fixed value of PCMK_XE_META_ATTRIBUTES of the parent * resource, it will be processed. */ if (parent_orig_meta != NULL) { GHashTableIter iter; char *key = NULL; char *value = NULL; g_hash_table_iter_init(&iter, parent_orig_meta); while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { /* Parameters set in the original xml of the parent resource will also try to overwrite the child resource. */ /* Attributes that already exist in the child lease are not updated. */ dup_attr(key, value, meta_hash); } } if (parent_orig_meta != NULL) { g_hash_table_destroy(parent_orig_meta); } return ; } void get_meta_attributes(GHashTable * meta_hash, pcmk_resource_t * rsc, pcmk_node_t *node, pcmk_scheduler_t *scheduler) { pe_rsc_eval_data_t rsc_rule_data = { .standard = crm_element_value(rsc->xml, PCMK_XA_CLASS), .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER), .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE) }; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .now = scheduler->now, .match_data = NULL, .rsc_data = &rsc_rule_data, .op_data = NULL }; if (node) { rule_data.node_hash = node->details->attrs; } for (xmlAttrPtr a = pcmk__xe_first_attr(rsc->xml); a != NULL; a = a->next) { const char *prop_name = (const char *) a->name; const char *prop_value = pcmk__xml_attr_value(a); add_hash_param(meta_hash, prop_name, prop_value); } pe__unpack_dataset_nvpairs(rsc->xml, PCMK_XE_META_ATTRIBUTES, &rule_data, meta_hash, NULL, FALSE, scheduler); /* Set the PCMK_XE_META_ATTRIBUTES explicitly set in the parent resource to * the hash table of the child resource. If it is already explicitly set as * a child, it will not be overwritten. */ if (rsc->parent != NULL) { expand_parents_fixed_nvpairs(rsc, &rule_data, meta_hash, scheduler); } /* check the defaults */ pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, PCMK_XE_META_ATTRIBUTES, &rule_data, meta_hash, NULL, FALSE, scheduler); /* If there is PCMK_XE_META_ATTRIBUTES that the parent resource has not * explicitly set, set a value that is not set from PCMK_XE_RSC_DEFAULTS * either. The values already set up to this point will not be overwritten. */ if (rsc->parent) { g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash); } } void get_rsc_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_scheduler_t *scheduler) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .now = scheduler->now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; if (node) { rule_data.node_hash = node->details->attrs; } pe__unpack_dataset_nvpairs(rsc->xml, PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data, meta_hash, NULL, FALSE, scheduler); /* set anything else based on the parent */ if (rsc->parent != NULL) { get_rsc_attributes(meta_hash, rsc->parent, node, scheduler); } else { /* and finally check the defaults */ pe__unpack_dataset_nvpairs(scheduler->rsc_defaults, PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data, meta_hash, NULL, FALSE, scheduler); } } static char * template_op_key(xmlNode * op) { const char *name = crm_element_value(op, PCMK_XA_NAME); const char *role = crm_element_value(op, PCMK_XA_ROLE); char *key = NULL; if ((role == NULL) || pcmk__strcase_any_of(role, PCMK__ROLE_STARTED, PCMK__ROLE_UNPROMOTED, PCMK__ROLE_UNPROMOTED_LEGACY, NULL)) { role = PCMK__ROLE_UNKNOWN; } key = crm_strdup_printf("%s-%s", name, role); return key; } static gboolean unpack_template(xmlNode *xml_obj, xmlNode **expanded_xml, pcmk_scheduler_t *scheduler) { xmlNode *cib_resources = NULL; xmlNode *template = NULL; xmlNode *new_xml = NULL; xmlNode *child_xml = NULL; xmlNode *rsc_ops = NULL; xmlNode *template_ops = NULL; const char *template_ref = NULL; const char *id = NULL; if (xml_obj == NULL) { pcmk__config_err("No resource object for template unpacking"); return FALSE; } template_ref = crm_element_value(xml_obj, PCMK_XA_TEMPLATE); if (template_ref == NULL) { return TRUE; } id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("'%s' object must have a id", xml_obj->name); return FALSE; } if (pcmk__str_eq(template_ref, id, pcmk__str_none)) { pcmk__config_err("The resource object '%s' should not reference itself", id); return FALSE; } cib_resources = get_xpath_object("//" PCMK_XE_RESOURCES, scheduler->input, LOG_TRACE); if (cib_resources == NULL) { pcmk__config_err("No resources configured"); return FALSE; } template = pcmk__xe_match(cib_resources, PCMK_XE_TEMPLATE, PCMK_XA_ID, template_ref); if (template == NULL) { pcmk__config_err("No template named '%s'", template_ref); return FALSE; } new_xml = copy_xml(template); xmlNodeSetName(new_xml, xml_obj->name); crm_xml_add(new_xml, PCMK_XA_ID, id); crm_xml_add(new_xml, PCMK__META_CLONE, crm_element_value(xml_obj, PCMK__META_CLONE)); template_ops = find_xml_node(new_xml, PCMK_XE_OPERATIONS, FALSE); for (child_xml = pcmk__xe_first_child(xml_obj); child_xml != NULL; child_xml = pcmk__xe_next(child_xml)) { xmlNode *new_child = NULL; new_child = add_node_copy(new_xml, child_xml); if (pcmk__xe_is(new_child, PCMK_XE_OPERATIONS)) { rsc_ops = new_child; } } if (template_ops && rsc_ops) { xmlNode *op = NULL; GHashTable *rsc_ops_hash = pcmk__strkey_table(free, NULL); for (op = pcmk__xe_first_child(rsc_ops); op != NULL; op = pcmk__xe_next(op)) { char *key = template_op_key(op); g_hash_table_insert(rsc_ops_hash, key, op); } for (op = pcmk__xe_first_child(template_ops); op != NULL; op = pcmk__xe_next(op)) { char *key = template_op_key(op); if (g_hash_table_lookup(rsc_ops_hash, key) == NULL) { add_node_copy(rsc_ops, op); } free(key); } if (rsc_ops_hash) { g_hash_table_destroy(rsc_ops_hash); } free_xml(template_ops); } /*free_xml(*expanded_xml); */ *expanded_xml = new_xml; #if 0 /* Disable multi-level templates for now */ if (!unpack_template(new_xml, expanded_xml, scheduler)) { free_xml(*expanded_xml); *expanded_xml = NULL; return FALSE; } #endif return TRUE; } static gboolean add_template_rsc(xmlNode *xml_obj, pcmk_scheduler_t *scheduler) { const char *template_ref = NULL; const char *id = NULL; if (xml_obj == NULL) { pcmk__config_err("No resource object for processing resource list " "of template"); return FALSE; } template_ref = crm_element_value(xml_obj, PCMK_XA_TEMPLATE); if (template_ref == NULL) { return TRUE; } id = pcmk__xe_id(xml_obj); if (id == NULL) { pcmk__config_err("'%s' object must have a id", xml_obj->name); return FALSE; } if (pcmk__str_eq(template_ref, id, pcmk__str_none)) { pcmk__config_err("The resource object '%s' should not reference itself", id); return FALSE; } if (add_tag_ref(scheduler->template_rsc_sets, template_ref, id) == FALSE) { return FALSE; } return TRUE; } static bool detect_promotable(pcmk_resource_t *rsc) { const char *promotable = g_hash_table_lookup(rsc->meta, PCMK_META_PROMOTABLE); if (crm_is_true(promotable)) { return TRUE; } // @COMPAT deprecated since 2.0.0 if (pcmk__xe_is(rsc->xml, PCMK__XE_PROMOTABLE_LEGACY)) { /* @TODO in some future version, pcmk__warn_once() here, * then drop support in even later version */ pcmk__insert_dup(rsc->meta, PCMK_META_PROMOTABLE, PCMK_VALUE_TRUE); return TRUE; } return FALSE; } static void free_params_table(gpointer data) { g_hash_table_destroy((GHashTable *) data); } /*! * \brief Get a table of resource parameters * * \param[in,out] rsc Resource to query * \param[in] node Node for evaluating rules (NULL for defaults) * \param[in,out] scheduler Scheduler data * * \return Hash table containing resource parameter names and values * (or NULL if \p rsc or \p scheduler is NULL) * \note The returned table will be destroyed when the resource is freed, so * callers should not destroy it. */ GHashTable * pe_rsc_params(pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_scheduler_t *scheduler) { GHashTable *params_on_node = NULL; /* A NULL node is used to request the resource's default parameters * (not evaluated for node), but we always want something non-NULL * as a hash table key. */ const char *node_name = ""; // Sanity check if ((rsc == NULL) || (scheduler == NULL)) { return NULL; } if ((node != NULL) && (node->details->uname != NULL)) { node_name = node->details->uname; } // Find the parameter table for given node if (rsc->parameter_cache == NULL) { rsc->parameter_cache = pcmk__strikey_table(free, free_params_table); } else { params_on_node = g_hash_table_lookup(rsc->parameter_cache, node_name); } // If none exists yet, create one with parameters evaluated for node if (params_on_node == NULL) { params_on_node = pcmk__strkey_table(free, free); get_rsc_attributes(params_on_node, rsc, node, scheduler); g_hash_table_insert(rsc->parameter_cache, strdup(node_name), params_on_node); } return params_on_node; } /*! * \internal * \brief Unpack a resource's \c PCMK_META_REQUIRES meta-attribute * * \param[in,out] rsc Resource being unpacked * \param[in] value Value of \c PCMK_META_REQUIRES meta-attribute * \param[in] is_default Whether \p value was selected by default */ static void unpack_requires(pcmk_resource_t *rsc, const char *value, bool is_default) { if (pcmk__str_eq(value, PCMK_VALUE_NOTHING, pcmk__str_casei)) { } else if (pcmk__str_eq(value, PCMK_VALUE_QUORUM, pcmk__str_casei)) { pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_quorum); } else if (pcmk__str_eq(value, PCMK_VALUE_FENCING, pcmk__str_casei)) { pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_fencing); if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { pcmk__config_warn("%s requires fencing but fencing is disabled", rsc->id); } } else if (pcmk__str_eq(value, PCMK_VALUE_UNFENCING, pcmk__str_casei)) { if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) { pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s " "to \"" PCMK_VALUE_QUORUM "\" because fencing " "devices cannot require unfencing", rsc->id); unpack_requires(rsc, PCMK_VALUE_QUORUM, true); return; } else if (!pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { pcmk__config_warn("Resetting \"" PCMK_META_REQUIRES "\" for %s " "to \"" PCMK_VALUE_QUORUM "\" because fencing is " "disabled", rsc->id); unpack_requires(rsc, PCMK_VALUE_QUORUM, true); return; } else { pcmk__set_rsc_flags(rsc, pcmk_rsc_needs_fencing |pcmk_rsc_needs_unfencing); } } else { const char *orig_value = value; if (pcmk_is_set(rsc->flags, pcmk_rsc_fence_device)) { value = PCMK_VALUE_QUORUM; } else if ((rsc->variant == pcmk_rsc_variant_primitive) && xml_contains_remote_node(rsc->xml)) { value = PCMK_VALUE_QUORUM; } else if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_enable_unfencing)) { value = PCMK_VALUE_UNFENCING; } else if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { value = PCMK_VALUE_FENCING; } else if (rsc->cluster->no_quorum_policy == pcmk_no_quorum_ignore) { value = PCMK_VALUE_NOTHING; } else { value = PCMK_VALUE_QUORUM; } if (orig_value != NULL) { pcmk__config_err("Resetting '" PCMK_META_REQUIRES "' for %s " "to '%s' because '%s' is not valid", rsc->id, value, orig_value); } unpack_requires(rsc, value, true); return; } pcmk__rsc_trace(rsc, "\tRequired to start: %s%s", value, (is_default? " (default)" : "")); } #ifndef PCMK__COMPAT_2_0 static void warn_about_deprecated_classes(pcmk_resource_t *rsc) { const char *std = crm_element_value(rsc->xml, PCMK_XA_CLASS); if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_none)) { pcmk__warn_once(pcmk__wo_upstart, "Support for Upstart resources (such as %s) is " "deprecated and will be removed in a future release", rsc->id); } else if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_none)) { pcmk__warn_once(pcmk__wo_nagios, "Support for Nagios resources (such as %s) is " "deprecated and will be removed in a future release", rsc->id); } } #endif /*! * \internal * \brief Unpack configuration XML for a given resource * * Unpack the XML object containing a resource's configuration into a new * \c pcmk_resource_t object. * * \param[in] xml_obj XML node containing the resource's configuration * \param[out] rsc Where to store the unpacked resource information * \param[in] parent Resource's parent, if any * \param[in,out] scheduler Scheduler data * * \return Standard Pacemaker return code * \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and * the caller is responsible for freeing it using its variant-specific * free() method. Otherwise, \p *rsc is guaranteed to be NULL. */ int pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc, pcmk_resource_t *parent, pcmk_scheduler_t *scheduler) { xmlNode *expanded_xml = NULL; xmlNode *ops = NULL; const char *value = NULL; const char *id = NULL; bool guest_node = false; bool remote_node = false; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .now = NULL, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; CRM_CHECK(rsc != NULL, return EINVAL); CRM_CHECK((xml_obj != NULL) && (scheduler != NULL), *rsc = NULL; return EINVAL); rule_data.now = scheduler->now; crm_log_xml_trace(xml_obj, "[raw XML]"); id = crm_element_value(xml_obj, PCMK_XA_ID); if (id == NULL) { pcmk__config_err("Ignoring <%s> configuration without " PCMK_XA_ID, xml_obj->name); return pcmk_rc_unpack_error; } if (unpack_template(xml_obj, &expanded_xml, scheduler) == FALSE) { return pcmk_rc_unpack_error; } *rsc = calloc(1, sizeof(pcmk_resource_t)); if (*rsc == NULL) { pcmk__sched_err("Unable to allocate memory for resource '%s'", id); return ENOMEM; } (*rsc)->cluster = scheduler; if (expanded_xml) { crm_log_xml_trace(expanded_xml, "[expanded XML]"); (*rsc)->xml = expanded_xml; (*rsc)->orig_xml = xml_obj; } else { (*rsc)->xml = xml_obj; (*rsc)->orig_xml = NULL; } /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */ (*rsc)->parent = parent; ops = find_xml_node((*rsc)->xml, PCMK_XE_OPERATIONS, FALSE); (*rsc)->ops_xml = expand_idref(ops, scheduler->input); (*rsc)->variant = get_resource_type((const char *) (*rsc)->xml->name); if ((*rsc)->variant == pcmk_rsc_variant_unknown) { pcmk__config_err("Ignoring resource '%s' of unknown type '%s'", id, (*rsc)->xml->name); common_free(*rsc); *rsc = NULL; return pcmk_rc_unpack_error; } #ifndef PCMK__COMPAT_2_0 warn_about_deprecated_classes(*rsc); #endif (*rsc)->meta = pcmk__strkey_table(free, free); (*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free); (*rsc)->known_on = pcmk__strkey_table(NULL, free); value = crm_element_value((*rsc)->xml, PCMK__META_CLONE); if (value) { (*rsc)->id = crm_strdup_printf("%s:%s", id, value); - add_hash_param((*rsc)->meta, PCMK__META_CLONE, value); + pcmk__insert_meta(*rsc, PCMK__META_CLONE, value); } else { (*rsc)->id = strdup(id); } (*rsc)->fns = &resource_class_functions[(*rsc)->variant]; get_meta_attributes((*rsc)->meta, *rsc, NULL, scheduler); (*rsc)->parameters = pe_rsc_params(*rsc, NULL, scheduler); // \deprecated (*rsc)->flags = 0; pcmk__set_rsc_flags(*rsc, pcmk_rsc_runnable|pcmk_rsc_unassigned); if (!pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed); } (*rsc)->rsc_cons = NULL; (*rsc)->rsc_tickets = NULL; (*rsc)->actions = NULL; (*rsc)->role = pcmk_role_stopped; (*rsc)->next_role = pcmk_role_unknown; (*rsc)->recovery_type = pcmk_multiply_active_restart; (*rsc)->stickiness = 0; (*rsc)->migration_threshold = INFINITY; (*rsc)->failure_timeout = 0; value = g_hash_table_lookup((*rsc)->meta, PCMK_META_PRIORITY); (*rsc)->priority = char2score(value); value = g_hash_table_lookup((*rsc)->meta, PCMK_META_CRITICAL); if ((value == NULL) || crm_is_true(value)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_critical); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_NOTIFY); if (crm_is_true(value)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_notify); } if (xml_contains_remote_node((*rsc)->xml)) { (*rsc)->is_remote_node = TRUE; if (g_hash_table_lookup((*rsc)->meta, PCMK__META_CONTAINER)) { guest_node = true; } else { remote_node = true; } } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_ALLOW_MIGRATE); if (crm_is_true(value)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_migratable); } else if ((value == NULL) && remote_node) { /* By default, we want remote nodes to be able * to float around the cluster without having to stop all the * resources within the remote-node before moving. Allowing * migration support enables this feature. If this ever causes * problems, migration support can be explicitly turned off with * PCMK_META_ALLOW_MIGRATE=false. */ pcmk__set_rsc_flags(*rsc, pcmk_rsc_migratable); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_IS_MANAGED); if ((value != NULL) && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) { if (crm_is_true(value)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_managed); } else { pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed); } } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MAINTENANCE); if (crm_is_true(value)) { pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed); pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance); } if (pcmk_is_set(scheduler->flags, pcmk_sched_in_maintenance)) { pcmk__clear_rsc_flags(*rsc, pcmk_rsc_managed); pcmk__set_rsc_flags(*rsc, pcmk_rsc_maintenance); } if (pcmk__is_clone(pe__const_top_resource(*rsc, false))) { value = g_hash_table_lookup((*rsc)->meta, PCMK_META_GLOBALLY_UNIQUE); if (crm_is_true(value)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique); } if (detect_promotable(*rsc)) { pcmk__set_rsc_flags(*rsc, pcmk_rsc_promotable); } } else { pcmk__set_rsc_flags(*rsc, pcmk_rsc_unique); } // @COMPAT Deprecated meta-attribute value = g_hash_table_lookup((*rsc)->meta, PCMK__META_RESTART_TYPE); if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) { (*rsc)->restart_type = pe_restart_restart; pcmk__rsc_trace(*rsc, "%s dependency restart handling: restart", (*rsc)->id); pcmk__warn_once(pcmk__wo_restart_type, "Support for " PCMK__META_RESTART_TYPE " is deprecated " "and will be removed in a future release"); } else { (*rsc)->restart_type = pe_restart_ignore; pcmk__rsc_trace(*rsc, "%s dependency restart handling: ignore", (*rsc)->id); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MULTIPLE_ACTIVE); if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) { (*rsc)->recovery_type = pcmk_multiply_active_stop; pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop only", (*rsc)->id); } else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) { (*rsc)->recovery_type = pcmk_multiply_active_block; pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: block", (*rsc)->id); } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) { (*rsc)->recovery_type = pcmk_multiply_active_unexpected; pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: " "stop unexpected instances", (*rsc)->id); } else { // "stop_start" if (!pcmk__str_eq(value, "stop_start", pcmk__str_casei|pcmk__str_null_matches)) { pcmk__config_warn("%s is not a valid value for " PCMK_META_MULTIPLE_ACTIVE ", using default of \"stop_start\"", value); } (*rsc)->recovery_type = pcmk_multiply_active_restart; pcmk__rsc_trace(*rsc, "%s multiple running resource recovery: stop/start", (*rsc)->id); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_RESOURCE_STICKINESS); if ((value != NULL) && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) { (*rsc)->stickiness = char2score(value); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_MIGRATION_THRESHOLD); if ((value != NULL) && !pcmk__str_eq(PCMK_VALUE_DEFAULT, value, pcmk__str_casei)) { (*rsc)->migration_threshold = char2score(value); if ((*rsc)->migration_threshold < 0) { /* @TODO We use 1 here to preserve previous behavior, but this * should probably use the default (INFINITY) or 0 (to disable) * instead. */ pcmk__warn_once(pcmk__wo_neg_threshold, PCMK_META_MIGRATION_THRESHOLD " must be non-negative, using 1 instead"); (*rsc)->migration_threshold = 1; } } if (pcmk__str_eq(crm_element_value((*rsc)->xml, PCMK_XA_CLASS), PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { pcmk__set_scheduler_flags(scheduler, pcmk_sched_have_fencing); pcmk__set_rsc_flags(*rsc, pcmk_rsc_fence_device); } value = g_hash_table_lookup((*rsc)->meta, PCMK_META_REQUIRES); unpack_requires(*rsc, value, false); value = g_hash_table_lookup((*rsc)->meta, PCMK_META_FAILURE_TIMEOUT); if (value != NULL) { guint interval_ms = 0U; // Stored as seconds pcmk_parse_interval_spec(value, &interval_ms); (*rsc)->failure_timeout = (int) (interval_ms / 1000); } if (remote_node) { GHashTable *params = pe_rsc_params(*rsc, NULL, scheduler); /* Grabbing the value now means that any rules based on node attributes * will evaluate to false, so such rules should not be used with * PCMK_REMOTE_RA_RECONNECT_INTERVAL. * * @TODO Evaluate per node before using */ value = g_hash_table_lookup(params, PCMK_REMOTE_RA_RECONNECT_INTERVAL); if (value) { /* reconnect delay works by setting failure_timeout and preventing the * connection from starting until the failure is cleared. */ pcmk_parse_interval_spec(value, &((*rsc)->remote_reconnect_ms)); /* We want to override any default failure_timeout in use when remote * PCMK_REMOTE_RA_RECONNECT_INTERVAL is in use. */ (*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000; } } get_target_role(*rsc, &((*rsc)->next_role)); pcmk__rsc_trace(*rsc, "%s desired next state: %s", (*rsc)->id, ((*rsc)->next_role == pcmk_role_unknown)? "default" : pcmk_role_text((*rsc)->next_role)); if ((*rsc)->fns->unpack(*rsc, scheduler) == FALSE) { (*rsc)->fns->free(*rsc); *rsc = NULL; return pcmk_rc_unpack_error; } if (pcmk_is_set(scheduler->flags, pcmk_sched_symmetric_cluster)) { // This tag must stay exactly the same because it is tested elsewhere resource_location(*rsc, NULL, 0, "symmetric_default", scheduler); } else if (guest_node) { /* remote resources tied to a container resource must always be allowed * to opt-in to the cluster. Whether the connection resource is actually * allowed to be placed on a node is dependent on the container resource */ resource_location(*rsc, NULL, 0, "remote_connection_default", scheduler); } pcmk__rsc_trace(*rsc, "%s action notification: %s", (*rsc)->id, pcmk_is_set((*rsc)->flags, pcmk_rsc_notify)? "required" : "not required"); (*rsc)->utilization = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs((*rsc)->xml, PCMK_XE_UTILIZATION, &rule_data, (*rsc)->utilization, NULL, FALSE, scheduler); if (expanded_xml) { if (add_template_rsc(xml_obj, scheduler) == FALSE) { (*rsc)->fns->free(*rsc); *rsc = NULL; return pcmk_rc_unpack_error; } } return pcmk_rc_ok; } gboolean is_parent(pcmk_resource_t *child, pcmk_resource_t *rsc) { pcmk_resource_t *parent = child; if (parent == NULL || rsc == NULL) { return FALSE; } while (parent->parent != NULL) { if (parent->parent == rsc) { return TRUE; } parent = parent->parent; } return FALSE; } pcmk_resource_t * uber_parent(pcmk_resource_t *rsc) { pcmk_resource_t *parent = rsc; if (parent == NULL) { return NULL; } while ((parent->parent != NULL) && (parent->parent->variant != pcmk_rsc_variant_bundle)) { parent = parent->parent; } return parent; } /*! * \internal * \brief Get the topmost parent of a resource as a const pointer * * \param[in] rsc Resource to check * \param[in] include_bundle If true, go all the way to bundle * * \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent, * the bundle if \p rsc is bundled and \p include_bundle is true, * otherwise the topmost parent of \p rsc up to a clone */ const pcmk_resource_t * pe__const_top_resource(const pcmk_resource_t *rsc, bool include_bundle) { const pcmk_resource_t *parent = rsc; if (parent == NULL) { return NULL; } while (parent->parent != NULL) { if (!include_bundle && (parent->parent->variant == pcmk_rsc_variant_bundle)) { break; } parent = parent->parent; } return parent; } void common_free(pcmk_resource_t * rsc) { if (rsc == NULL) { return; } pcmk__rsc_trace(rsc, "Freeing %s %d", rsc->id, rsc->variant); g_list_free(rsc->rsc_cons); g_list_free(rsc->rsc_cons_lhs); g_list_free(rsc->rsc_tickets); g_list_free(rsc->dangling_migrations); if (rsc->parameter_cache != NULL) { g_hash_table_destroy(rsc->parameter_cache); } if (rsc->meta != NULL) { g_hash_table_destroy(rsc->meta); } if (rsc->utilization != NULL) { g_hash_table_destroy(rsc->utilization); } if ((rsc->parent == NULL) && pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { free_xml(rsc->xml); rsc->xml = NULL; free_xml(rsc->orig_xml); rsc->orig_xml = NULL; /* if rsc->orig_xml, then rsc->xml is an expanded xml from a template */ } else if (rsc->orig_xml) { free_xml(rsc->xml); rsc->xml = NULL; } if (rsc->running_on) { g_list_free(rsc->running_on); rsc->running_on = NULL; } if (rsc->known_on) { g_hash_table_destroy(rsc->known_on); rsc->known_on = NULL; } if (rsc->actions) { g_list_free(rsc->actions); rsc->actions = NULL; } if (rsc->allowed_nodes) { g_hash_table_destroy(rsc->allowed_nodes); rsc->allowed_nodes = NULL; } g_list_free(rsc->fillers); g_list_free(rsc->rsc_location); pcmk__rsc_trace(rsc, "Resource freed"); free(rsc->id); free(rsc->clone_name); free(rsc->allocated_to); free(rsc->variant_opaque); free(rsc->pending_task); free(rsc); } /*! * \internal * \brief Count a node and update most preferred to it as appropriate * * \param[in] rsc An active resource * \param[in] node A node that \p rsc is active on * \param[in,out] active This will be set to \p node if \p node is more * preferred than the current value * \param[in,out] count_all If not NULL, this will be incremented * \param[in,out] count_clean If not NULL, this will be incremented if \p node * is online and clean * * \return true if the count should continue, or false if sufficiently known */ bool pe__count_active_node(const pcmk_resource_t *rsc, pcmk_node_t *node, pcmk_node_t **active, unsigned int *count_all, unsigned int *count_clean) { bool keep_looking = false; bool is_happy = false; CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL), return false); is_happy = node->details->online && !node->details->unclean; if (count_all != NULL) { ++*count_all; } if ((count_clean != NULL) && is_happy) { ++*count_clean; } if ((count_all != NULL) || (count_clean != NULL)) { keep_looking = true; // We're counting, so go through entire list } if (rsc->partial_migration_source != NULL) { if (pcmk__same_node(node, rsc->partial_migration_source)) { *active = node; // This is the migration source } else { keep_looking = true; } } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) { if (is_happy && ((*active == NULL) || !(*active)->details->online || (*active)->details->unclean)) { *active = node; // This is the first clean node } else { keep_looking = true; } } if (*active == NULL) { *active = node; // This is the first node checked } return keep_looking; } // Shared implementation of pcmk_rsc_methods_t:active_node() static pcmk_node_t * active_node(const pcmk_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean) { pcmk_node_t *active = NULL; if (count_all != NULL) { *count_all = 0; } if (count_clean != NULL) { *count_clean = 0; } if (rsc == NULL) { return NULL; } for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { if (!pe__count_active_node(rsc, (pcmk_node_t *) iter->data, &active, count_all, count_clean)) { break; // Don't waste time iterating if we don't have to } } return active; } /*! * \brief * \internal Find and count active nodes according to \c PCMK_META_REQUIRES * * \param[in] rsc Resource to check * \param[out] count If not NULL, will be set to count of active nodes * * \return An active node (or NULL if resource is not active anywhere) * * \note This is a convenience wrapper for active_node() where the count of all * active nodes or only clean active nodes is desired according to the * \c PCMK_META_REQUIRES meta-attribute. */ pcmk_node_t * pe__find_active_requires(const pcmk_resource_t *rsc, unsigned int *count) { if (rsc == NULL) { if (count != NULL) { *count = 0; } return NULL; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) { return rsc->fns->active_node(rsc, count, NULL); } else { return rsc->fns->active_node(rsc, NULL, count); } } void pe__count_common(pcmk_resource_t *rsc) { if (rsc->children != NULL) { for (GList *item = rsc->children; item != NULL; item = item->next) { ((pcmk_resource_t *) item->data)->fns->count(item->data); } } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_removed) || (rsc->role > pcmk_role_stopped)) { rsc->cluster->ninstances++; if (pe__resource_is_disabled(rsc)) { rsc->cluster->disabled_resources++; } if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) { rsc->cluster->blocked_resources++; } } } /*! * \internal * \brief Update a resource's next role * * \param[in,out] rsc Resource to be updated * \param[in] role Resource's new next role * \param[in] why Human-friendly reason why role is changing (for logs) */ void pe__set_next_role(pcmk_resource_t *rsc, enum rsc_role_e role, const char *why) { CRM_ASSERT((rsc != NULL) && (why != NULL)); if (rsc->next_role != role) { pcmk__rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)", rsc->id, pcmk_role_text(rsc->next_role), pcmk_role_text(role), why); rsc->next_role = role; } } diff --git a/lib/pengine/failcounts.c b/lib/pengine/failcounts.c index 64669f9a5e..f33b90ffbf 100644 --- a/lib/pengine/failcounts.c +++ b/lib/pengine/failcounts.c @@ -1,473 +1,473 @@ /* * Copyright 2008-2024 the Pacemaker project contributors * * 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 static gboolean is_matched_failure(const char *rsc_id, const xmlNode *conf_op_xml, const xmlNode *lrm_op_xml) { gboolean matched = FALSE; const char *conf_op_name = NULL; const char *lrm_op_task = NULL; const char *conf_op_interval_spec = NULL; guint conf_op_interval_ms = 0; guint lrm_op_interval_ms = 0; const char *lrm_op_id = NULL; char *last_failure_key = NULL; if (rsc_id == NULL || conf_op_xml == NULL || lrm_op_xml == NULL) { return FALSE; } // Get name and interval from configured op conf_op_name = crm_element_value(conf_op_xml, PCMK_XA_NAME); conf_op_interval_spec = crm_element_value(conf_op_xml, PCMK_META_INTERVAL); pcmk_parse_interval_spec(conf_op_interval_spec, &conf_op_interval_ms); // Get name and interval from op history entry lrm_op_task = crm_element_value(lrm_op_xml, PCMK_XA_OPERATION); crm_element_value_ms(lrm_op_xml, PCMK_META_INTERVAL, &lrm_op_interval_ms); if ((conf_op_interval_ms != lrm_op_interval_ms) || !pcmk__str_eq(conf_op_name, lrm_op_task, pcmk__str_casei)) { return FALSE; } lrm_op_id = pcmk__xe_id(lrm_op_xml); last_failure_key = pcmk__op_key(rsc_id, "last_failure", 0); if (pcmk__str_eq(last_failure_key, lrm_op_id, pcmk__str_casei)) { matched = TRUE; } else { char *expected_op_key = pcmk__op_key(rsc_id, conf_op_name, conf_op_interval_ms); if (pcmk__str_eq(expected_op_key, lrm_op_id, pcmk__str_casei)) { int rc = 0; int target_rc = pe__target_rc_from_xml(lrm_op_xml); crm_element_value_int(lrm_op_xml, PCMK__XA_RC_CODE, &rc); if (rc != target_rc) { matched = TRUE; } } free(expected_op_key); } free(last_failure_key); return matched; } static gboolean block_failure(const pcmk_node_t *node, pcmk_resource_t *rsc, const xmlNode *xml_op) { char *xml_name = clone_strip(rsc->id); /* @TODO This xpath search occurs after template expansion, but it is unable * to properly detect on-fail in id-ref, operation meta-attributes, or * op_defaults, or evaluate rules. * * Also, PCMK_META_ON_FAIL defaults to PCMK_VALUE_BLOCK (in * unpack_operation()) for stop actions when stonith is disabled. * * Ideally, we'd unpack the operation before this point, and pass in a * meta-attributes table that takes all that into consideration. */ char *xpath = crm_strdup_printf("//" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='%s']" "//" PCMK_XE_OP "[@" PCMK_META_ON_FAIL "='" PCMK_VALUE_BLOCK "']", xml_name); xmlXPathObject *xpathObj = xpath_search(rsc->xml, xpath); gboolean should_block = FALSE; free(xpath); if (xpathObj) { int max = numXpathResults(xpathObj); int lpc = 0; for (lpc = 0; lpc < max; lpc++) { xmlNode *pref = getXpathResult(xpathObj, lpc); if (xml_op) { should_block = is_matched_failure(xml_name, pref, xml_op); if (should_block) { break; } } else { const char *conf_op_name = NULL; const char *conf_op_interval_spec = NULL; guint conf_op_interval_ms = 0; char *lrm_op_xpath = NULL; xmlXPathObject *lrm_op_xpathObj = NULL; // Get name and interval from configured op conf_op_name = crm_element_value(pref, PCMK_XA_NAME); conf_op_interval_spec = crm_element_value(pref, PCMK_META_INTERVAL); pcmk_parse_interval_spec(conf_op_interval_spec, &conf_op_interval_ms); #define XPATH_FMT "//" PCMK__XE_NODE_STATE "[@" PCMK_XA_UNAME "='%s']" \ "//" PCMK__XE_LRM_RESOURCE "[@" PCMK_XA_ID "='%s']" \ "/" PCMK__XE_LRM_RSC_OP "[@" PCMK_XA_OPERATION "='%s']" \ "[@" PCMK_META_INTERVAL "='%u']" lrm_op_xpath = crm_strdup_printf(XPATH_FMT, node->details->uname, xml_name, conf_op_name, conf_op_interval_ms); lrm_op_xpathObj = xpath_search(rsc->cluster->input, lrm_op_xpath); free(lrm_op_xpath); if (lrm_op_xpathObj) { int max2 = numXpathResults(lrm_op_xpathObj); int lpc2 = 0; for (lpc2 = 0; lpc2 < max2; lpc2++) { xmlNode *lrm_op_xml = getXpathResult(lrm_op_xpathObj, lpc2); should_block = is_matched_failure(xml_name, pref, lrm_op_xml); if (should_block) { break; } } } freeXpathObject(lrm_op_xpathObj); if (should_block) { break; } } } } free(xml_name); freeXpathObject(xpathObj); return should_block; } /*! * \internal * \brief Get resource name as used in failure-related node attributes * * \param[in] rsc Resource to check * * \return Newly allocated string containing resource's fail name * \note The caller is responsible for freeing the result. */ static inline char * rsc_fail_name(const pcmk_resource_t *rsc) { const char *name = (rsc->clone_name? rsc->clone_name : rsc->id); return pcmk_is_set(rsc->flags, pcmk_rsc_unique)? strdup(name) : clone_strip(name); } /*! * \internal * \brief Compile regular expression to match a failure-related node attribute * * \param[in] prefix Attribute prefix to match * \param[in] rsc_name Resource name to match as used in failure attributes * \param[in] is_legacy Whether DC uses per-resource fail counts * \param[in] is_unique Whether the resource is a globally unique clone * \param[out] re Where to store resulting regular expression * * \return Standard Pacemaker return code * \note Fail attributes are named like PREFIX-RESOURCE#OP_INTERVAL. * The caller is responsible for freeing re with regfree(). */ static int generate_fail_regex(const char *prefix, const char *rsc_name, gboolean is_legacy, gboolean is_unique, regex_t *re) { char *pattern; /* @COMPAT DC < 1.1.17: Fail counts used to be per-resource rather than * per-operation. */ const char *op_pattern = (is_legacy? "" : "#.+_[0-9]+"); /* Ignore instance numbers for anything other than globally unique clones. * Anonymous clone fail counts could contain an instance number if the * clone was initially unique, failed, then was converted to anonymous. * @COMPAT Also, before 1.1.8, anonymous clone fail counts always contained * clone instance numbers. */ const char *instance_pattern = (is_unique? "" : "(:[0-9]+)?"); pattern = crm_strdup_printf("^%s-%s%s%s$", prefix, rsc_name, instance_pattern, op_pattern); if (regcomp(re, pattern, REG_EXTENDED|REG_NOSUB) != 0) { free(pattern); return EINVAL; } free(pattern); return pcmk_rc_ok; } /*! * \internal * \brief Compile regular expressions to match failure-related node attributes * * \param[in] rsc Resource being checked for failures * \param[out] failcount_re Storage for regular expression for fail count * \param[out] lastfailure_re Storage for regular expression for last failure * * \return Standard Pacemaker return code * \note On success, the caller is responsible for freeing the expressions with * regfree(). */ static int generate_fail_regexes(const pcmk_resource_t *rsc, regex_t *failcount_re, regex_t *lastfailure_re) { int rc = pcmk_rc_ok; char *rsc_name = rsc_fail_name(rsc); const char *version = crm_element_value(rsc->cluster->input, PCMK_XA_CRM_FEATURE_SET); // @COMPAT Pacemaker <= 1.1.16 used a single fail count per resource gboolean is_legacy = (compare_version(version, "3.0.13") < 0); if (generate_fail_regex(PCMK__FAIL_COUNT_PREFIX, rsc_name, is_legacy, pcmk_is_set(rsc->flags, pcmk_rsc_unique), failcount_re) != pcmk_rc_ok) { rc = EINVAL; } else if (generate_fail_regex(PCMK__LAST_FAILURE_PREFIX, rsc_name, is_legacy, pcmk_is_set(rsc->flags, pcmk_rsc_unique), lastfailure_re) != pcmk_rc_ok) { rc = EINVAL; regfree(failcount_re); } free(rsc_name); return rc; } // Data for fail-count-related iterators struct failcount_data { const pcmk_node_t *node;// Node to check for fail count pcmk_resource_t *rsc; // Resource to check for fail count uint32_t flags; // Fail count flags const xmlNode *xml_op; // History entry for expiration purposes (or NULL) regex_t failcount_re; // Fail count regular expression to match regex_t lastfailure_re; // Last failure regular expression to match int failcount; // Fail count so far time_t last_failure; // Time of most recent failure so far }; /*! * \internal * \brief Update fail count and last failure appropriately for a node attribute * * \param[in] key Node attribute name * \param[in] value Node attribute value * \param[in] user_data Fail count data to update */ static void update_failcount_for_attr(gpointer key, gpointer value, gpointer user_data) { struct failcount_data *fc_data = user_data; // If this is a matching fail count attribute, update fail count if (regexec(&(fc_data->failcount_re), (const char *) key, 0, NULL, 0) == 0) { fc_data->failcount = pcmk__add_scores(fc_data->failcount, char2score(value)); pcmk__rsc_trace(fc_data->rsc, "Added %s (%s) to %s fail count (now %s)", (const char *) key, (const char *) value, fc_data->rsc->id, pcmk_readable_score(fc_data->failcount)); return; } // If this is a matching last failure attribute, update last failure if (regexec(&(fc_data->lastfailure_re), (const char *) key, 0, NULL, 0) == 0) { long long last_ll; if (pcmk__scan_ll(value, &last_ll, 0LL) == pcmk_rc_ok) { fc_data->last_failure = (time_t) QB_MAX(fc_data->last_failure, last_ll); } } } /*! * \internal * \brief Update fail count and last failure appropriately for a filler resource * * \param[in] data Filler resource * \param[in] user_data Fail count data to update */ static void update_failcount_for_filler(gpointer data, gpointer user_data) { pcmk_resource_t *filler = data; struct failcount_data *fc_data = user_data; time_t filler_last_failure = 0; fc_data->failcount += pe_get_failcount(fc_data->node, filler, &filler_last_failure, fc_data->flags, fc_data->xml_op); fc_data->last_failure = QB_MAX(fc_data->last_failure, filler_last_failure); } /*! * \internal * \brief Get a resource's fail count on a node * * \param[in] node Node to check * \param[in,out] rsc Resource to check * \param[out] last_failure If not NULL, where to set time of most recent * failure of \p rsc on \p node * \param[in] flags Group of enum pcmk__fc_flags * \param[in] xml_op If not NULL, consider only the action in this * history entry when determining whether on-fail * is configured as "blocked", otherwise consider * all actions configured for \p rsc * * \return Fail count for \p rsc on \p node according to \p flags */ int pe_get_failcount(const pcmk_node_t *node, pcmk_resource_t *rsc, time_t *last_failure, uint32_t flags, const xmlNode *xml_op) { struct failcount_data fc_data = { .node = node, .rsc = rsc, .flags = flags, .xml_op = xml_op, .failcount = 0, .last_failure = (time_t) 0, }; // Calculate resource failcount as sum of all matching operation failcounts CRM_CHECK(generate_fail_regexes(rsc, &fc_data.failcount_re, &fc_data.lastfailure_re) == pcmk_rc_ok, return 0); g_hash_table_foreach(node->details->attrs, update_failcount_for_attr, &fc_data); regfree(&(fc_data.failcount_re)); regfree(&(fc_data.lastfailure_re)); // If failure blocks the resource, disregard any failure timeout if ((fc_data.failcount > 0) && (rsc->failure_timeout > 0) && block_failure(node, rsc, xml_op)) { pcmk__config_warn("Ignoring failure timeout %d for %s " "because it conflicts with " PCMK_META_ON_FAIL "=" PCMK_VALUE_BLOCK, rsc->failure_timeout, rsc->id); rsc->failure_timeout = 0; } // If all failures have expired, ignore fail count if (pcmk_is_set(flags, pcmk__fc_effective) && (fc_data.failcount > 0) && (fc_data.last_failure > 0) && (rsc->failure_timeout != 0)) { time_t now = get_effective_time(rsc->cluster); if (now > (fc_data.last_failure + rsc->failure_timeout)) { pcmk__rsc_debug(rsc, "Failcount for %s on %s expired after %ds", rsc->id, pcmk__node_name(node), rsc->failure_timeout); fc_data.failcount = 0; } } /* Add the fail count of any filler resources, except that we never want the * fail counts of a bundle container's fillers to count towards the * container's fail count. * * Most importantly, a Pacemaker Remote connection to a bundle container * is a filler of the container, but can reside on a different node than the * container itself. Counting its fail count on its node towards the * container's fail count on that node could lead to attempting to stop the * container on the wrong node. */ if (pcmk_is_set(flags, pcmk__fc_fillers) && (rsc->fillers != NULL) && !pcmk__is_bundled(rsc)) { g_list_foreach(rsc->fillers, update_failcount_for_filler, &fc_data); if (fc_data.failcount > 0) { pcmk__rsc_info(rsc, "Container %s and the resources within it " "have failed %s time%s on %s", rsc->id, pcmk_readable_score(fc_data.failcount), pcmk__plural_s(fc_data.failcount), pcmk__node_name(node)); } } else if (fc_data.failcount > 0) { pcmk__rsc_info(rsc, "%s has failed %s time%s on %s", rsc->id, pcmk_readable_score(fc_data.failcount), pcmk__plural_s(fc_data.failcount), pcmk__node_name(node)); } if (last_failure != NULL) { if ((fc_data.failcount > 0) && (fc_data.last_failure > 0)) { *last_failure = fc_data.last_failure; } else { *last_failure = 0; } } return fc_data.failcount; } /*! * \brief Schedule a controller operation to clear a fail count * * \param[in,out] rsc Resource with failure * \param[in] node Node failure occurred on * \param[in] reason Readable description why needed (for logging) * \param[in,out] scheduler Scheduler data cluster * * \return Scheduled action */ pcmk_action_t * pe__clear_failcount(pcmk_resource_t *rsc, const pcmk_node_t *node, const char *reason, pcmk_scheduler_t *scheduler) { char *key = NULL; pcmk_action_t *clear = NULL; CRM_CHECK(rsc && node && reason && scheduler, return NULL); key = pcmk__op_key(rsc->id, PCMK_ACTION_CLEAR_FAILCOUNT, 0); clear = custom_action(rsc, key, PCMK_ACTION_CLEAR_FAILCOUNT, node, FALSE, scheduler); - add_hash_param(clear->meta, PCMK__META_OP_NO_WAIT, PCMK_VALUE_TRUE); + pcmk__insert_meta(clear, PCMK__META_OP_NO_WAIT, PCMK_VALUE_TRUE); crm_notice("Clearing failure of %s on %s because %s " CRM_XS " %s", rsc->id, pcmk__node_name(node), reason, clear->uuid); return clear; } diff --git a/lib/pengine/native.c b/lib/pengine/native.c index 8a4d6d176d..3828a4cbaf 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -1,1477 +1,1477 @@ /* * 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 #include #ifdef PCMK__COMPAT_2_0 #define PROVIDER_SEP "::" #else #define PROVIDER_SEP ":" #endif /*! * \internal * \brief Check whether a resource is active on multiple nodes */ static bool is_multiply_active(const pcmk_resource_t *rsc) { unsigned int count = 0; if (rsc->variant == pcmk_rsc_variant_primitive) { pe__find_active_requires(rsc, &count); } return count > 1; } static void native_priority_to_node(pcmk_resource_t *rsc, pcmk_node_t *node, gboolean failed) { int priority = 0; if ((rsc->priority == 0) || (failed == TRUE)) { return; } if (rsc->role == pcmk_role_promoted) { // Promoted instance takes base priority + 1 priority = rsc->priority + 1; } else { priority = rsc->priority; } node->details->priority += priority; pcmk__rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s)", pcmk__node_name(node), node->details->priority, (rsc->role == pcmk_role_promoted)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == pcmk_role_promoted)? " + 1" : ""); /* Priority of a resource running on a guest node is added to the cluster * node as well. */ if (node->details->remote_rsc && node->details->remote_rsc->container) { GList *gIter = node->details->remote_rsc->container->running_on; for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *a_node = gIter->data; a_node->details->priority += priority; pcmk__rsc_trace(rsc, "%s now has priority %d with %s'%s' " "(priority: %d%s) from guest node %s", pcmk__node_name(a_node), a_node->details->priority, (rsc->role == pcmk_role_promoted)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == pcmk_role_promoted)? " + 1" : "", pcmk__node_name(node)); } } } void native_add_running(pcmk_resource_t *rsc, pcmk_node_t *node, pcmk_scheduler_t *scheduler, gboolean failed) { GList *gIter = rsc->running_on; CRM_CHECK(node != NULL, return); for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *a_node = (pcmk_node_t *) gIter->data; CRM_CHECK(a_node != NULL, return); if (pcmk__str_eq(a_node->details->id, node->details->id, pcmk__str_casei)) { return; } } pcmk__rsc_trace(rsc, "Adding %s to %s %s", rsc->id, pcmk__node_name(node), pcmk_is_set(rsc->flags, pcmk_rsc_managed)? "" : "(unmanaged)"); rsc->running_on = g_list_append(rsc->running_on, node); if (rsc->variant == pcmk_rsc_variant_primitive) { node->details->running_rsc = g_list_append(node->details->running_rsc, rsc); native_priority_to_node(rsc, node, failed); } if ((rsc->variant == pcmk_rsc_variant_primitive) && node->details->maintenance) { pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed); pcmk__set_rsc_flags(rsc, pcmk_rsc_maintenance); } if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk_resource_t *p = rsc->parent; pcmk__rsc_info(rsc, "resource %s isn't managed", rsc->id); resource_location(rsc, node, INFINITY, "not_managed_default", scheduler); while(p && node->details->online) { /* add without the additional location constraint */ p->running_on = g_list_append(p->running_on, node); p = p->parent; } return; } if (is_multiply_active(rsc)) { switch (rsc->recovery_type) { case pcmk_multiply_active_stop: { GHashTableIter gIter; pcmk_node_t *local_node = NULL; /* make sure it doesn't come up again */ if (rsc->allowed_nodes != NULL) { g_hash_table_destroy(rsc->allowed_nodes); } rsc->allowed_nodes = pe__node_list2table(scheduler->nodes); g_hash_table_iter_init(&gIter, rsc->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&local_node)) { local_node->weight = -INFINITY; } } break; case pcmk_multiply_active_block: pcmk__clear_rsc_flags(rsc, pcmk_rsc_managed); pcmk__set_rsc_flags(rsc, pcmk_rsc_blocked); /* If the resource belongs to a group or bundle configured with * PCMK_META_MULTIPLE_ACTIVE=PCMK_VALUE_BLOCK, block the entire * entity. */ if (rsc->parent && ((rsc->parent->variant == pcmk_rsc_variant_group) || (rsc->parent->variant == pcmk_rsc_variant_bundle)) && (rsc->parent->recovery_type == pcmk_multiply_active_block)) { GList *gIter = rsc->parent->children; for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child = gIter->data; pcmk__clear_rsc_flags(child, pcmk_rsc_managed); pcmk__set_rsc_flags(child, pcmk_rsc_blocked); } } break; // pcmk_multiply_active_restart, pcmk_multiply_active_unexpected default: /* The scheduler will do the right thing because the relevant * variables and flags are set when unpacking the history. */ break; } crm_debug("%s is active on multiple nodes including %s: %s", rsc->id, pcmk__node_name(node), pcmk_multiply_active_text(rsc->recovery_type)); } else { pcmk__rsc_trace(rsc, "Resource %s is active on %s", rsc->id, pcmk__node_name(node)); } if (rsc->parent != NULL) { native_add_running(rsc->parent, node, scheduler, FALSE); } } static void recursive_clear_unique(pcmk_resource_t *rsc, gpointer user_data) { pcmk__clear_rsc_flags(rsc, pcmk_rsc_unique); - add_hash_param(rsc->meta, PCMK_META_GLOBALLY_UNIQUE, PCMK_VALUE_FALSE); + pcmk__insert_meta(rsc, PCMK_META_GLOBALLY_UNIQUE, PCMK_VALUE_FALSE); g_list_foreach(rsc->children, (GFunc) recursive_clear_unique, NULL); } gboolean native_unpack(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { pcmk_resource_t *parent = uber_parent(rsc); const char *standard = crm_element_value(rsc->xml, PCMK_XA_CLASS); uint32_t ra_caps = pcmk_get_ra_caps(standard); pcmk__rsc_trace(rsc, "Processing resource %s...", rsc->id); // Only some agent standards support unique and promotable clones if (!pcmk_is_set(ra_caps, pcmk_ra_cap_unique) && pcmk_is_set(rsc->flags, pcmk_rsc_unique) && pcmk__is_clone(parent)) { /* @COMPAT We should probably reject this situation as an error (as we * do for promotable below) rather than warn and convert, but that would * be a backward-incompatible change that we should probably do with a * transform at a schema major version bump. */ pe__force_anon(standard, parent, rsc->id, scheduler); /* Clear PCMK_META_GLOBALLY_UNIQUE on the parent and all its descendants * unpacked so far (clearing the parent should make any future children * unpacking correct). We have to clear this resource explicitly because * it isn't hooked into the parent's children yet. */ recursive_clear_unique(parent, NULL); recursive_clear_unique(rsc, NULL); } if (!pcmk_is_set(ra_caps, pcmk_ra_cap_promotable) && pcmk_is_set(parent->flags, pcmk_rsc_promotable)) { pcmk__config_err("Resource %s is of type %s and therefore " "cannot be used as a promotable clone resource", rsc->id, standard); return FALSE; } return TRUE; } static bool rsc_is_on_node(pcmk_resource_t *rsc, const pcmk_node_t *node, int flags) { pcmk__rsc_trace(rsc, "Checking whether %s is on %s", rsc->id, pcmk__node_name(node)); if (pcmk_is_set(flags, pcmk_rsc_match_current_node) && (rsc->running_on != NULL)) { for (GList *iter = rsc->running_on; iter; iter = iter->next) { if (pcmk__same_node((pcmk_node_t *) iter->data, node)) { return true; } } } else if (pcmk_is_set(flags, pe_find_inactive) // @COMPAT deprecated && (rsc->running_on == NULL)) { return true; } else if (!pcmk_is_set(flags, pcmk_rsc_match_current_node) && (rsc->allocated_to != NULL) && pcmk__same_node(rsc->allocated_to, node)) { return true; } return false; } pcmk_resource_t * native_find_rsc(pcmk_resource_t *rsc, const char *id, const pcmk_node_t *on_node, int flags) { bool match = false; pcmk_resource_t *result = NULL; CRM_CHECK(id && rsc && rsc->id, return NULL); if (pcmk_is_set(flags, pcmk_rsc_match_clone_only)) { const char *rid = pcmk__xe_id(rsc->xml); if (!pcmk__is_clone(pe__const_top_resource(rsc, false))) { match = false; } else if (!strcmp(id, rsc->id) || pcmk__str_eq(id, rid, pcmk__str_none)) { match = true; } } else if (!strcmp(id, rsc->id)) { match = true; } else if (pcmk_is_set(flags, pcmk_rsc_match_history) && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) { match = true; } else if (pcmk_is_set(flags, pcmk_rsc_match_basename) || (pcmk_is_set(flags, pcmk_rsc_match_anon_basename) && !pcmk_is_set(rsc->flags, pcmk_rsc_unique))) { match = pe_base_name_eq(rsc, id); } if (match && on_node) { if (!rsc_is_on_node(rsc, on_node, flags)) { match = false; } } if (match) { return rsc; } for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child = (pcmk_resource_t *) gIter->data; result = rsc->fns->find_rsc(child, id, on_node, flags); if (result) { return result; } } return NULL; } // create is ignored char * native_parameter(pcmk_resource_t *rsc, pcmk_node_t *node, gboolean create, const char *name, pcmk_scheduler_t *scheduler) { char *value_copy = NULL; const char *value = NULL; GHashTable *params = NULL; CRM_CHECK(rsc != NULL, return NULL); CRM_CHECK(name != NULL && strlen(name) != 0, return NULL); pcmk__rsc_trace(rsc, "Looking up %s in %s", name, rsc->id); params = pe_rsc_params(rsc, node, scheduler); value = g_hash_table_lookup(params, name); if (value == NULL) { /* try meta attributes instead */ value = g_hash_table_lookup(rsc->meta, name); } pcmk__str_update(&value_copy, value); return value_copy; } gboolean native_active(pcmk_resource_t * rsc, gboolean all) { for (GList *gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) { pcmk_node_t *a_node = (pcmk_node_t *) gIter->data; if (a_node->details->unclean) { pcmk__rsc_trace(rsc, "Resource %s: %s is unclean", rsc->id, pcmk__node_name(a_node)); return TRUE; } else if (!a_node->details->online && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { pcmk__rsc_trace(rsc, "Resource %s: %s is offline", rsc->id, pcmk__node_name(a_node)); } else { pcmk__rsc_trace(rsc, "Resource %s active on %s", rsc->id, pcmk__node_name(a_node)); return TRUE; } } return FALSE; } struct print_data_s { long options; void *print_data; }; static const char * native_pending_state(const pcmk_resource_t *rsc) { const char *pending_state = NULL; if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_START, pcmk__str_casei)) { pending_state = "Starting"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_STOP, pcmk__str_casei)) { pending_state = "Stopping"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_TO, pcmk__str_casei)) { pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MIGRATE_FROM, pcmk__str_casei)) { /* Work might be done in here. */ pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_PROMOTE, pcmk__str_casei)) { pending_state = "Promoting"; } else if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_DEMOTE, pcmk__str_casei)) { pending_state = "Demoting"; } return pending_state; } static const char * native_pending_task(const pcmk_resource_t *rsc) { const char *pending_task = NULL; if (pcmk__str_eq(rsc->pending_task, PCMK_ACTION_MONITOR, pcmk__str_casei)) { pending_task = "Monitoring"; /* Pending probes are not printed, even if pending * operations are requested. If someone ever requests that * behavior, uncomment this and the corresponding part of * unpack.c:unpack_rsc_op(). */ /* } else if (pcmk__str_eq(rsc->pending_task, "probe", pcmk__str_casei)) { pending_task = "Checking"; */ } return pending_task; } static enum rsc_role_e native_displayable_role(const pcmk_resource_t *rsc) { enum rsc_role_e role = rsc->role; if ((role == pcmk_role_started) && pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pcmk_rsc_promotable)) { role = pcmk_role_unpromoted; } return role; } static const char * native_displayable_state(const pcmk_resource_t *rsc, bool print_pending) { const char *rsc_state = NULL; if (print_pending) { rsc_state = native_pending_state(rsc); } if (rsc_state == NULL) { rsc_state = pcmk_role_text(native_displayable_role(rsc)); } return rsc_state; } /*! * \internal * \deprecated This function will be removed in a future release */ static void native_print_xml(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS); const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER); const char *rsc_state = native_displayable_state(rsc, pcmk_is_set(options, pe_print_pending)); const char *target_role = NULL; /* resource information. */ status_print("%sxml, PCMK_XA_TYPE)); status_print("role=\"%s\" ", rsc_state); if (rsc->meta) { target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); } if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print("active=\"%s\" ", pcmk__btoa(rsc->fns->active(rsc, TRUE))); status_print("orphaned=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_removed)); status_print("blocked=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_blocked)); status_print("managed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_managed)); status_print("failed=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_failed)); status_print("failure_ignored=\"%s\" ", pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure)); status_print("nodes_running_on=\"%d\" ", g_list_length(rsc->running_on)); if (options & pe_print_pending) { const char *pending_task = native_pending_task(rsc); if (pending_task) { status_print("pending=\"%s\" ", pending_task); } } /* print out the nodes this resource is running on */ if (options & pe_print_rsconly) { status_print("/>\n"); /* do nothing */ } else if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; status_print("%s \n", pre_text, pcmk__s(node->details->uname, ""), node->details->id, pcmk__btoa(!node->details->online)); } status_print("%s\n", pre_text); } else { status_print("/>\n"); } } // Append a flag to resource description string's flags list static bool add_output_flag(GString *s, const char *flag_desc, bool have_flags) { g_string_append(s, (have_flags? ", " : " (")); g_string_append(s, flag_desc); return true; } // Append a node name to resource description string's node list static bool add_output_node(GString *s, const char *node, bool have_nodes) { g_string_append(s, (have_nodes? " " : " [ ")); g_string_append(s, node); return true; } /*! * \internal * \brief Create a string description of a resource * * \param[in] rsc Resource to describe * \param[in] name Desired identifier for the resource * \param[in] node If not NULL, node that resource is "on" * \param[in] show_opts Bitmask of pcmk_show_opt_e. * \param[in] target_role Resource's target role * \param[in] show_nodes Whether to display nodes when multiply active * * \return Newly allocated string description of resource * \note Caller must free the result with g_free(). */ gchar * pcmk__native_output_string(const pcmk_resource_t *rsc, const char *name, const pcmk_node_t *node, uint32_t show_opts, const char *target_role, bool show_nodes) { const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS); const char *provider = NULL; const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE); GString *outstr = NULL; bool have_flags = false; if (rsc->variant != pcmk_rsc_variant_primitive) { return NULL; } CRM_CHECK(name != NULL, name = "unknown"); CRM_CHECK(kind != NULL, kind = "unknown"); CRM_CHECK(class != NULL, class = "unknown"); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER); } if ((node == NULL) && (rsc->lock_node != NULL)) { node = rsc->lock_node; } if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only) || pcmk__list_of_multiple(rsc->running_on)) { node = NULL; } outstr = g_string_sized_new(128); // Resource name and agent pcmk__g_strcat(outstr, name, "\t(", class, ((provider == NULL)? "" : PROVIDER_SEP), pcmk__s(provider, ""), ":", kind, "):\t", NULL); // State on node if (pcmk_is_set(rsc->flags, pcmk_rsc_removed)) { g_string_append(outstr, " ORPHANED"); } if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) { enum rsc_role_e role = native_displayable_role(rsc); g_string_append(outstr, " FAILED"); if (role > pcmk_role_unpromoted) { pcmk__add_word(&outstr, 0, pcmk_role_text(role)); } } else { bool show_pending = pcmk_is_set(show_opts, pcmk_show_pending); pcmk__add_word(&outstr, 0, native_displayable_state(rsc, show_pending)); } if (node) { pcmk__add_word(&outstr, 0, pcmk__node_name(node)); } // Failed probe operation if (native_displayable_role(rsc) == pcmk_role_stopped) { xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node ? node->details->uname : NULL); if (probe_op != NULL) { int rc; pcmk__scan_min_int(crm_element_value(probe_op, PCMK__XA_RC_CODE), &rc, 0); pcmk__g_strcat(outstr, " (", services_ocf_exitcode_str(rc), ") ", NULL); } } // Flags, as: ( [...]) if (node && !(node->details->online) && node->details->unclean) { have_flags = add_output_flag(outstr, "UNCLEAN", have_flags); } if (node && (node == rsc->lock_node)) { have_flags = add_output_flag(outstr, "LOCKED", have_flags); } if (pcmk_is_set(show_opts, pcmk_show_pending)) { const char *pending_task = native_pending_task(rsc); if (pending_task) { have_flags = add_output_flag(outstr, pending_task, have_flags); } } if (target_role != NULL) { switch (pcmk_parse_role(target_role)) { case pcmk_role_unknown: pcmk__config_err("Invalid " PCMK_META_TARGET_ROLE " %s for resource %s", target_role, rsc->id); break; case pcmk_role_stopped: have_flags = add_output_flag(outstr, "disabled", have_flags); break; case pcmk_role_unpromoted: if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pcmk_rsc_promotable)) { have_flags = add_output_flag(outstr, PCMK_META_TARGET_ROLE ":", have_flags); g_string_append(outstr, target_role); } break; default: /* Only show target role if it limits our abilities (i.e. ignore * Started, as it is the default anyways, and doesn't prevent * the resource from becoming promoted). */ break; } } // Blocked or maintenance implies unmanaged if (pcmk_any_flags_set(rsc->flags, pcmk_rsc_blocked|pcmk_rsc_maintenance)) { if (pcmk_is_set(rsc->flags, pcmk_rsc_blocked)) { have_flags = add_output_flag(outstr, "blocked", have_flags); } else if (pcmk_is_set(rsc->flags, pcmk_rsc_maintenance)) { have_flags = add_output_flag(outstr, "maintenance", have_flags); } } else if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { have_flags = add_output_flag(outstr, "unmanaged", have_flags); } if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) { have_flags = add_output_flag(outstr, "failure ignored", have_flags); } if (have_flags) { g_string_append_c(outstr, ')'); } // User-supplied description if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description) || pcmk__list_of_multiple(rsc->running_on)) { const char *desc = crm_element_value(rsc->xml, PCMK_XA_DESCRIPTION); if (desc) { g_string_append(outstr, " ("); g_string_append(outstr, desc); g_string_append(outstr, ")"); } } if (show_nodes && !pcmk_is_set(show_opts, pcmk_show_rsc_only) && pcmk__list_of_multiple(rsc->running_on)) { bool have_nodes = false; for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { pcmk_node_t *n = (pcmk_node_t *) iter->data; have_nodes = add_output_node(outstr, n->details->uname, have_nodes); } if (have_nodes) { g_string_append(outstr, " ]"); } } return g_string_free(outstr, FALSE); } int pe__common_output_html(pcmk__output_t *out, const pcmk_resource_t *rsc, const char *name, const pcmk_node_t *node, uint32_t show_opts) { const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE); const char *target_role = NULL; xmlNodePtr list_node = NULL; const char *cl = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); CRM_ASSERT(kind != NULL); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, PCMK__META_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); } if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { cl = PCMK__VALUE_RSC_MANAGED; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) { cl = PCMK__VALUE_RSC_FAILED; } else if ((rsc->variant == pcmk_rsc_variant_primitive) && (rsc->running_on == NULL)) { cl = PCMK__VALUE_RSC_FAILED; } else if (pcmk__list_of_multiple(rsc->running_on)) { cl = PCMK__VALUE_RSC_MULTIPLE; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) { cl = PCMK__VALUE_RSC_FAILURE_IGNORED; } else { cl = PCMK__VALUE_RSC_OK; } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); list_node = pcmk__output_create_html_node(out, "li", NULL, NULL, NULL); pcmk_create_html_node(list_node, PCMK__XE_SPAN, NULL, cl, s); g_free(s); } return pcmk_rc_ok; } int pe__common_output_text(pcmk__output_t *out, const pcmk_resource_t *rsc, const char *name, const pcmk_node_t *node, uint32_t show_opts) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, PCMK__META_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); out->list_item(out, NULL, "%s", s); g_free(s); } return pcmk_rc_ok; } /*! * \internal * \deprecated This function will be removed in a future release */ void common_print(pcmk_resource_t *rsc, const char *pre_text, const char *name, const pcmk_node_t *node, long options, void *print_data) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, PCMK__META_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(options, pe_print_implicit)) { crm_trace("skipping print of internal resource %s", rsc->id); return; } target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); } if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } if ((pre_text == NULL) && (options & pe_print_printf)) { pre_text = " "; } if (options & pe_print_html) { if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pcmk_rsc_failed)) { status_print(""); } else if (rsc->running_on == NULL) { status_print(""); } else if (pcmk__list_of_multiple(rsc->running_on)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pcmk_rsc_ignore_failure)) { status_print(""); } else { status_print(""); } } { gchar *resource_s = pcmk__native_output_string(rsc, name, node, options, target_role, false); status_print("%s%s", (pre_text? pre_text : ""), resource_s); g_free(resource_s); } if (pcmk_is_set(options, pe_print_html)) { status_print(" "); } if (!pcmk_is_set(options, pe_print_rsconly) && pcmk__list_of_multiple(rsc->running_on)) { GList *gIter = rsc->running_on; int counter = 0; if (options & pe_print_html) { status_print("
      \n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("["); } for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *n = (pcmk_node_t *) gIter->data; counter++; if (options & pe_print_html) { status_print("
    • \n%s", pcmk__node_name(n)); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" %s", pcmk__node_name(n)); } else if ((options & pe_print_log)) { status_print("\t%d : %s", counter, pcmk__node_name(n)); } else { status_print("%s", pcmk__node_name(n)); } if (options & pe_print_html) { status_print("
    • \n"); } } if (options & pe_print_html) { status_print("
    \n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" ]"); } } if (options & pe_print_html) { status_print("
    \n"); } else if (options & pe_print_suppres_nl) { /* nothing */ } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("\n"); } } /*! * \internal * \deprecated This function will be removed in a future release */ void native_print(pcmk_resource_t *rsc, const char *pre_text, long options, void *print_data) { const pcmk_node_t *node = NULL; CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } node = pcmk__current_node(rsc); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } common_print(rsc, pre_text, rsc_printable_id(rsc), node, options, print_data); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__resource_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); int rc = pcmk_rc_no_output; bool print_pending = pcmk_is_set(show_opts, pcmk_show_pending); const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS); const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER); char ra_name[LINE_MAX]; const char *rsc_state = native_displayable_state(rsc, print_pending); const char *target_role = NULL; const char *active = pcmk__btoa(rsc->fns->active(rsc, TRUE)); const char *orphaned = pcmk__flag_text(rsc->flags, pcmk_rsc_removed); const char *blocked = pcmk__flag_text(rsc->flags, pcmk_rsc_blocked); const char *maintenance = pcmk__flag_text(rsc->flags, pcmk_rsc_maintenance); const char *managed = pcmk__flag_text(rsc->flags, pcmk_rsc_managed); const char *failed = pcmk__flag_text(rsc->flags, pcmk_rsc_failed); const char *ignored = pcmk__flag_text(rsc->flags, pcmk_rsc_ignore_failure); char *nodes_running_on = NULL; const char *pending = print_pending? native_pending_task(rsc) : NULL; const char *locked_to = NULL; const char *desc = pe__resource_description(rsc, show_opts); CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } // Resource information snprintf(ra_name, LINE_MAX, "%s%s%s:%s", class, ((prov == NULL)? "" : PROVIDER_SEP), ((prov == NULL)? "" : prov), crm_element_value(rsc->xml, PCMK_XA_TYPE)); if (rsc->meta != NULL) { target_role = g_hash_table_lookup(rsc->meta, PCMK_META_TARGET_ROLE); } nodes_running_on = pcmk__itoa(g_list_length(rsc->running_on)); if (rsc->lock_node != NULL) { locked_to = rsc->lock_node->details->uname; } rc = pe__name_and_nvpairs_xml(out, true, PCMK_XE_RESOURCE, 15, PCMK_XA_ID, rsc_printable_id(rsc), PCMK_XA_RESOURCE_AGENT, ra_name, PCMK_XA_ROLE, rsc_state, PCMK_XA_TARGET_ROLE, target_role, PCMK_XA_ACTIVE, active, PCMK_XA_ORPHANED, orphaned, PCMK_XA_BLOCKED, blocked, PCMK_XA_MAINTENANCE, maintenance, PCMK_XA_MANAGED, managed, PCMK_XA_FAILED, failed, PCMK_XA_FAILURE_IGNORED, ignored, PCMK_XA_NODES_RUNNING_ON, nodes_running_on, PCMK_XA_PENDING, pending, PCMK_XA_LOCKED_TO, locked_to, PCMK_XA_DESCRIPTION, desc); free(nodes_running_on); CRM_ASSERT(rc == pcmk_rc_ok); if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; const char *cached = pcmk__btoa(node->details->online); rc = pe__name_and_nvpairs_xml(out, false, PCMK_XE_NODE, 3, PCMK_XA_NAME, node->details->uname, PCMK_XA_ID, node->details->id, PCMK_XA_CACHED, cached); CRM_ASSERT(rc == pcmk_rc_ok); } } pcmk__output_xml_pop_parent(out); return rc; } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__resource_html(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pcmk_node_t *node = pcmk__current_node(rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_html(out, rsc, rsc_printable_id(rsc), node, show_opts); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pcmk_resource_t *", "GList *", "GList *") int pe__resource_text(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pcmk_resource_t *rsc = va_arg(args, pcmk_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pcmk_node_t *node = pcmk__current_node(rsc); CRM_ASSERT(rsc->variant == pcmk_rsc_variant_primitive); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_text(out, rsc, rsc_printable_id(rsc), node, show_opts); } void native_free(pcmk_resource_t * rsc) { pcmk__rsc_trace(rsc, "Freeing resource action list (not the data)"); common_free(rsc); } enum rsc_role_e native_resource_state(const pcmk_resource_t * rsc, gboolean current) { enum rsc_role_e role = rsc->next_role; if (current) { role = rsc->role; } pcmk__rsc_trace(rsc, "%s state: %s", rsc->id, pcmk_role_text(role)); return role; } /*! * \internal * \brief List nodes where a resource (or any of its children) is * * \param[in] rsc Resource to check * \param[out] list List to add result to * \param[in] current 0 = where allocated, 1 = where running, * 2 = where running or pending * * \return If list contains only one node, that node, or NULL otherwise */ pcmk_node_t * native_location(const pcmk_resource_t *rsc, GList **list, int current) { // @COMPAT: Accept a pcmk__rsc_node argument instead of int current pcmk_node_t *one = NULL; GList *result = NULL; if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *child = (pcmk_resource_t *) gIter->data; child->fns->location(child, &result, current); } } else if (current) { if (rsc->running_on) { result = g_list_copy(rsc->running_on); } if ((current == 2) && rsc->pending_node && !pe_find_node_id(result, rsc->pending_node->details->id)) { result = g_list_append(result, rsc->pending_node); } } else if (current == FALSE && rsc->allocated_to) { result = g_list_append(NULL, rsc->allocated_to); } if (result && (result->next == NULL)) { one = result->data; } if (list) { GList *gIter = result; for (; gIter != NULL; gIter = gIter->next) { pcmk_node_t *node = (pcmk_node_t *) gIter->data; if (*list == NULL || pe_find_node_id(*list, node->details->id) == NULL) { *list = g_list_append(*list, node); } } } g_list_free(result); return one; } static void get_rscs_brief(GList *rsc_list, GHashTable * rsc_table, GHashTable * active_table) { GList *gIter = rsc_list; for (; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *rsc = (pcmk_resource_t *) gIter->data; const char *class = crm_element_value(rsc->xml, PCMK_XA_CLASS); const char *kind = crm_element_value(rsc->xml, PCMK_XA_TYPE); int offset = 0; char buffer[LINE_MAX]; int *rsc_counter = NULL; int *active_counter = NULL; if (rsc->variant != pcmk_rsc_variant_primitive) { continue; } offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", class); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { const char *prov = crm_element_value(rsc->xml, PCMK_XA_PROVIDER); if (prov != NULL) { offset += snprintf(buffer + offset, LINE_MAX - offset, PROVIDER_SEP "%s", prov); } } offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s", kind); CRM_LOG_ASSERT(offset > 0); if (rsc_table) { rsc_counter = g_hash_table_lookup(rsc_table, buffer); if (rsc_counter == NULL) { rsc_counter = calloc(1, sizeof(int)); *rsc_counter = 0; g_hash_table_insert(rsc_table, strdup(buffer), rsc_counter); } (*rsc_counter)++; } if (active_table) { GList *gIter2 = rsc->running_on; for (; gIter2 != NULL; gIter2 = gIter2->next) { pcmk_node_t *node = (pcmk_node_t *) gIter2->data; GHashTable *node_table = NULL; if (node->details->unclean == FALSE && node->details->online == FALSE && pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { continue; } node_table = g_hash_table_lookup(active_table, node->details->uname); if (node_table == NULL) { node_table = pcmk__strkey_table(free, free); g_hash_table_insert(active_table, strdup(node->details->uname), node_table); } active_counter = g_hash_table_lookup(node_table, buffer); if (active_counter == NULL) { active_counter = calloc(1, sizeof(int)); *active_counter = 0; g_hash_table_insert(node_table, strdup(buffer), active_counter); } (*active_counter)++; } } } } static void destroy_node_table(gpointer data) { GHashTable *node_table = data; if (node_table) { g_hash_table_destroy(node_table); } } /*! * \internal * \deprecated This function will be removed in a future release */ void print_rscs_brief(GList *rsc_list, const char *pre_text, long options, void *print_data, gboolean print_all) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GHashTableIter hash_iter; char *type = NULL; int *rsc_counter = NULL; get_rscs_brief(rsc_list, rsc_table, active_table); g_hash_table_iter_init(&hash_iter, rsc_table); while (g_hash_table_iter_next(&hash_iter, (gpointer *)&type, (gpointer *)&rsc_counter)) { GHashTableIter hash_iter2; char *node_name = NULL; GHashTable *node_table = NULL; int active_counter_all = 0; g_hash_table_iter_init(&hash_iter2, active_table); while (g_hash_table_iter_next(&hash_iter2, (gpointer *)&node_name, (gpointer *)&node_table)) { int *active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (options & pe_print_rsconly) { node_name = NULL; } if (options & pe_print_html) { status_print("
  • \n"); } if (print_all) { status_print("%s%d/%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, rsc_counter ? *rsc_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } else { status_print("%s%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } if (options & pe_print_html) { status_print("
  • \n"); } } if (print_all && active_counter_all == 0) { if (options & pe_print_html) { status_print("
  • \n"); } status_print("%s%d/%d\t(%s):\tActive\n", pre_text ? pre_text : "", active_counter_all, rsc_counter ? *rsc_counter : 0, type); if (options & pe_print_html) { status_print("
  • \n"); } } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } } int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, uint32_t show_opts) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GList *sorted_rscs; int rc = pcmk_rc_no_output; get_rscs_brief(rsc_list, rsc_table, active_table); /* Make a list of the rsc_table keys so that it can be sorted. This is to make sure * output order stays consistent between systems. */ sorted_rscs = g_hash_table_get_keys(rsc_table); sorted_rscs = g_list_sort(sorted_rscs, (GCompareFunc) strcmp); for (GList *gIter = sorted_rscs; gIter; gIter = gIter->next) { char *type = (char *) gIter->data; int *rsc_counter = g_hash_table_lookup(rsc_table, type); GList *sorted_nodes = NULL; int active_counter_all = 0; /* Also make a list of the active_table keys so it can be sorted. If there's * more than one instance of a type of resource running, we need the nodes to * be sorted to make sure output order stays consistent between systems. */ sorted_nodes = g_hash_table_get_keys(active_table); sorted_nodes = g_list_sort(sorted_nodes, (GCompareFunc) pcmk__numeric_strcasecmp); for (GList *gIter2 = sorted_nodes; gIter2; gIter2 = gIter2->next) { char *node_name = (char *) gIter2->data; GHashTable *node_table = g_hash_table_lookup(active_table, node_name); int *active_counter = NULL; if (node_table == NULL) { continue; } active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (pcmk_is_set(show_opts, pcmk_show_rsc_only)) { node_name = NULL; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive %s", *active_counter, rsc_counter ? *rsc_counter : 0, type, (*active_counter > 0) && node_name ? node_name : ""); } else { out->list_item(out, NULL, "%d\t(%s):\tActive %s", *active_counter, type, (*active_counter > 0) && node_name ? node_name : ""); } rc = pcmk_rc_ok; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs) && active_counter_all == 0) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive", active_counter_all, rsc_counter ? *rsc_counter : 0, type); rc = pcmk_rc_ok; } if (sorted_nodes) { g_list_free(sorted_nodes); } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } if (sorted_rscs) { g_list_free(sorted_rscs); } return rc; } gboolean pe__native_is_filtered(const pcmk_resource_t *rsc, GList *only_rsc, gboolean check_parent) { if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) { return FALSE; } else if (check_parent && rsc->parent) { const pcmk_resource_t *up = pe__const_top_resource(rsc, true); return up->fns->is_filtered(up, only_rsc, FALSE); } return TRUE; } /*! * \internal * \brief Get maximum primitive resource instances per node * * \param[in] rsc Primitive resource to check * * \return Maximum number of \p rsc instances that can be active on one node */ unsigned int pe__primitive_max_per_node(const pcmk_resource_t *rsc) { CRM_ASSERT((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)); return 1U; } diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c index c8d4823a27..afc7469ac5 100644 --- a/lib/pengine/pe_actions.c +++ b/lib/pengine/pe_actions.c @@ -1,1888 +1,1887 @@ /* * 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 #include "pe_status_private.h" static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj, guint interval_ms); static void add_singleton(pcmk_scheduler_t *scheduler, pcmk_action_t *action) { if (scheduler->singletons == NULL) { scheduler->singletons = pcmk__strkey_table(NULL, NULL); } g_hash_table_insert(scheduler->singletons, action->uuid, action); } static pcmk_action_t * lookup_singleton(pcmk_scheduler_t *scheduler, const char *action_uuid) { if (scheduler->singletons == NULL) { return NULL; } return g_hash_table_lookup(scheduler->singletons, action_uuid); } /*! * \internal * \brief Find an existing action that matches arguments * * \param[in] key Action key to match * \param[in] rsc Resource to match (if any) * \param[in] node Node to match (if any) * \param[in] scheduler Scheduler data * * \return Existing action that matches arguments (or NULL if none) */ static pcmk_action_t * find_existing_action(const char *key, const pcmk_resource_t *rsc, const pcmk_node_t *node, const pcmk_scheduler_t *scheduler) { GList *matches = NULL; pcmk_action_t *action = NULL; /* When rsc is NULL, it would be quicker to check scheduler->singletons, * but checking all scheduler->actions takes the node into account. */ matches = find_actions(((rsc == NULL)? scheduler->actions : rsc->actions), key, node); if (matches == NULL) { return NULL; } CRM_LOG_ASSERT(!pcmk__list_of_multiple(matches)); action = matches->data; g_list_free(matches); return action; } /*! * \internal * \brief Find the XML configuration corresponding to a specific action key * * \param[in] rsc Resource to find action configuration for * \param[in] key "RSC_ACTION_INTERVAL" of action to find * \param[in] include_disabled If false, do not return disabled actions * * \return XML configuration of desired action if any, otherwise NULL */ static xmlNode * find_exact_action_config(const pcmk_resource_t *rsc, const char *action_name, guint interval_ms, bool include_disabled) { for (xmlNode *operation = first_named_child(rsc->ops_xml, PCMK_XE_OP); operation != NULL; operation = crm_next_same_xml(operation)) { bool enabled = false; const char *config_name = NULL; const char *interval_spec = NULL; guint tmp_ms = 0U; // @TODO This does not consider meta-attributes, rules, defaults, etc. if (!include_disabled && (pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED, &enabled) == pcmk_rc_ok) && !enabled) { continue; } interval_spec = crm_element_value(operation, PCMK_META_INTERVAL); pcmk_parse_interval_spec(interval_spec, &tmp_ms); if (tmp_ms != interval_ms) { continue; } config_name = crm_element_value(operation, PCMK_XA_NAME); if (pcmk__str_eq(action_name, config_name, pcmk__str_none)) { return operation; } } return NULL; } /*! * \internal * \brief Find the XML configuration of a resource action * * \param[in] rsc Resource to find action configuration for * \param[in] action_name Action name to search for * \param[in] interval_ms Action interval (in milliseconds) to search for * \param[in] include_disabled If false, do not return disabled actions * * \return XML configuration of desired action if any, otherwise NULL */ xmlNode * pcmk__find_action_config(const pcmk_resource_t *rsc, const char *action_name, guint interval_ms, bool include_disabled) { xmlNode *action_config = NULL; // Try requested action first action_config = find_exact_action_config(rsc, action_name, interval_ms, include_disabled); // For migrate_to and migrate_from actions, retry with "migrate" // @TODO This should be either documented or deprecated if ((action_config == NULL) && pcmk__str_any_of(action_name, PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, NULL)) { action_config = find_exact_action_config(rsc, "migrate", 0, include_disabled); } return action_config; } /*! * \internal * \brief Create a new action object * * \param[in] key Action key * \param[in] task Action name * \param[in,out] rsc Resource that action is for (if any) * \param[in] node Node that action is on (if any) * \param[in] optional Whether action should be considered optional * \param[in,out] scheduler Scheduler data * * \return Newly allocated action * \note This function takes ownership of \p key. It is the caller's * responsibility to free the return value with pe_free_action(). */ static pcmk_action_t * new_action(char *key, const char *task, pcmk_resource_t *rsc, const pcmk_node_t *node, bool optional, pcmk_scheduler_t *scheduler) { pcmk_action_t *action = calloc(1, sizeof(pcmk_action_t)); CRM_ASSERT(action != NULL); action->rsc = rsc; action->task = strdup(task); CRM_ASSERT(action->task != NULL); action->uuid = key; if (node) { action->node = pe__copy_node(node); } if (pcmk__str_eq(task, PCMK_ACTION_LRM_DELETE, pcmk__str_casei)) { // Resource history deletion for a node can be done on the DC pcmk__set_action_flags(action, pcmk_action_on_dc); } pcmk__set_action_flags(action, pcmk_action_runnable); if (optional) { pcmk__set_action_flags(action, pcmk_action_optional); } else { pcmk__clear_action_flags(action, pcmk_action_optional); } if (rsc == NULL) { action->meta = pcmk__strkey_table(free, free); } else { guint interval_ms = 0; parse_op_key(key, NULL, NULL, &interval_ms); action->op_entry = pcmk__find_action_config(rsc, task, interval_ms, true); /* If the given key is for one of the many notification pseudo-actions * (pre_notify_promote, etc.), the actual action name is "notify" */ if ((action->op_entry == NULL) && (strstr(key, "_notify_") != NULL)) { action->op_entry = find_exact_action_config(rsc, PCMK_ACTION_NOTIFY, 0, true); } unpack_operation(action, action->op_entry, interval_ms); } pcmk__rsc_trace(rsc, "Created %s action %d (%s): %s for %s on %s", (optional? "optional" : "required"), scheduler->action_id, key, task, ((rsc == NULL)? "no resource" : rsc->id), pcmk__node_name(node)); action->id = scheduler->action_id++; scheduler->actions = g_list_prepend(scheduler->actions, action); if (rsc == NULL) { add_singleton(scheduler, action); } else { rsc->actions = g_list_prepend(rsc->actions, action); } return action; } /*! * \internal * \brief Unpack a resource's action-specific instance parameters * * \param[in] action_xml XML of action's configuration in CIB (if any) * \param[in,out] node_attrs Table of node attributes (for rule evaluation) * \param[in,out] scheduler Cluster working set (for rule evaluation) * * \return Newly allocated hash table of action-specific instance parameters */ GHashTable * pcmk__unpack_action_rsc_params(const xmlNode *action_xml, GHashTable *node_attrs, pcmk_scheduler_t *scheduler) { GHashTable *params = pcmk__strkey_table(free, free); pe_rule_eval_data_t rule_data = { .node_hash = node_attrs, .now = scheduler->now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; pe__unpack_dataset_nvpairs(action_xml, PCMK_XE_INSTANCE_ATTRIBUTES, &rule_data, params, NULL, FALSE, scheduler); return params; } /*! * \internal * \brief Update an action's optional flag * * \param[in,out] action Action to update * \param[in] optional Requested optional status */ static void update_action_optional(pcmk_action_t *action, gboolean optional) { // Force a non-recurring action to be optional if its resource is unmanaged if ((action->rsc != NULL) && (action->node != NULL) && !pcmk_is_set(action->flags, pcmk_action_pseudo) && !pcmk_is_set(action->rsc->flags, pcmk_rsc_managed) && (g_hash_table_lookup(action->meta, PCMK_META_INTERVAL) == NULL)) { pcmk__rsc_debug(action->rsc, "%s on %s is optional (%s is unmanaged)", action->uuid, pcmk__node_name(action->node), action->rsc->id); pcmk__set_action_flags(action, pcmk_action_optional); // We shouldn't clear runnable here because ... something // Otherwise require the action if requested } else if (!optional) { pcmk__clear_action_flags(action, pcmk_action_optional); } } static enum pe_quorum_policy effective_quorum_policy(pcmk_resource_t *rsc, pcmk_scheduler_t *scheduler) { enum pe_quorum_policy policy = scheduler->no_quorum_policy; if (pcmk_is_set(scheduler->flags, pcmk_sched_quorate)) { policy = pcmk_no_quorum_ignore; } else if (scheduler->no_quorum_policy == pcmk_no_quorum_demote) { switch (rsc->role) { case pcmk_role_promoted: case pcmk_role_unpromoted: if (rsc->next_role > pcmk_role_unpromoted) { pe__set_next_role(rsc, pcmk_role_unpromoted, PCMK_OPT_NO_QUORUM_POLICY "=demote"); } policy = pcmk_no_quorum_ignore; break; default: policy = pcmk_no_quorum_stop; break; } } return policy; } /*! * \internal * \brief Update a resource action's runnable flag * * \param[in,out] action Action to update * \param[in,out] scheduler Scheduler data * * \note This may also schedule fencing if a stop is unrunnable. */ static void update_resource_action_runnable(pcmk_action_t *action, pcmk_scheduler_t *scheduler) { if (pcmk_is_set(action->flags, pcmk_action_pseudo)) { return; } if (action->node == NULL) { pcmk__rsc_trace(action->rsc, "%s is unrunnable (unallocated)", action->uuid); pcmk__clear_action_flags(action, pcmk_action_runnable); } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc) && !(action->node->details->online) && (!pe__is_guest_node(action->node) || action->node->details->remote_requires_reset)) { pcmk__clear_action_flags(action, pcmk_action_runnable); do_crm_log(LOG_WARNING, "%s on %s is unrunnable (node is offline)", action->uuid, pcmk__node_name(action->node)); if (pcmk_is_set(action->rsc->flags, pcmk_rsc_managed) && pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei) && !(action->node->details->unclean)) { pe_fence_node(scheduler, action->node, "stop is unrunnable", false); } } else if (!pcmk_is_set(action->flags, pcmk_action_on_dc) && action->node->details->pending) { pcmk__clear_action_flags(action, pcmk_action_runnable); do_crm_log(LOG_WARNING, "Action %s on %s is unrunnable (node is pending)", action->uuid, pcmk__node_name(action->node)); } else if (action->needs == pcmk_requires_nothing) { pe_action_set_reason(action, NULL, TRUE); if (pe__is_guest_node(action->node) && !pe_can_fence(scheduler, action->node)) { /* An action that requires nothing usually does not require any * fencing in order to be runnable. However, there is an exception: * such an action cannot be completed if it is on a guest node whose * host is unclean and cannot be fenced. */ pcmk__rsc_debug(action->rsc, "%s on %s is unrunnable " "(node's host cannot be fenced)", action->uuid, pcmk__node_name(action->node)); pcmk__clear_action_flags(action, pcmk_action_runnable); } else { pcmk__rsc_trace(action->rsc, "%s on %s does not require fencing or quorum", action->uuid, pcmk__node_name(action->node)); pcmk__set_action_flags(action, pcmk_action_runnable); } } else { switch (effective_quorum_policy(action->rsc, scheduler)) { case pcmk_no_quorum_stop: pcmk__rsc_debug(action->rsc, "%s on %s is unrunnable (no quorum)", action->uuid, pcmk__node_name(action->node)); pcmk__clear_action_flags(action, pcmk_action_runnable); pe_action_set_reason(action, "no quorum", true); break; case pcmk_no_quorum_freeze: if (!action->rsc->fns->active(action->rsc, TRUE) || (action->rsc->next_role > action->rsc->role)) { pcmk__rsc_debug(action->rsc, "%s on %s is unrunnable (no quorum)", action->uuid, pcmk__node_name(action->node)); pcmk__clear_action_flags(action, pcmk_action_runnable); pe_action_set_reason(action, "quorum freeze", true); } break; default: //pe_action_set_reason(action, NULL, TRUE); pcmk__set_action_flags(action, pcmk_action_runnable); break; } } } /*! * \internal * \brief Update a resource object's flags for a new action on it * * \param[in,out] rsc Resource that action is for (if any) * \param[in] action New action */ static void update_resource_flags_for_action(pcmk_resource_t *rsc, const pcmk_action_t *action) { /* @COMPAT pcmk_rsc_starting and pcmk_rsc_stopping are deprecated and unused * within Pacemaker, and will eventually be removed */ if (pcmk__str_eq(action->task, PCMK_ACTION_STOP, pcmk__str_casei)) { pcmk__set_rsc_flags(rsc, pcmk_rsc_stopping); } else if (pcmk__str_eq(action->task, PCMK_ACTION_START, pcmk__str_casei)) { if (pcmk_is_set(action->flags, pcmk_action_runnable)) { pcmk__set_rsc_flags(rsc, pcmk_rsc_starting); } else { pcmk__clear_rsc_flags(rsc, pcmk_rsc_starting); } } } static bool valid_stop_on_fail(const char *value) { return !pcmk__strcase_any_of(value, PCMK_VALUE_STANDBY, PCMK_VALUE_DEMOTE, PCMK_VALUE_STOP, NULL); } /*! * \internal * \brief Validate (and possibly reset) resource action's on_fail meta-attribute * * \param[in] rsc Resource that action is for * \param[in] action_name Action name * \param[in] action_config Action configuration XML from CIB (if any) * \param[in,out] meta Table of action meta-attributes */ static void validate_on_fail(const pcmk_resource_t *rsc, const char *action_name, const xmlNode *action_config, GHashTable *meta) { const char *name = NULL; const char *role = NULL; const char *interval_spec = NULL; const char *value = g_hash_table_lookup(meta, PCMK_META_ON_FAIL); char *key = NULL; char *new_value = NULL; guint interval_ms = 0U; // Stop actions can only use certain on-fail values if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none) && !valid_stop_on_fail(value)) { pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s stop " "action to default value because '%s' is not " "allowed for stop", rsc->id, value); g_hash_table_remove(meta, PCMK_META_ON_FAIL); return; } /* Demote actions default on-fail to the on-fail value for the first * recurring monitor for the promoted role (if any). */ if (pcmk__str_eq(action_name, PCMK_ACTION_DEMOTE, pcmk__str_none) && (value == NULL)) { /* @TODO This does not consider promote options set in a meta-attribute * block (which may have rules that need to be evaluated) rather than * XML properties. */ for (xmlNode *operation = first_named_child(rsc->ops_xml, PCMK_XE_OP); operation != NULL; operation = crm_next_same_xml(operation)) { bool enabled = false; const char *promote_on_fail = NULL; /* We only care about explicit on-fail (if promote uses default, so * can demote) */ promote_on_fail = crm_element_value(operation, PCMK_META_ON_FAIL); if (promote_on_fail == NULL) { continue; } // We only care about recurring monitors for the promoted role name = crm_element_value(operation, PCMK_XA_NAME); role = crm_element_value(operation, PCMK_XA_ROLE); if (!pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none) || !pcmk__strcase_any_of(role, PCMK__ROLE_PROMOTED, PCMK__ROLE_PROMOTED_LEGACY, NULL)) { continue; } interval_spec = crm_element_value(operation, PCMK_META_INTERVAL); pcmk_parse_interval_spec(interval_spec, &interval_ms); if (interval_ms == 0U) { continue; } // We only care about enabled monitors if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED, &enabled) == pcmk_rc_ok) && !enabled) { continue; } /* Demote actions can't default to * PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE */ if (pcmk__str_eq(promote_on_fail, PCMK_VALUE_DEMOTE, pcmk__str_casei)) { continue; } // Use value from first applicable promote action found key = strdup(PCMK_META_ON_FAIL); new_value = strdup(promote_on_fail); CRM_ASSERT((key != NULL) && (new_value != NULL)); g_hash_table_insert(meta, key, new_value); } return; } if (pcmk__str_eq(action_name, PCMK_ACTION_LRM_DELETE, pcmk__str_none) && !pcmk__str_eq(value, PCMK_VALUE_IGNORE, pcmk__str_casei)) { key = strdup(PCMK_META_ON_FAIL); new_value = strdup(PCMK_VALUE_IGNORE); CRM_ASSERT((key != NULL) && (new_value != NULL)); g_hash_table_insert(meta, key, new_value); return; } // PCMK_META_ON_FAIL=PCMK_VALUE_DEMOTE is allowed only for certain actions if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) { name = crm_element_value(action_config, PCMK_XA_NAME); role = crm_element_value(action_config, PCMK_XA_ROLE); interval_spec = crm_element_value(action_config, PCMK_META_INTERVAL); pcmk_parse_interval_spec(interval_spec, &interval_ms); if (!pcmk__str_eq(name, PCMK_ACTION_PROMOTE, pcmk__str_none) && ((interval_ms == 0U) || !pcmk__str_eq(name, PCMK_ACTION_MONITOR, pcmk__str_none) || !pcmk__strcase_any_of(role, PCMK__ROLE_PROMOTED, PCMK__ROLE_PROMOTED_LEGACY, NULL))) { pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for %s %s " "action to default value because 'demote' is not " "allowed for it", rsc->id, name); g_hash_table_remove(meta, PCMK_META_ON_FAIL); return; } } } static int unpack_timeout(const char *value) { long long timeout_ms = crm_get_msec(value); if (timeout_ms < 0) { timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS; } return (int) QB_MIN(timeout_ms, INT_MAX); } // true if value contains valid, non-NULL interval origin for recurring op static bool unpack_interval_origin(const char *value, const xmlNode *xml_obj, guint interval_ms, const crm_time_t *now, long long *start_delay) { long long result = 0; guint interval_sec = interval_ms / 1000; crm_time_t *origin = NULL; // Ignore unspecified values and non-recurring operations if ((value == NULL) || (interval_ms == 0) || (now == NULL)) { return false; } // Parse interval origin from text origin = crm_time_new(value); if (origin == NULL) { pcmk__config_err("Ignoring '" PCMK_META_INTERVAL_ORIGIN "' for " "operation '%s' because '%s' is not valid", pcmk__s(pcmk__xe_id(xml_obj), "(missing ID)"), value); return false; } // Get seconds since origin (negative if origin is in the future) result = crm_time_get_seconds(now) - crm_time_get_seconds(origin); crm_time_free(origin); // Calculate seconds from closest interval to now result = result % interval_sec; // Calculate seconds remaining until next interval result = ((result <= 0)? 0 : interval_sec) - result; crm_info("Calculated a start delay of %llds for operation '%s'", result, pcmk__s(pcmk__xe_id(xml_obj), "(unspecified)")); if (start_delay != NULL) { *start_delay = result * 1000; // milliseconds } return true; } static int unpack_start_delay(const char *value, GHashTable *meta) { long long start_delay_ms = 0; if (value == NULL) { return 0; } start_delay_ms = crm_get_msec(value); start_delay_ms = QB_MIN(start_delay_ms, INT_MAX); if (start_delay_ms < 0) { start_delay_ms = 0; } if (meta != NULL) { g_hash_table_replace(meta, strdup(PCMK_META_START_DELAY), pcmk__itoa(start_delay_ms)); } return (int) start_delay_ms; } /*! * \internal * \brief Find a resource's most frequent recurring monitor * * \param[in] rsc Resource to check * * \return Operation XML configured for most frequent recurring monitor for * \p rsc (if any) */ static xmlNode * most_frequent_monitor(const pcmk_resource_t *rsc) { guint min_interval_ms = G_MAXUINT; xmlNode *op = NULL; for (xmlNode *operation = first_named_child(rsc->ops_xml, PCMK_XE_OP); operation != NULL; operation = crm_next_same_xml(operation)) { bool enabled = false; guint interval_ms = 0U; const char *interval_spec = crm_element_value(operation, PCMK_META_INTERVAL); // We only care about enabled recurring monitors if (!pcmk__str_eq(crm_element_value(operation, PCMK_XA_NAME), PCMK_ACTION_MONITOR, pcmk__str_none)) { continue; } pcmk_parse_interval_spec(interval_spec, &interval_ms); if (interval_ms == 0U) { continue; } // @TODO This does not consider meta-attributes, rules, defaults, etc. if ((pcmk__xe_get_bool_attr(operation, PCMK_META_ENABLED, &enabled) == pcmk_rc_ok) && !enabled) { continue; } if (interval_ms < min_interval_ms) { min_interval_ms = interval_ms; op = operation; } } return op; } /*! * \internal * \brief Unpack action meta-attributes * * \param[in,out] rsc Resource that action is for * \param[in] node Node that action is on * \param[in] action_name Action name * \param[in] interval_ms Action interval (in milliseconds) * \param[in] action_config Action XML configuration from CIB (if any) * * Unpack a resource action's meta-attributes (normalizing the interval, * timeout, and start delay values as integer milliseconds) from its CIB XML * configuration (including defaults). * * \return Newly allocated hash table with normalized action meta-attributes */ GHashTable * pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node, const char *action_name, guint interval_ms, const xmlNode *action_config) { GHashTable *meta = NULL; char *name = NULL; const char *timeout_spec = NULL; const char *str = NULL; pe_rsc_eval_data_t rsc_rule_data = { .standard = crm_element_value(rsc->xml, PCMK_XA_CLASS), .provider = crm_element_value(rsc->xml, PCMK_XA_PROVIDER), .agent = crm_element_value(rsc->xml, PCMK_XA_TYPE), }; pe_op_eval_data_t op_rule_data = { .op_name = action_name, .interval = interval_ms, }; pe_rule_eval_data_t rule_data = { .node_hash = (node == NULL)? NULL : node->details->attrs, .now = rsc->cluster->now, .match_data = NULL, .rsc_data = &rsc_rule_data, .op_data = &op_rule_data, }; meta = pcmk__strkey_table(free, free); // Cluster-wide pe__unpack_dataset_nvpairs(rsc->cluster->op_defaults, PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL, FALSE, rsc->cluster); // Derive default timeout for probes from recurring monitor timeouts if (pcmk_is_probe(action_name, interval_ms)) { xmlNode *min_interval_mon = most_frequent_monitor(rsc); if (min_interval_mon != NULL) { /* @TODO This does not consider timeouts set in * PCMK_XE_META_ATTRIBUTES blocks (which may also have rules that * need to be evaluated). */ timeout_spec = crm_element_value(min_interval_mon, PCMK_META_TIMEOUT); if (timeout_spec != NULL) { pcmk__rsc_trace(rsc, "Setting default timeout for %s probe to " "most frequent monitor's timeout '%s'", rsc->id, timeout_spec); pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec); } } } if (action_config != NULL) { // take precedence over defaults pe__unpack_dataset_nvpairs(action_config, PCMK_XE_META_ATTRIBUTES, &rule_data, meta, NULL, TRUE, rsc->cluster); /* Anything set as an XML property has highest precedence. * This ensures we use the name and interval from the tag. * (See below for the only exception, fence device start/probe timeout.) */ for (xmlAttrPtr attr = action_config->properties; attr != NULL; attr = attr->next) { pcmk__insert_dup(meta, (const char *) attr->name, pcmk__xml_attr_value(attr)); } } g_hash_table_remove(meta, PCMK_XA_ID); // Normalize interval to milliseconds if (interval_ms > 0) { name = strdup(PCMK_META_INTERVAL); CRM_ASSERT(name != NULL); g_hash_table_insert(meta, name, crm_strdup_printf("%u", interval_ms)); } else { g_hash_table_remove(meta, PCMK_META_INTERVAL); } /* Timeout order of precedence (highest to lowest): * 1. pcmk_monitor_timeout resource parameter (only for starts and probes * when rsc has pcmk_ra_cap_fence_params; this gets used for recurring * monitors via the executor instead) * 2. timeout configured in (with taking precedence over * ) * 3. timeout configured in * 4. PCMK_DEFAULT_ACTION_TIMEOUT_MS */ // Check for pcmk_monitor_timeout if (pcmk_is_set(pcmk_get_ra_caps(rsc_rule_data.standard), pcmk_ra_cap_fence_params) && (pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none) || pcmk_is_probe(action_name, interval_ms))) { GHashTable *params = pe_rsc_params(rsc, node, rsc->cluster); timeout_spec = g_hash_table_lookup(params, "pcmk_monitor_timeout"); if (timeout_spec != NULL) { pcmk__rsc_trace(rsc, "Setting timeout for %s %s to " "pcmk_monitor_timeout (%s)", rsc->id, action_name, timeout_spec); pcmk__insert_dup(meta, PCMK_META_TIMEOUT, timeout_spec); } } // Normalize timeout to positive milliseconds name = strdup(PCMK_META_TIMEOUT); CRM_ASSERT(name != NULL); timeout_spec = g_hash_table_lookup(meta, PCMK_META_TIMEOUT); g_hash_table_insert(meta, name, pcmk__itoa(unpack_timeout(timeout_spec))); // Ensure on-fail has a valid value validate_on_fail(rsc, action_name, action_config, meta); // Normalize PCMK_META_START_DELAY str = g_hash_table_lookup(meta, PCMK_META_START_DELAY); if (str != NULL) { unpack_start_delay(str, meta); } else { long long start_delay = 0; str = g_hash_table_lookup(meta, PCMK_META_INTERVAL_ORIGIN); if (unpack_interval_origin(str, action_config, interval_ms, rsc->cluster->now, &start_delay)) { name = strdup(PCMK_META_START_DELAY); CRM_ASSERT(name != NULL); g_hash_table_insert(meta, name, crm_strdup_printf("%lld", start_delay)); } } return meta; } /*! * \internal * \brief Determine an action's quorum and fencing dependency * * \param[in] rsc Resource that action is for * \param[in] action_name Name of action being unpacked * * \return Quorum and fencing dependency appropriate to action */ enum rsc_start_requirement pcmk__action_requires(const pcmk_resource_t *rsc, const char *action_name) { const char *value = NULL; enum rsc_start_requirement requires = pcmk_requires_nothing; CRM_CHECK((rsc != NULL) && (action_name != NULL), return requires); if (!pcmk__strcase_any_of(action_name, PCMK_ACTION_START, PCMK_ACTION_PROMOTE, NULL)) { value = "nothing (not start or promote)"; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_fencing)) { requires = pcmk_requires_fencing; value = "fencing"; } else if (pcmk_is_set(rsc->flags, pcmk_rsc_needs_quorum)) { requires = pcmk_requires_quorum; value = "quorum"; } else { value = "nothing"; } pcmk__rsc_trace(rsc, "%s of %s requires %s", action_name, rsc->id, value); return requires; } /*! * \internal * \brief Parse action failure response from a user-provided string * * \param[in] rsc Resource that action is for * \param[in] action_name Name of action * \param[in] interval_ms Action interval (in milliseconds) * \param[in] value User-provided configuration value for on-fail * * \return Action failure response parsed from \p text */ enum action_fail_response pcmk__parse_on_fail(const pcmk_resource_t *rsc, const char *action_name, guint interval_ms, const char *value) { const char *desc = NULL; bool needs_remote_reset = false; enum action_fail_response on_fail = pcmk_on_fail_ignore; if (value == NULL) { // Use default } else if (pcmk__str_eq(value, PCMK_VALUE_BLOCK, pcmk__str_casei)) { on_fail = pcmk_on_fail_block; desc = "block"; } else if (pcmk__str_eq(value, PCMK_VALUE_FENCE, pcmk__str_casei)) { if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { on_fail = pcmk_on_fail_fence_node; desc = "node fencing"; } else { pcmk__config_err("Resetting '" PCMK_META_ON_FAIL "' for " "%s of %s to 'stop' because 'fence' is not " "valid when fencing is disabled", action_name, rsc->id); on_fail = pcmk_on_fail_stop; desc = "stop resource"; } } else if (pcmk__str_eq(value, PCMK_VALUE_STANDBY, pcmk__str_casei)) { on_fail = pcmk_on_fail_standby_node; desc = "node standby"; } else if (pcmk__strcase_any_of(value, PCMK_VALUE_IGNORE, PCMK_VALUE_NOTHING, NULL)) { desc = "ignore"; } else if (pcmk__str_eq(value, "migrate", pcmk__str_casei)) { on_fail = pcmk_on_fail_ban; desc = "force migration"; } else if (pcmk__str_eq(value, PCMK_VALUE_STOP, pcmk__str_casei)) { on_fail = pcmk_on_fail_stop; desc = "stop resource"; } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART, pcmk__str_casei)) { on_fail = pcmk_on_fail_restart; desc = "restart (and possibly migrate)"; } else if (pcmk__str_eq(value, PCMK_VALUE_RESTART_CONTAINER, pcmk__str_casei)) { if (rsc->container == NULL) { pcmk__rsc_debug(rsc, "Using default " PCMK_META_ON_FAIL " for %s " "of %s because it does not have a container", action_name, rsc->id); } else { on_fail = pcmk_on_fail_restart_container; desc = "restart container (and possibly migrate)"; } } else if (pcmk__str_eq(value, PCMK_VALUE_DEMOTE, pcmk__str_casei)) { on_fail = pcmk_on_fail_demote; desc = "demote instance"; } else { pcmk__config_err("Using default '" PCMK_META_ON_FAIL "' for " "%s of %s because '%s' is not valid", action_name, rsc->id, value); } /* Remote node connections are handled specially. Failures that result * in dropping an active connection must result in fencing. The only * failures that don't are probes and starts. The user can explicitly set * PCMK_META_ON_FAIL=PCMK_VALUE_FENCE to fence after start failures. */ if (pe__resource_is_remote_conn(rsc) && !pcmk_is_probe(action_name, interval_ms) && !pcmk__str_eq(action_name, PCMK_ACTION_START, pcmk__str_none)) { needs_remote_reset = true; if (!pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { desc = NULL; // Force default for unmanaged connections } } if (desc != NULL) { // Explicit value used, default not needed } else if (rsc->container != NULL) { on_fail = pcmk_on_fail_restart_container; desc = "restart container (and possibly migrate) (default)"; } else if (needs_remote_reset) { if (pcmk_is_set(rsc->flags, pcmk_rsc_managed)) { if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { desc = "fence remote node (default)"; } else { desc = "recover remote node connection (default)"; } on_fail = pcmk_on_fail_reset_remote; } else { on_fail = pcmk_on_fail_stop; desc = "stop unmanaged remote node (enforcing default)"; } } else if (pcmk__str_eq(action_name, PCMK_ACTION_STOP, pcmk__str_none)) { if (pcmk_is_set(rsc->cluster->flags, pcmk_sched_fencing_enabled)) { on_fail = pcmk_on_fail_fence_node; desc = "resource fence (default)"; } else { on_fail = pcmk_on_fail_block; desc = "resource block (default)"; } } else { on_fail = pcmk_on_fail_restart; desc = "restart (and possibly migrate) (default)"; } pcmk__rsc_trace(rsc, "Failure handling for %s-interval %s of %s: %s", pcmk__readable_interval(interval_ms), action_name, rsc->id, desc); return on_fail; } /*! * \internal * \brief Determine a resource's role after failure of an action * * \param[in] rsc Resource that action is for * \param[in] action_name Action name * \param[in] on_fail Failure handling for action * \param[in] meta Unpacked action meta-attributes * * \return Resource role that results from failure of action */ enum rsc_role_e pcmk__role_after_failure(const pcmk_resource_t *rsc, const char *action_name, enum action_fail_response on_fail, GHashTable *meta) { const char *value = NULL; enum rsc_role_e role = pcmk_role_unknown; // Set default for role after failure specially in certain circumstances switch (on_fail) { case pcmk_on_fail_stop: role = pcmk_role_stopped; break; case pcmk_on_fail_reset_remote: if (rsc->remote_reconnect_ms != 0) { role = pcmk_role_stopped; } break; default: break; } // @COMPAT Check for explicitly configured role (deprecated) value = g_hash_table_lookup(meta, PCMK__META_ROLE_AFTER_FAILURE); if (value != NULL) { pcmk__warn_once(pcmk__wo_role_after, "Support for " PCMK__META_ROLE_AFTER_FAILURE " is " "deprecated and will be removed in a future release"); if (role == pcmk_role_unknown) { role = pcmk_parse_role(value); if (role == pcmk_role_unknown) { pcmk__config_err("Ignoring invalid value %s for " PCMK__META_ROLE_AFTER_FAILURE, value); } } } if (role == pcmk_role_unknown) { // Use default if (pcmk__str_eq(action_name, PCMK_ACTION_PROMOTE, pcmk__str_none)) { role = pcmk_role_unpromoted; } else { role = pcmk_role_started; } } pcmk__rsc_trace(rsc, "Role after %s %s failure is: %s", rsc->id, action_name, pcmk_role_text(role)); return role; } /*! * \internal * \brief Unpack action configuration * * Unpack a resource action's meta-attributes (normalizing the interval, * timeout, and start delay values as integer milliseconds), requirements, and * failure policy from its CIB XML configuration (including defaults). * * \param[in,out] action Resource action to unpack into * \param[in] xml_obj Action configuration XML (NULL for defaults only) * \param[in] interval_ms How frequently to perform the operation */ static void unpack_operation(pcmk_action_t *action, const xmlNode *xml_obj, guint interval_ms) { const char *value = NULL; action->meta = pcmk__unpack_action_meta(action->rsc, action->node, action->task, interval_ms, xml_obj); action->needs = pcmk__action_requires(action->rsc, action->task); value = g_hash_table_lookup(action->meta, PCMK_META_ON_FAIL); action->on_fail = pcmk__parse_on_fail(action->rsc, action->task, interval_ms, value); action->fail_role = pcmk__role_after_failure(action->rsc, action->task, action->on_fail, action->meta); } /*! * \brief Create or update an action object * * \param[in,out] rsc Resource that action is for (if any) * \param[in,out] key Action key (must be non-NULL) * \param[in] task Action name (must be non-NULL) * \param[in] on_node Node that action is on (if any) * \param[in] optional Whether action should be considered optional * \param[in,out] scheduler Scheduler data * * \return Action object corresponding to arguments (guaranteed not to be * \c NULL) * \note This function takes ownership of (and might free) \p key, and * \p scheduler takes ownership of the returned action (the caller should * not free it). */ pcmk_action_t * custom_action(pcmk_resource_t *rsc, char *key, const char *task, const pcmk_node_t *on_node, gboolean optional, pcmk_scheduler_t *scheduler) { pcmk_action_t *action = NULL; CRM_ASSERT((key != NULL) && (task != NULL) && (scheduler != NULL)); action = find_existing_action(key, rsc, on_node, scheduler); if (action == NULL) { action = new_action(key, task, rsc, on_node, optional, scheduler); } else { free(key); } update_action_optional(action, optional); if (rsc != NULL) { if ((action->node != NULL) && (action->op_entry != NULL) && !pcmk_is_set(action->flags, pcmk_action_attrs_evaluated)) { GHashTable *attrs = action->node->details->attrs; if (action->extra != NULL) { g_hash_table_destroy(action->extra); } action->extra = pcmk__unpack_action_rsc_params(action->op_entry, attrs, scheduler); pcmk__set_action_flags(action, pcmk_action_attrs_evaluated); } update_resource_action_runnable(action, scheduler); update_resource_flags_for_action(rsc, action); } if (action->extra == NULL) { action->extra = pcmk__strkey_table(free, free); } return action; } pcmk_action_t * get_pseudo_op(const char *name, pcmk_scheduler_t *scheduler) { pcmk_action_t *op = lookup_singleton(scheduler, name); if (op == NULL) { op = custom_action(NULL, strdup(name), name, NULL, TRUE, scheduler); pcmk__set_action_flags(op, pcmk_action_pseudo|pcmk_action_runnable); } return op; } static GList * find_unfencing_devices(GList *candidates, GList *matches) { for (GList *gIter = candidates; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *candidate = gIter->data; if (candidate->children != NULL) { matches = find_unfencing_devices(candidate->children, matches); } else if (!pcmk_is_set(candidate->flags, pcmk_rsc_fence_device)) { continue; } else if (pcmk_is_set(candidate->flags, pcmk_rsc_needs_unfencing)) { matches = g_list_prepend(matches, candidate); } else if (pcmk__str_eq(g_hash_table_lookup(candidate->meta, PCMK_STONITH_PROVIDES), PCMK_VALUE_UNFENCING, pcmk__str_casei)) { matches = g_list_prepend(matches, candidate); } } return matches; } static int node_priority_fencing_delay(const pcmk_node_t *node, const pcmk_scheduler_t *scheduler) { int member_count = 0; int online_count = 0; int top_priority = 0; int lowest_priority = 0; GList *gIter = NULL; // PCMK_OPT_PRIORITY_FENCING_DELAY is disabled if (scheduler->priority_fencing_delay <= 0) { return 0; } /* No need to request a delay if the fencing target is not a normal cluster * member, for example if it's a remote node or a guest node. */ if (node->details->type != pcmk_node_variant_cluster) { return 0; } // No need to request a delay if the fencing target is in our partition if (node->details->online) { return 0; } for (gIter = scheduler->nodes; gIter != NULL; gIter = gIter->next) { pcmk_node_t *n = gIter->data; if (n->details->type != pcmk_node_variant_cluster) { continue; } member_count ++; if (n->details->online) { online_count++; } if (member_count == 1 || n->details->priority > top_priority) { top_priority = n->details->priority; } if (member_count == 1 || n->details->priority < lowest_priority) { lowest_priority = n->details->priority; } } // No need to delay if we have more than half of the cluster members if (online_count > member_count / 2) { return 0; } /* All the nodes have equal priority. * Any configured corresponding `pcmk_delay_base/max` will be applied. */ if (lowest_priority == top_priority) { return 0; } if (node->details->priority < top_priority) { return 0; } return scheduler->priority_fencing_delay; } pcmk_action_t * pe_fence_op(pcmk_node_t *node, const char *op, bool optional, const char *reason, bool priority_delay, pcmk_scheduler_t *scheduler) { char *op_key = NULL; pcmk_action_t *stonith_op = NULL; if(op == NULL) { op = scheduler->stonith_action; } op_key = crm_strdup_printf("%s-%s-%s", PCMK_ACTION_STONITH, node->details->uname, op); stonith_op = lookup_singleton(scheduler, op_key); if(stonith_op == NULL) { stonith_op = custom_action(NULL, op_key, PCMK_ACTION_STONITH, node, TRUE, scheduler); - add_hash_param(stonith_op->meta, PCMK__META_ON_NODE, - node->details->uname); - add_hash_param(stonith_op->meta, PCMK__META_ON_NODE_UUID, - node->details->id); - add_hash_param(stonith_op->meta, PCMK__META_STONITH_ACTION, op); + pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE, node->details->uname); + pcmk__insert_meta(stonith_op, PCMK__META_ON_NODE_UUID, + node->details->id); + pcmk__insert_meta(stonith_op, PCMK__META_STONITH_ACTION, op); if (pcmk_is_set(scheduler->flags, pcmk_sched_enable_unfencing)) { /* Extra work to detect device changes */ GString *digests_all = g_string_sized_new(1024); GString *digests_secure = g_string_sized_new(1024); GList *matches = find_unfencing_devices(scheduler->resources, NULL); char *key = NULL; char *value = NULL; for (GList *gIter = matches; gIter != NULL; gIter = gIter->next) { pcmk_resource_t *match = gIter->data; const char *agent = g_hash_table_lookup(match->meta, PCMK_XA_TYPE); pcmk__op_digest_t *data = NULL; data = pe__compare_fencing_digest(match, agent, node, scheduler); if (data->rc == pcmk__digest_mismatch) { optional = FALSE; crm_notice("Unfencing node %s because the definition of " "%s changed", pcmk__node_name(node), match->id); if (!pcmk__is_daemon && scheduler->priv != NULL) { pcmk__output_t *out = scheduler->priv; out->info(out, "notice: Unfencing node %s because the " "definition of %s changed", pcmk__node_name(node), match->id); } } pcmk__g_strcat(digests_all, match->id, ":", agent, ":", data->digest_all_calc, ",", NULL); pcmk__g_strcat(digests_secure, match->id, ":", agent, ":", data->digest_secure_calc, ",", NULL); } key = strdup(PCMK__META_DIGESTS_ALL); value = strdup((const char *) digests_all->str); CRM_ASSERT((key != NULL) && (value != NULL)); g_hash_table_insert(stonith_op->meta, key, value); g_string_free(digests_all, TRUE); key = strdup(PCMK__META_DIGESTS_SECURE); value = strdup((const char *) digests_secure->str); CRM_ASSERT((key != NULL) && (value != NULL)); g_hash_table_insert(stonith_op->meta, key, value); g_string_free(digests_secure, TRUE); } } else { free(op_key); } if (scheduler->priority_fencing_delay > 0 /* It's a suitable case where PCMK_OPT_PRIORITY_FENCING_DELAY * applies. At least add PCMK_OPT_PRIORITY_FENCING_DELAY field as * an indicator. */ && (priority_delay /* The priority delay needs to be recalculated if this function has * been called by schedule_fencing_and_shutdowns() after node * priority has already been calculated by native_add_running(). */ || g_hash_table_lookup(stonith_op->meta, PCMK_OPT_PRIORITY_FENCING_DELAY) != NULL)) { /* Add PCMK_OPT_PRIORITY_FENCING_DELAY to the fencing op even if * it's 0 for the targeting node. So that it takes precedence over * any possible `pcmk_delay_base/max`. */ char *delay_s = pcmk__itoa(node_priority_fencing_delay(node, scheduler)); g_hash_table_insert(stonith_op->meta, strdup(PCMK_OPT_PRIORITY_FENCING_DELAY), delay_s); } if(optional == FALSE && pe_can_fence(scheduler, node)) { pcmk__clear_action_flags(stonith_op, pcmk_action_optional); pe_action_set_reason(stonith_op, reason, false); } else if(reason && stonith_op->reason == NULL) { stonith_op->reason = strdup(reason); } return stonith_op; } void pe_free_action(pcmk_action_t *action) { if (action == NULL) { return; } g_list_free_full(action->actions_before, free); g_list_free_full(action->actions_after, free); if (action->extra) { g_hash_table_destroy(action->extra); } if (action->meta) { g_hash_table_destroy(action->meta); } free(action->cancel_task); free(action->reason); free(action->task); free(action->uuid); free(action->node); free(action); } int pe_get_configured_timeout(pcmk_resource_t *rsc, const char *action, pcmk_scheduler_t *scheduler) { xmlNode *child = NULL; GHashTable *action_meta = NULL; const char *timeout_spec = NULL; long long timeout_ms = 0; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .now = scheduler->now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; for (child = first_named_child(rsc->ops_xml, PCMK_XE_OP); child != NULL; child = crm_next_same_xml(child)) { if (pcmk__str_eq(action, crm_element_value(child, PCMK_XA_NAME), pcmk__str_casei)) { timeout_spec = crm_element_value(child, PCMK_META_TIMEOUT); break; } } if (timeout_spec == NULL && scheduler->op_defaults) { action_meta = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs(scheduler->op_defaults, PCMK_XE_META_ATTRIBUTES, &rule_data, action_meta, NULL, FALSE, scheduler); timeout_spec = g_hash_table_lookup(action_meta, PCMK_META_TIMEOUT); } // @TODO check meta-attributes // @TODO maybe use min-interval monitor timeout as default for monitors timeout_ms = crm_get_msec(timeout_spec); if (timeout_ms < 0) { timeout_ms = PCMK_DEFAULT_ACTION_TIMEOUT_MS; } if (action_meta != NULL) { g_hash_table_destroy(action_meta); } return (int) QB_MIN(timeout_ms, INT_MAX); } enum action_tasks get_complex_task(const pcmk_resource_t *rsc, const char *name) { enum action_tasks task = pcmk_parse_action(name); if ((rsc != NULL) && (rsc->variant == pcmk_rsc_variant_primitive)) { switch (task) { case pcmk_action_stopped: case pcmk_action_started: case pcmk_action_demoted: case pcmk_action_promoted: crm_trace("Folding %s back into its atomic counterpart for %s", name, rsc->id); --task; break; default: break; } } return task; } /*! * \internal * \brief Find first matching action in a list * * \param[in] input List of actions to search * \param[in] uuid If not NULL, action must have this UUID * \param[in] task If not NULL, action must have this action name * \param[in] on_node If not NULL, action must be on this node * * \return First action in list that matches criteria, or NULL if none */ pcmk_action_t * find_first_action(const GList *input, const char *uuid, const char *task, const pcmk_node_t *on_node) { CRM_CHECK(uuid || task, return NULL); for (const GList *gIter = input; gIter != NULL; gIter = gIter->next) { pcmk_action_t *action = (pcmk_action_t *) gIter->data; if (uuid != NULL && !pcmk__str_eq(uuid, action->uuid, pcmk__str_casei)) { continue; } else if (task != NULL && !pcmk__str_eq(task, action->task, pcmk__str_casei)) { continue; } else if (on_node == NULL) { return action; } else if (action->node == NULL) { continue; } else if (pcmk__same_node(on_node, action->node)) { return action; } } return NULL; } GList * find_actions(GList *input, const char *key, const pcmk_node_t *on_node) { GList *gIter = input; GList *result = NULL; CRM_CHECK(key != NULL, return NULL); for (; gIter != NULL; gIter = gIter->next) { pcmk_action_t *action = (pcmk_action_t *) gIter->data; if (!pcmk__str_eq(key, action->uuid, pcmk__str_casei)) { continue; } else if (on_node == NULL) { crm_trace("Action %s matches (ignoring node)", key); result = g_list_prepend(result, action); } else if (action->node == NULL) { crm_trace("Action %s matches (unallocated, assigning to %s)", key, pcmk__node_name(on_node)); action->node = pe__copy_node(on_node); result = g_list_prepend(result, action); } else if (pcmk__same_node(on_node, action->node)) { crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node)); result = g_list_prepend(result, action); } } return result; } GList * find_actions_exact(GList *input, const char *key, const pcmk_node_t *on_node) { GList *result = NULL; CRM_CHECK(key != NULL, return NULL); if (on_node == NULL) { return NULL; } for (GList *gIter = input; gIter != NULL; gIter = gIter->next) { pcmk_action_t *action = (pcmk_action_t *) gIter->data; if ((action->node != NULL) && pcmk__str_eq(key, action->uuid, pcmk__str_casei) && pcmk__str_eq(on_node->details->id, action->node->details->id, pcmk__str_casei)) { crm_trace("Action %s on %s matches", key, pcmk__node_name(on_node)); result = g_list_prepend(result, action); } } return result; } /*! * \brief Find all actions of given type for a resource * * \param[in] rsc Resource to search * \param[in] node Find only actions scheduled on this node * \param[in] task Action name to search for * \param[in] require_node If TRUE, NULL node or action node will not match * * \return List of actions found (or NULL if none) * \note If node is not NULL and require_node is FALSE, matching actions * without a node will be assigned to node. */ GList * pe__resource_actions(const pcmk_resource_t *rsc, const pcmk_node_t *node, const char *task, bool require_node) { GList *result = NULL; char *key = pcmk__op_key(rsc->id, task, 0); if (require_node) { result = find_actions_exact(rsc->actions, key, node); } else { result = find_actions(rsc->actions, key, node); } free(key); return result; } /*! * \internal * \brief Create an action reason string based on the action itself * * \param[in] action Action to create reason string for * \param[in] flag Action flag that was cleared * * \return Newly allocated string suitable for use as action reason * \note It is the caller's responsibility to free() the result. */ char * pe__action2reason(const pcmk_action_t *action, enum pe_action_flags flag) { const char *change = NULL; switch (flag) { case pcmk_action_runnable: change = "unrunnable"; break; case pcmk_action_migratable: change = "unmigrateable"; break; case pcmk_action_optional: change = "required"; break; default: // Bug: caller passed unsupported flag CRM_CHECK(change != NULL, change = ""); break; } return crm_strdup_printf("%s%s%s %s", change, (action->rsc == NULL)? "" : " ", (action->rsc == NULL)? "" : action->rsc->id, action->task); } void pe_action_set_reason(pcmk_action_t *action, const char *reason, bool overwrite) { if (action->reason != NULL && overwrite) { pcmk__rsc_trace(action->rsc, "Changing %s reason from '%s' to '%s'", action->uuid, action->reason, pcmk__s(reason, "(none)")); } else if (action->reason == NULL) { pcmk__rsc_trace(action->rsc, "Set %s reason to '%s'", action->uuid, pcmk__s(reason, "(none)")); } else { // crm_assert(action->reason != NULL && !overwrite); return; } pcmk__str_update(&action->reason, reason); } /*! * \internal * \brief Create an action to clear a resource's history from CIB * * \param[in,out] rsc Resource to clear * \param[in] node Node to clear history on */ void pe__clear_resource_history(pcmk_resource_t *rsc, const pcmk_node_t *node) { CRM_ASSERT((rsc != NULL) && (node != NULL)); custom_action(rsc, pcmk__op_key(rsc->id, PCMK_ACTION_LRM_DELETE, 0), PCMK_ACTION_LRM_DELETE, node, FALSE, rsc->cluster); } #define sort_return(an_int, why) do { \ free(a_uuid); \ free(b_uuid); \ crm_trace("%s (%d) %c %s (%d) : %s", \ a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \ b_xml_id, b_call_id, why); \ return an_int; \ } while(0) int pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b, bool same_node_default) { int a_call_id = -1; int b_call_id = -1; char *a_uuid = NULL; char *b_uuid = NULL; const char *a_xml_id = crm_element_value(xml_a, PCMK_XA_ID); const char *b_xml_id = crm_element_value(xml_b, PCMK_XA_ID); const char *a_node = crm_element_value(xml_a, PCMK__META_ON_NODE); const char *b_node = crm_element_value(xml_b, PCMK__META_ON_NODE); bool same_node = true; /* @COMPAT The on_node attribute was added to last_failure as of 1.1.13 (via * 8b3ca1c) and the other entries as of 1.1.12 (via 0b07b5c). * * In case that any of the PCMK__XE_LRM_RSC_OP entries doesn't have on_node * attribute, we need to explicitly tell whether the two operations are on * the same node. */ if (a_node == NULL || b_node == NULL) { same_node = same_node_default; } else { same_node = pcmk__str_eq(a_node, b_node, pcmk__str_casei); } if (same_node && pcmk__str_eq(a_xml_id, b_xml_id, pcmk__str_none)) { /* We have duplicate PCMK__XE_LRM_RSC_OP entries in the status * section which is unlikely to be a good thing * - we can handle it easily enough, but we need to get * to the bottom of why it's happening. */ pcmk__config_err("Duplicate " PCMK__XE_LRM_RSC_OP " entries named %s", a_xml_id); sort_return(0, "duplicate"); } crm_element_value_int(xml_a, PCMK__XA_CALL_ID, &a_call_id); crm_element_value_int(xml_b, PCMK__XA_CALL_ID, &b_call_id); if (a_call_id == -1 && b_call_id == -1) { /* both are pending ops so it doesn't matter since * stops are never pending */ sort_return(0, "pending"); } else if (same_node && a_call_id >= 0 && a_call_id < b_call_id) { sort_return(-1, "call id"); } else if (same_node && b_call_id >= 0 && a_call_id > b_call_id) { sort_return(1, "call id"); } else if (a_call_id >= 0 && b_call_id >= 0 && (!same_node || a_call_id == b_call_id)) { /* The op and last_failed_op are the same. Order on * PCMK_XA_LAST_RC_CHANGE. */ time_t last_a = -1; time_t last_b = -1; crm_element_value_epoch(xml_a, PCMK_XA_LAST_RC_CHANGE, &last_a); crm_element_value_epoch(xml_b, PCMK_XA_LAST_RC_CHANGE, &last_b); crm_trace("rc-change: %lld vs %lld", (long long) last_a, (long long) last_b); if (last_a >= 0 && last_a < last_b) { sort_return(-1, "rc-change"); } else if (last_b >= 0 && last_a > last_b) { sort_return(1, "rc-change"); } sort_return(0, "rc-change"); } else { /* One of the inputs is a pending operation. * Attempt to use PCMK__XA_TRANSITION_MAGIC to determine its age relative * to the other. */ int a_id = -1; int b_id = -1; const char *a_magic = crm_element_value(xml_a, PCMK__XA_TRANSITION_MAGIC); const char *b_magic = crm_element_value(xml_b, PCMK__XA_TRANSITION_MAGIC); CRM_CHECK(a_magic != NULL && b_magic != NULL, sort_return(0, "No magic")); if (!decode_transition_magic(a_magic, &a_uuid, &a_id, NULL, NULL, NULL, NULL)) { sort_return(0, "bad magic a"); } if (!decode_transition_magic(b_magic, &b_uuid, &b_id, NULL, NULL, NULL, NULL)) { sort_return(0, "bad magic b"); } /* try to determine the relative age of the operation... * some pending operations (e.g. a start) may have been superseded * by a subsequent stop * * [a|b]_id == -1 means it's a shutdown operation and _always_ comes last */ if (!pcmk__str_eq(a_uuid, b_uuid, pcmk__str_casei) || a_id == b_id) { /* * some of the logic in here may be redundant... * * if the UUID from the TE doesn't match then one better * be a pending operation. * pending operations don't survive between elections and joins * because we query the LRM directly */ if (b_call_id == -1) { sort_return(-1, "transition + call"); } else if (a_call_id == -1) { sort_return(1, "transition + call"); } } else if ((a_id >= 0 && a_id < b_id) || b_id == -1) { sort_return(-1, "transition"); } else if ((b_id >= 0 && a_id > b_id) || a_id == -1) { sort_return(1, "transition"); } } /* we should never end up here */ CRM_CHECK(FALSE, sort_return(0, "default")); } gint sort_op_by_callid(gconstpointer a, gconstpointer b) { const xmlNode *xml_a = a; const xmlNode *xml_b = b; return pe__is_newer_op(xml_a, xml_b, true); } /*! * \internal * \brief Create a new pseudo-action for a resource * * \param[in,out] rsc Resource to create action for * \param[in] task Action name * \param[in] optional Whether action should be considered optional * \param[in] runnable Whethe action should be considered runnable * * \return New action object corresponding to arguments */ pcmk_action_t * pe__new_rsc_pseudo_action(pcmk_resource_t *rsc, const char *task, bool optional, bool runnable) { pcmk_action_t *action = NULL; CRM_ASSERT((rsc != NULL) && (task != NULL)); action = custom_action(rsc, pcmk__op_key(rsc->id, task, 0), task, NULL, optional, rsc->cluster); pcmk__set_action_flags(action, pcmk_action_pseudo); if (runnable) { pcmk__set_action_flags(action, pcmk_action_runnable); } return action; } /*! * \internal * \brief Add the expected result to an action * * \param[in,out] action Action to add expected result to * \param[in] expected_result Expected result to add * - * \note This is more efficient than calling add_hash_param(). + * \note This is more efficient than calling pcmk__insert_meta(). */ void pe__add_action_expected_result(pcmk_action_t *action, int expected_result) { char *name = NULL; CRM_ASSERT((action != NULL) && (action->meta != NULL)); name = strdup(PCMK__META_OP_TARGET_RC); CRM_ASSERT (name != NULL); g_hash_table_insert(action->meta, name, pcmk__itoa(expected_result)); } diff --git a/lib/pengine/pe_notif.c b/lib/pengine/pe_notif.c index c84e28527f..f5b87668a7 100644 --- a/lib/pengine/pe_notif.c +++ b/lib/pengine/pe_notif.c @@ -1,1008 +1,1007 @@ /* * 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 "pe_status_private.h" typedef struct notify_entry_s { const pcmk_resource_t *rsc; const pcmk_node_t *node; } notify_entry_t; /*! * \internal * \brief Compare two notification entries * * Compare two notification entries, where the one with the alphabetically first * resource name (or if equal, node name) sorts as first, with NULL sorting as * less than non-NULL. * * \param[in] a First notification entry to compare * \param[in] b Second notification entry to compare * * \return -1 if \p a sorts before \p b, 0 if they are equal, otherwise 1 */ static gint compare_notify_entries(gconstpointer a, gconstpointer b) { int tmp; const notify_entry_t *entry_a = a; const notify_entry_t *entry_b = b; // NULL a or b is not actually possible if ((entry_a == NULL) && (entry_b == NULL)) { return 0; } if (entry_a == NULL) { return 1; } if (entry_b == NULL) { return -1; } // NULL resources sort first if ((entry_a->rsc == NULL) && (entry_b->rsc == NULL)) { return 0; } if (entry_a->rsc == NULL) { return 1; } if (entry_b->rsc == NULL) { return -1; } // Compare resource names tmp = strcmp(entry_a->rsc->id, entry_b->rsc->id); if (tmp != 0) { return tmp; } // Otherwise NULL nodes sort first if ((entry_a->node == NULL) && (entry_b->node == NULL)) { return 0; } if (entry_a->node == NULL) { return 1; } if (entry_b->node == NULL) { return -1; } // Finally, compare node names return strcmp(entry_a->node->details->id, entry_b->node->details->id); } /*! * \internal * \brief Duplicate a notification entry * * \param[in] entry Entry to duplicate * * \return Newly allocated duplicate of \p entry * \note It is the caller's responsibility to free the return value. */ static notify_entry_t * dup_notify_entry(const notify_entry_t *entry) { notify_entry_t *dup = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(dup != NULL); dup->rsc = entry->rsc; dup->node = entry->node; return dup; } /*! * \internal * \brief Given a list of nodes, create strings with node names * * \param[in] list List of nodes (as pcmk_node_t *) * \param[out] all_node_names If not NULL, will be set to space-separated list * of the names of all nodes in \p list * \param[out] host_node_names Same as \p all_node_names, except active * guest nodes will list the name of their host * * \note The caller is responsible for freeing the output argument values using * \p g_string_free(). */ static void get_node_names(const GList *list, GString **all_node_names, GString **host_node_names) { if (all_node_names != NULL) { *all_node_names = NULL; } if (host_node_names != NULL) { *host_node_names = NULL; } for (const GList *iter = list; iter != NULL; iter = iter->next) { const pcmk_node_t *node = (const pcmk_node_t *) iter->data; if (node->details->uname == NULL) { continue; } // Always add to list of all node names if (all_node_names != NULL) { pcmk__add_word(all_node_names, 1024, node->details->uname); } // Add to host node name list if appropriate if (host_node_names != NULL) { if (pe__is_guest_node(node) && (node->details->remote_rsc->container->running_on != NULL)) { node = pcmk__current_node(node->details->remote_rsc->container); if (node->details->uname == NULL) { continue; } } pcmk__add_word(host_node_names, 1024, node->details->uname); } } if ((all_node_names != NULL) && (*all_node_names == NULL)) { *all_node_names = g_string_new(" "); } if ((host_node_names != NULL) && (*host_node_names == NULL)) { *host_node_names = g_string_new(" "); } } /*! * \internal * \brief Create strings of instance and node names from notification entries * * \param[in,out] list List of notification entries (will be sorted here) * \param[out] rsc_names If not NULL, will be set to space-separated list * of clone instances from \p list * \param[out] node_names If not NULL, will be set to space-separated list * of node names from \p list * * \return (Possibly new) head of sorted \p list * \note The caller is responsible for freeing the output argument values using * \p g_list_free_full() and \p g_string_free(). */ static GList * notify_entries_to_strings(GList *list, GString **rsc_names, GString **node_names) { const char *last_rsc_id = NULL; // Initialize output lists to NULL if (rsc_names != NULL) { *rsc_names = NULL; } if (node_names != NULL) { *node_names = NULL; } // Sort input list for user-friendliness (and ease of filtering duplicates) list = g_list_sort(list, compare_notify_entries); for (GList *gIter = list; gIter != NULL; gIter = gIter->next) { notify_entry_t *entry = (notify_entry_t *) gIter->data; // Entry must have a resource (with ID) CRM_LOG_ASSERT((entry != NULL) && (entry->rsc != NULL) && (entry->rsc->id != NULL)); if ((entry == NULL) || (entry->rsc == NULL) || (entry->rsc->id == NULL)) { continue; } // Entry must have a node unless listing inactive resources CRM_LOG_ASSERT((node_names == NULL) || (entry->node != NULL)); if ((node_names != NULL) && (entry->node == NULL)) { continue; } // Don't add duplicates of a particular clone instance if (pcmk__str_eq(entry->rsc->id, last_rsc_id, pcmk__str_none)) { continue; } last_rsc_id = entry->rsc->id; if (rsc_names != NULL) { pcmk__add_word(rsc_names, 1024, entry->rsc->id); } if ((node_names != NULL) && (entry->node->details->uname != NULL)) { pcmk__add_word(node_names, 1024, entry->node->details->uname); } } // If there are no entries, return "empty" lists if ((rsc_names != NULL) && (*rsc_names == NULL)) { *rsc_names = g_string_new(" "); } if ((node_names != NULL) && (*node_names == NULL)) { *node_names = g_string_new(" "); } return list; } /*! * \internal * \brief Copy a meta-attribute into a notify action * * \param[in] key Name of meta-attribute to copy * \param[in] value Value of meta-attribute to copy * \param[in,out] user_data Notify action to copy into */ static void copy_meta_to_notify(gpointer key, gpointer value, gpointer user_data) { pcmk_action_t *notify = (pcmk_action_t *) user_data; /* Any existing meta-attributes (for example, the action timeout) are for * the notify action itself, so don't override those. */ if (g_hash_table_lookup(notify->meta, (const char *) key) != NULL) { return; } pcmk__insert_dup(notify->meta, (const char *) key, (const char *) value); } static void add_notify_data_to_action_meta(const notify_data_t *n_data, pcmk_action_t *action) { for (const GSList *item = n_data->keys; item; item = item->next) { const pcmk_nvpair_t *nvpair = (const pcmk_nvpair_t *) item->data; - add_hash_param(action->meta, nvpair->name, nvpair->value); + pcmk__insert_meta(action, nvpair->name, nvpair->value); } } /*! * \internal * \brief Create a new notify pseudo-action for a clone resource * * \param[in,out] rsc Clone resource that notification is for * \param[in] action Action to use in notify action key * \param[in] notif_action PCMK_ACTION_NOTIFY or PCMK_ACTION_NOTIFIED * \param[in] notif_type "pre", "post", "confirmed-pre", "confirmed-post" * * \return Newly created notify pseudo-action */ static pcmk_action_t * new_notify_pseudo_action(pcmk_resource_t *rsc, const pcmk_action_t *action, const char *notif_action, const char *notif_type) { pcmk_action_t *notify = NULL; notify = custom_action(rsc, pcmk__notify_key(rsc->id, notif_type, action->task), notif_action, NULL, pcmk_is_set(action->flags, pcmk_action_optional), rsc->cluster); pcmk__set_action_flags(notify, pcmk_action_pseudo); - add_hash_param(notify->meta, "notify_key_type", notif_type); - add_hash_param(notify->meta, "notify_key_operation", action->task); + pcmk__insert_meta(notify, "notify_key_type", notif_type); + pcmk__insert_meta(notify, "notify_key_operation", action->task); return notify; } /*! * \internal * \brief Create a new notify action for a clone instance * * \param[in,out] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in,out] op Action that notification is for * \param[in,out] notify_done Parent pseudo-action for notifications complete * \param[in] n_data Notification values to add to action meta-data * * \return Newly created notify action */ static pcmk_action_t * new_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_action_t *op, pcmk_action_t *notify_done, const notify_data_t *n_data) { char *key = NULL; pcmk_action_t *notify_action = NULL; const char *value = NULL; const char *task = NULL; const char *skip_reason = NULL; CRM_CHECK((rsc != NULL) && (node != NULL), return NULL); // Ensure we have all the info we need if (op == NULL) { skip_reason = "no action"; } else if (notify_done == NULL) { skip_reason = "no parent notification"; } else if (!node->details->online) { skip_reason = "node offline"; } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) { skip_reason = "original action not runnable"; } if (skip_reason != NULL) { pcmk__rsc_trace(rsc, "Skipping notify action for %s on %s: %s", rsc->id, pcmk__node_name(node), skip_reason); return NULL; } value = g_hash_table_lookup(op->meta, "notify_type"); // "pre" or "post" task = g_hash_table_lookup(op->meta, "notify_operation"); // original action pcmk__rsc_trace(rsc, "Creating notify action for %s on %s (%s-%s)", rsc->id, pcmk__node_name(node), value, task); // Create the notify action key = pcmk__notify_key(rsc->id, value, task); notify_action = custom_action(rsc, key, op->task, node, pcmk_is_set(op->flags, pcmk_action_optional), rsc->cluster); // Add meta-data to notify action g_hash_table_foreach(op->meta, copy_meta_to_notify, notify_action); add_notify_data_to_action_meta(n_data, notify_action); // Order notify after original action and before parent notification order_actions(op, notify_action, pcmk__ar_ordered); order_actions(notify_action, notify_done, pcmk__ar_ordered); return notify_action; } /*! * \internal * \brief Create a new "post-" notify action for a clone instance * * \param[in,out] rsc Clone instance that notification is for * \param[in] node Node that notification is for * \param[in,out] n_data Notification values to add to action meta-data */ static void new_post_notify_action(pcmk_resource_t *rsc, const pcmk_node_t *node, notify_data_t *n_data) { pcmk_action_t *notify = NULL; CRM_ASSERT(n_data != NULL); // Create the "post-" notify action for specified instance notify = new_notify_action(rsc, node, n_data->post, n_data->post_done, n_data); if (notify != NULL) { notify->priority = INFINITY; } // Order recurring monitors after all "post-" notifications complete if (n_data->post_done == NULL) { return; } for (GList *iter = rsc->actions; iter != NULL; iter = iter->next) { pcmk_action_t *mon = (pcmk_action_t *) iter->data; const char *interval_ms_s = NULL; interval_ms_s = g_hash_table_lookup(mon->meta, PCMK_META_INTERVAL); if (pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches) || pcmk__str_eq(mon->task, PCMK_ACTION_CANCEL, pcmk__str_none)) { continue; // Not a recurring monitor } order_actions(n_data->post_done, mon, pcmk__ar_ordered); } } /*! * \internal * \brief Create and order notification pseudo-actions for a clone action * * In addition to the actual notify actions needed for each clone instance, * clone notifications also require pseudo-actions to provide ordering points * in the notification process. This creates the notification data, along with * appropriate pseudo-actions and their orderings. * * For example, the ordering sequence for starting a clone is: * * "pre-" notify pseudo-action for clone * -> "pre-" notify actions for each clone instance * -> "pre-" notifications complete pseudo-action for clone * -> start actions for each clone instance * -> "started" pseudo-action for clone * -> "post-" notify pseudo-action for clone * -> "post-" notify actions for each clone instance * -> "post-" notifications complete pseudo-action for clone * * \param[in,out] rsc Clone that notifications are for * \param[in] task Name of action that notifications are for * \param[in,out] action If not NULL, create a "pre-" pseudo-action ordered * before a "pre-" complete pseudo-action, ordered * before this action * \param[in,out] complete If not NULL, create a "post-" pseudo-action ordered * after this action, and a "post-" complete * pseudo-action ordered after that * * \return Newly created notification data */ notify_data_t * pe__action_notif_pseudo_ops(pcmk_resource_t *rsc, const char *task, pcmk_action_t *action, pcmk_action_t *complete) { notify_data_t *n_data = NULL; if (!pcmk_is_set(rsc->flags, pcmk_rsc_notify)) { return NULL; } n_data = calloc(1, sizeof(notify_data_t)); CRM_ASSERT(n_data != NULL); n_data->action = task; if (action != NULL) { // Need "pre-" pseudo-actions // Create "pre-" notify pseudo-action for clone n_data->pre = new_notify_pseudo_action(rsc, action, PCMK_ACTION_NOTIFY, "pre"); pcmk__set_action_flags(n_data->pre, pcmk_action_runnable); - add_hash_param(n_data->pre->meta, "notify_type", "pre"); - add_hash_param(n_data->pre->meta, "notify_operation", n_data->action); + pcmk__insert_meta(n_data->pre, "notify_type", "pre"); + pcmk__insert_meta(n_data->pre, "notify_operation", n_data->action); // Create "pre-" notifications complete pseudo-action for clone n_data->pre_done = new_notify_pseudo_action(rsc, action, PCMK_ACTION_NOTIFIED, "confirmed-pre"); pcmk__set_action_flags(n_data->pre_done, pcmk_action_runnable); - add_hash_param(n_data->pre_done->meta, "notify_type", "pre"); - add_hash_param(n_data->pre_done->meta, - "notify_operation", n_data->action); + pcmk__insert_meta(n_data->pre_done, "notify_type", "pre"); + pcmk__insert_meta(n_data->pre_done, "notify_operation", n_data->action); // Order "pre-" -> "pre-" complete -> original action order_actions(n_data->pre, n_data->pre_done, pcmk__ar_ordered); order_actions(n_data->pre_done, action, pcmk__ar_ordered); } if (complete != NULL) { // Need "post-" pseudo-actions // Create "post-" notify pseudo-action for clone n_data->post = new_notify_pseudo_action(rsc, complete, PCMK_ACTION_NOTIFY, "post"); n_data->post->priority = INFINITY; if (pcmk_is_set(complete->flags, pcmk_action_runnable)) { pcmk__set_action_flags(n_data->post, pcmk_action_runnable); } else { pcmk__clear_action_flags(n_data->post, pcmk_action_runnable); } - add_hash_param(n_data->post->meta, "notify_type", "post"); - add_hash_param(n_data->post->meta, "notify_operation", n_data->action); + pcmk__insert_meta(n_data->post, "notify_type", "post"); + pcmk__insert_meta(n_data->post, "notify_operation", n_data->action); // Create "post-" notifications complete pseudo-action for clone n_data->post_done = new_notify_pseudo_action(rsc, complete, PCMK_ACTION_NOTIFIED, "confirmed-post"); n_data->post_done->priority = INFINITY; if (pcmk_is_set(complete->flags, pcmk_action_runnable)) { pcmk__set_action_flags(n_data->post_done, pcmk_action_runnable); } else { pcmk__clear_action_flags(n_data->post_done, pcmk_action_runnable); } - add_hash_param(n_data->post_done->meta, "notify_type", "post"); - add_hash_param(n_data->post_done->meta, - "notify_operation", n_data->action); + pcmk__insert_meta(n_data->post_done, "notify_type", "post"); + pcmk__insert_meta(n_data->post_done, + "notify_operation", n_data->action); // Order original action complete -> "post-" -> "post-" complete order_actions(complete, n_data->post, pcmk__ar_first_implies_then); order_actions(n_data->post, n_data->post_done, pcmk__ar_first_implies_then); } // If we created both, order "pre-" complete -> "post-" if ((action != NULL) && (complete != NULL)) { order_actions(n_data->pre_done, n_data->post, pcmk__ar_ordered); } return n_data; } /*! * \internal * \brief Create a new notification entry * * \param[in] rsc Resource for notification * \param[in] node Node for notification * * \return Newly allocated notification entry * \note The caller is responsible for freeing the return value. */ static notify_entry_t * new_notify_entry(const pcmk_resource_t *rsc, const pcmk_node_t *node) { notify_entry_t *entry = calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(entry != NULL); entry->rsc = rsc; entry->node = node; return entry; } /*! * \internal * \brief Add notification data for resource state and optionally actions * * \param[in] rsc Clone or clone instance being notified * \param[in] activity Whether to add notification entries for actions * \param[in,out] n_data Notification data for clone */ static void collect_resource_data(const pcmk_resource_t *rsc, bool activity, notify_data_t *n_data) { const GList *iter = NULL; notify_entry_t *entry = NULL; const pcmk_node_t *node = NULL; if (n_data == NULL) { return; } if (n_data->allowed_nodes == NULL) { n_data->allowed_nodes = rsc->allowed_nodes; } // If this is a clone, call recursively for each instance if (rsc->children != NULL) { for (iter = rsc->children; iter != NULL; iter = iter->next) { const pcmk_resource_t *child = (const pcmk_resource_t *) iter->data; collect_resource_data(child, activity, n_data); } return; } // This is a notification for a single clone instance if (rsc->running_on != NULL) { node = rsc->running_on->data; // First is sufficient } entry = new_notify_entry(rsc, node); // Add notification indicating the resource state switch (rsc->role) { case pcmk_role_stopped: n_data->inactive = g_list_prepend(n_data->inactive, entry); break; case pcmk_role_started: n_data->active = g_list_prepend(n_data->active, entry); break; case pcmk_role_unpromoted: n_data->unpromoted = g_list_prepend(n_data->unpromoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; case pcmk_role_promoted: n_data->promoted = g_list_prepend(n_data->promoted, entry); n_data->active = g_list_prepend(n_data->active, dup_notify_entry(entry)); break; default: pcmk__sched_err("Resource %s role on %s (%s) is not supported for " "notifications (bug?)", rsc->id, pcmk__node_name(node), pcmk_role_text(rsc->role)); free(entry); break; } if (!activity) { return; } // Add notification entries for each of the resource's actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { const pcmk_action_t *op = (const pcmk_action_t *) iter->data; if (!pcmk_is_set(op->flags, pcmk_action_optional) && (op->node != NULL)) { enum action_tasks task = pcmk_parse_action(op->task); if ((task == pcmk_action_stop) && op->node->details->unclean) { // Create anyway (additional noise if node can't be fenced) } else if (!pcmk_is_set(op->flags, pcmk_action_runnable)) { continue; } entry = new_notify_entry(rsc, op->node); switch (task) { case pcmk_action_start: n_data->start = g_list_prepend(n_data->start, entry); break; case pcmk_action_stop: n_data->stop = g_list_prepend(n_data->stop, entry); break; case pcmk_action_promote: n_data->promote = g_list_prepend(n_data->promote, entry); break; case pcmk_action_demote: n_data->demote = g_list_prepend(n_data->demote, entry); break; default: free(entry); break; } } } } // For (char *) value #define add_notify_env(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, value); \ } while (0) // For (GString *) value #define add_notify_env_gs(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \ (const char *) value->str); \ } while (0) // For (GString *) value #define add_notify_env_free_gs(n_data, key, value) do { \ n_data->keys = pcmk_prepend_nvpair(n_data->keys, key, \ (const char *) value->str); \ g_string_free(value, TRUE); value = NULL; \ } while (0) /*! * \internal * \brief Create notification name/value pairs from structured data * * \param[in] rsc Resource that notification is for * \param[in,out] n_data Notification data */ static void add_notif_keys(const pcmk_resource_t *rsc, notify_data_t *n_data) { bool required = false; // Whether to make notify actions required GString *rsc_list = NULL; GString *node_list = NULL; GString *metal_list = NULL; const char *source = NULL; GList *nodes = NULL; n_data->stop = notify_entries_to_strings(n_data->stop, &rsc_list, &node_list); if ((strcmp(" ", (const char *) rsc_list->str) != 0) && pcmk__str_eq(n_data->action, PCMK_ACTION_STOP, pcmk__str_none)) { required = true; } add_notify_env_free_gs(n_data, "notify_stop_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_stop_uname", node_list); if ((n_data->start != NULL) && pcmk__str_eq(n_data->action, PCMK_ACTION_START, pcmk__str_none)) { required = true; } n_data->start = notify_entries_to_strings(n_data->start, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_start_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_start_uname", node_list); if ((n_data->demote != NULL) && pcmk__str_eq(n_data->action, PCMK_ACTION_DEMOTE, pcmk__str_none)) { required = true; } n_data->demote = notify_entries_to_strings(n_data->demote, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_demote_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_demote_uname", node_list); if ((n_data->promote != NULL) && pcmk__str_eq(n_data->action, PCMK_ACTION_PROMOTE, pcmk__str_none)) { required = true; } n_data->promote = notify_entries_to_strings(n_data->promote, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_promote_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_promote_uname", node_list); n_data->active = notify_entries_to_strings(n_data->active, &rsc_list, &node_list); add_notify_env_free_gs(n_data, "notify_active_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_active_uname", node_list); n_data->unpromoted = notify_entries_to_strings(n_data->unpromoted, &rsc_list, &node_list); add_notify_env_gs(n_data, "notify_unpromoted_resource", rsc_list); add_notify_env_gs(n_data, "notify_unpromoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free_gs(n_data, "notify_slave_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_slave_uname", node_list); n_data->promoted = notify_entries_to_strings(n_data->promoted, &rsc_list, &node_list); add_notify_env_gs(n_data, "notify_promoted_resource", rsc_list); add_notify_env_gs(n_data, "notify_promoted_uname", node_list); // Deprecated: kept for backward compatibility with older resource agents add_notify_env_free_gs(n_data, "notify_master_resource", rsc_list); add_notify_env_free_gs(n_data, "notify_master_uname", node_list); n_data->inactive = notify_entries_to_strings(n_data->inactive, &rsc_list, NULL); add_notify_env_free_gs(n_data, "notify_inactive_resource", rsc_list); nodes = g_hash_table_get_values(n_data->allowed_nodes); if (!pcmk__is_daemon) { /* For display purposes, sort the node list, for consistent * regression test output (while avoiding the performance hit * for the live cluster). */ nodes = g_list_sort(nodes, pe__cmp_node_name); } get_node_names(nodes, &node_list, NULL); add_notify_env_free_gs(n_data, "notify_available_uname", node_list); g_list_free(nodes); source = g_hash_table_lookup(rsc->meta, PCMK_META_CONTAINER_ATTRIBUTE_TARGET); if (pcmk__str_eq(PCMK_VALUE_HOST, source, pcmk__str_none)) { get_node_names(rsc->cluster->nodes, &node_list, &metal_list); add_notify_env_free_gs(n_data, "notify_all_hosts", metal_list); } else { get_node_names(rsc->cluster->nodes, &node_list, NULL); } add_notify_env_free_gs(n_data, "notify_all_uname", node_list); if (required && (n_data->pre != NULL)) { pcmk__clear_action_flags(n_data->pre, pcmk_action_optional); pcmk__clear_action_flags(n_data->pre_done, pcmk_action_optional); } if (required && (n_data->post != NULL)) { pcmk__clear_action_flags(n_data->post, pcmk_action_optional); pcmk__clear_action_flags(n_data->post_done, pcmk_action_optional); } } /* * \internal * \brief Find any remote connection start relevant to an action * * \param[in] action Action to check * * \return If action is behind a remote connection, connection's start */ static pcmk_action_t * find_remote_start(pcmk_action_t *action) { if ((action != NULL) && (action->node != NULL)) { pcmk_resource_t *remote_rsc = action->node->details->remote_rsc; if (remote_rsc != NULL) { return find_first_action(remote_rsc->actions, NULL, PCMK_ACTION_START, NULL); } } return NULL; } /*! * \internal * \brief Create notify actions, and add notify data to original actions * * \param[in,out] rsc Clone or clone instance that notification is for * \param[in,out] n_data Clone notification data for some action */ static void create_notify_actions(pcmk_resource_t *rsc, notify_data_t *n_data) { GList *iter = NULL; pcmk_action_t *stop = NULL; pcmk_action_t *start = NULL; enum action_tasks task = pcmk_parse_action(n_data->action); // If this is a clone, call recursively for each instance if (rsc->children != NULL) { g_list_foreach(rsc->children, (GFunc) create_notify_actions, n_data); return; } // Add notification meta-attributes to original actions for (iter = rsc->actions; iter != NULL; iter = iter->next) { pcmk_action_t *op = (pcmk_action_t *) iter->data; if (!pcmk_is_set(op->flags, pcmk_action_optional) && (op->node != NULL)) { switch (pcmk_parse_action(op->task)) { case pcmk_action_start: case pcmk_action_stop: case pcmk_action_promote: case pcmk_action_demote: add_notify_data_to_action_meta(n_data, op); break; default: break; } } } // Skip notify action itself if original action was not needed switch (task) { case pcmk_action_start: if (n_data->start == NULL) { pcmk__rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case pcmk_action_promote: if (n_data->promote == NULL) { pcmk__rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; case pcmk_action_demote: if (n_data->demote == NULL) { pcmk__rsc_trace(rsc, "No notify action needed for %s %s", rsc->id, n_data->action); return; } break; default: // We cannot do same for stop because it might be implied by fencing break; } pcmk__rsc_trace(rsc, "Creating notify actions for %s %s", rsc->id, n_data->action); // Create notify actions for stop or demote if ((rsc->role != pcmk_role_stopped) && ((task == pcmk_action_stop) || (task == pcmk_action_demote))) { stop = find_first_action(rsc->actions, NULL, PCMK_ACTION_STOP, NULL); for (iter = rsc->running_on; iter != NULL; iter = iter->next) { pcmk_node_t *current_node = (pcmk_node_t *) iter->data; /* If a stop is a pseudo-action implied by fencing, don't try to * notify the node getting fenced. */ if ((stop != NULL) && pcmk_is_set(stop->flags, pcmk_action_pseudo) && (current_node->details->unclean || current_node->details->remote_requires_reset)) { continue; } new_notify_action(rsc, current_node, n_data->pre, n_data->pre_done, n_data); if ((task == pcmk_action_demote) || (stop == NULL) || pcmk_is_set(stop->flags, pcmk_action_optional)) { new_post_notify_action(rsc, current_node, n_data); } } } // Create notify actions for start or promote if ((rsc->next_role != pcmk_role_stopped) && ((task == pcmk_action_start) || (task == pcmk_action_promote))) { start = find_first_action(rsc->actions, NULL, PCMK_ACTION_START, NULL); if (start != NULL) { pcmk_action_t *remote_start = find_remote_start(start); if ((remote_start != NULL) && !pcmk_is_set(remote_start->flags, pcmk_action_runnable)) { /* Start and promote actions for a clone instance behind * a Pacemaker Remote connection happen after the * connection starts. If the connection start is blocked, do * not schedule notifications for these actions. */ return; } } if (rsc->allocated_to == NULL) { pcmk__sched_err("Next role '%s' but %s is not allocated", pcmk_role_text(rsc->next_role), rsc->id); return; } if ((task != pcmk_action_start) || (start == NULL) || pcmk_is_set(start->flags, pcmk_action_optional)) { new_notify_action(rsc, rsc->allocated_to, n_data->pre, n_data->pre_done, n_data); } new_post_notify_action(rsc, rsc->allocated_to, n_data); } } /*! * \internal * \brief Create notification data and actions for one clone action * * \param[in,out] rsc Clone resource that notification is for * \param[in,out] n_data Clone notification data for some action */ void pe__create_action_notifications(pcmk_resource_t *rsc, notify_data_t *n_data) { if ((rsc == NULL) || (n_data == NULL)) { return; } collect_resource_data(rsc, true, n_data); add_notif_keys(rsc, n_data); create_notify_actions(rsc, n_data); } /*! * \internal * \brief Free notification data for one action * * \param[in,out] n_data Notification data to free */ void pe__free_action_notification_data(notify_data_t *n_data) { if (n_data == NULL) { return; } g_list_free_full(n_data->stop, free); g_list_free_full(n_data->start, free); g_list_free_full(n_data->demote, free); g_list_free_full(n_data->promote, free); g_list_free_full(n_data->promoted, free); g_list_free_full(n_data->unpromoted, free); g_list_free_full(n_data->active, free); g_list_free_full(n_data->inactive, free); pcmk_free_nvpairs(n_data->keys); free(n_data); } /*! * \internal * \brief Order clone "notifications complete" pseudo-action after fencing * * If a stop action is implied by fencing, the usual notification pseudo-actions * will not be sufficient to order things properly, or even create all needed * notifications if the clone is also stopping on another node, and another * clone is ordered after it. This function creates new notification * pseudo-actions relative to the fencing to ensure everything works properly. * * \param[in] stop Stop action implied by fencing * \param[in,out] rsc Clone resource that notification is for * \param[in,out] stonith_op Fencing action that implies \p stop */ void pe__order_notifs_after_fencing(const pcmk_action_t *stop, pcmk_resource_t *rsc, pcmk_action_t *stonith_op) { notify_data_t *n_data; crm_info("Ordering notifications for implied %s after fencing", stop->uuid); n_data = pe__action_notif_pseudo_ops(rsc, PCMK_ACTION_STOP, NULL, stonith_op); if (n_data != NULL) { collect_resource_data(rsc, false, n_data); add_notify_env(n_data, "notify_stop_resource", rsc->id); add_notify_env(n_data, "notify_stop_uname", stop->node->details->uname); create_notify_actions(uber_parent(rsc), n_data); pe__free_action_notification_data(n_data); } }