diff --git a/include/crm/pengine/complex.h b/include/crm/pengine/complex.h index 98371722f5..97be597852 100644 --- a/include/crm/pengine/complex.h +++ b/include/crm/pengine/complex.h @@ -1,42 +1,42 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_PENGINE_COMPLEX__H # define PCMK__CRM_PENGINE_COMPLEX__H #include // gboolean, GHashTable #include // xmlNode #include // pe_node_t, pe_resource_t, etc. #ifdef __cplusplus extern "C" { #endif extern resource_object_functions_t resource_class_functions[]; -GHashTable *pe_rsc_params(pe_resource_t *rsc, pe_node_t *node, +GHashTable *pe_rsc_params(pe_resource_t *rsc, const pe_node_t *node, pe_working_set_t *data_set); void get_meta_attributes(GHashTable * meta_hash, pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set); -void get_rsc_attributes(GHashTable *meta_hash, pe_resource_t *rsc, - pe_node_t *node, pe_working_set_t *data_set); +void get_rsc_attributes(GHashTable *meta_hash, const pe_resource_t *rsc, + const pe_node_t *node, pe_working_set_t *data_set); #if ENABLE_VERSIONED_ATTRS void pe_get_versioned_attributes(xmlNode *meta_hash, pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set); #endif gboolean is_parent(pe_resource_t *child, pe_resource_t *rsc); pe_resource_t *uber_parent(pe_resource_t *rsc); #ifdef __cplusplus } #endif #endif diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index 77815f0894..fdafdb1d33 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -1,618 +1,618 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PE_INTERNAL__H # define PE_INTERNAL__H # include # include # include # include # include # include # include enum pe__clone_flags { // Whether instances should be started sequentially pe__clone_ordered = (1 << 0), // Whether promotion scores have been added pe__clone_promotion_added = (1 << 1), // Whether promotion constraints have been added pe__clone_promotion_constrained = (1 << 2), }; bool pe__clone_is_ordered(pe_resource_t *clone); int pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag); # define pe_rsc_info(rsc, fmt, args...) crm_log_tag(LOG_INFO, rsc ? rsc->id : "", fmt, ##args) # define pe_rsc_debug(rsc, fmt, args...) crm_log_tag(LOG_DEBUG, rsc ? rsc->id : "", fmt, ##args) # define pe_rsc_trace(rsc, fmt, args...) crm_log_tag(LOG_TRACE, rsc ? rsc->id : "", fmt, ##args) # define pe_err(fmt...) do { \ was_processing_error = TRUE; \ pcmk__config_err(fmt); \ } while (0) # define pe_warn(fmt...) do { \ was_processing_warning = TRUE; \ pcmk__config_warn(fmt); \ } while (0) # define pe_proc_err(fmt...) { was_processing_error = TRUE; crm_err(fmt); } # define pe_proc_warn(fmt...) { was_processing_warning = TRUE; crm_warn(fmt); } #define pe__set_working_set_flags(working_set, flags_to_set) do { \ (working_set)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Working set", crm_system_name, \ (working_set)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_working_set_flags(working_set, flags_to_clear) do { \ (working_set)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Working set", crm_system_name, \ (working_set)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) #define pe__set_resource_flags(resource, flags_to_set) do { \ (resource)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \ (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_resource_flags(resource, flags_to_clear) do { \ (resource)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \ (flags_to_clear), #flags_to_clear); \ } while (0) #define pe__set_action_flags(action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_action_flags(action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_raw_action_flags(action_flags, action_name, flags_to_set) do { \ action_flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "Action", action_name, \ (action_flags), \ (flags_to_set), #flags_to_set); \ } while (0) #define pe__clear_raw_action_flags(action_flags, action_name, flags_to_clear) do { \ action_flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", action_name, \ (action_flags), \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_action_flags_as(function, line, action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as((function), (line), \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_action_flags_as(function, line, action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as((function), (line), \ LOG_TRACE, \ "Action", (action)->uuid, \ (action)->flags, \ (flags_to_clear), \ #flags_to_clear); \ } while (0) #define pe__set_order_flags(order_flags, flags_to_set) do { \ order_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Ordering", "constraint", \ order_flags, (flags_to_set), \ #flags_to_set); \ } while (0) #define pe__clear_order_flags(order_flags, flags_to_clear) do { \ order_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \ "Ordering", "constraint", \ order_flags, (flags_to_clear), \ #flags_to_clear); \ } while (0) // Some warnings we don't want to print every transition enum pe_warn_once_e { pe_wo_blind = (1 << 0), pe_wo_restart_type = (1 << 1), pe_wo_role_after = (1 << 2), pe_wo_poweroff = (1 << 3), pe_wo_require_all = (1 << 4), pe_wo_order_score = (1 << 5), pe_wo_neg_threshold = (1 << 6), pe_wo_remove_after = (1 << 7), pe_wo_ping_node = (1 << 8), }; extern uint32_t pe_wo; #define pe_warn_once(pe_wo_bit, fmt...) do { \ if (!pcmk_is_set(pe_wo, pe_wo_bit)) { \ if (pe_wo_bit == pe_wo_blind) { \ crm_warn(fmt); \ } else { \ pe_warn(fmt); \ } \ pe_wo = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Warn-once", "logging", pe_wo, \ (pe_wo_bit), #pe_wo_bit); \ } \ } while (0); typedef struct pe__location_constraint_s { char *id; // Constraint XML ID pe_resource_t *rsc_lh; // Resource being located enum rsc_role_e role_filter; // Role to locate enum pe_discover_e discover_mode; // Resource discovery GList *node_list_rh; // List of pe_node_t* } pe__location_t; typedef struct pe__order_constraint_s { int id; enum pe_ordering type; void *lh_opaque; pe_resource_t *lh_rsc; pe_action_t *lh_action; char *lh_action_task; void *rh_opaque; pe_resource_t *rh_rsc; pe_action_t *rh_action; char *rh_action_task; } pe__ordering_t; typedef struct notify_data_s { GSList *keys; // Environment variable name/value pairs const char *action; pe_action_t *pre; pe_action_t *post; pe_action_t *pre_done; pe_action_t *post_done; GList *active; /* notify_entry_t* */ GList *inactive; /* notify_entry_t* */ GList *start; /* notify_entry_t* */ GList *stop; /* notify_entry_t* */ GList *demote; /* notify_entry_t* */ GList *promote; /* notify_entry_t* */ GList *promoted; /* notify_entry_t* */ GList *unpromoted; /* notify_entry_t* */ GHashTable *allowed_nodes; } notify_data_t; int pe__clone_promoted_max(pe_resource_t *clone); int pe__clone_promoted_node_max(pe_resource_t *clone); pe_action_t *pe__new_rsc_pseudo_action(pe_resource_t *rsc, const char *task, bool optional, bool runnable); void pe__create_promotable_pseudo_ops(pe_resource_t *clone, bool any_promoting, bool any_demoting); bool pe_can_fence(pe_working_set_t *data_set, pe_node_t *node); void add_hash_param(GHashTable * hash, const char *name, const char *value); char *native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name, pe_working_set_t * data_set); pe_node_t *native_location(const pe_resource_t *rsc, GList **list, int current); void pe_metadata(pcmk__output_t *out); void verify_pe_options(GHashTable * options); void common_update_score(pe_resource_t * rsc, const char *id, int score); void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set, gboolean failed); gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set); gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set); pe_resource_t *native_find_rsc(pe_resource_t *rsc, const char *id, const pe_node_t *node, int flags); gboolean native_active(pe_resource_t * rsc, gboolean all); gboolean group_active(pe_resource_t * rsc, gboolean all); gboolean clone_active(pe_resource_t * rsc, gboolean all); gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all); void native_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void group_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void clone_print(pe_resource_t * rsc, const char *pre_text, long options, void *print_data); void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); gchar * pcmk__native_output_string(pe_resource_t *rsc, const char *name, pe_node_t *node, uint32_t show_opts, const char *target_role, bool show_nodes); int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name , size_t pairs_count, ...); char *pe__node_display_name(pe_node_t *node, bool print_detail); // Clone notifications (pe_notif.c) void pe__create_notifications(pe_resource_t *rsc, notify_data_t *n_data); notify_data_t *pe__clone_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete); void pe__free_notification_data(notify_data_t *n_data); void pe__order_notifs_after_fencing(pe_action_t *action, pe_resource_t *rsc, pe_action_t *stonith_op); static inline const char * pe__rsc_bool_str(pe_resource_t *rsc, uint64_t rsc_flag) { return pcmk__btoa(pcmk_is_set(rsc->flags, rsc_flag)); } int pe__clone_xml(pcmk__output_t *out, va_list args); int pe__clone_default(pcmk__output_t *out, va_list args); int pe__group_xml(pcmk__output_t *out, va_list args); int pe__group_default(pcmk__output_t *out, va_list args); int pe__bundle_xml(pcmk__output_t *out, va_list args); int pe__bundle_html(pcmk__output_t *out, va_list args); int pe__bundle_text(pcmk__output_t *out, va_list args); int pe__node_html(pcmk__output_t *out, va_list args); int pe__node_text(pcmk__output_t *out, va_list args); int pe__node_xml(pcmk__output_t *out, va_list args); int pe__resource_xml(pcmk__output_t *out, va_list args); int pe__resource_html(pcmk__output_t *out, va_list args); int pe__resource_text(pcmk__output_t *out, va_list args); void native_free(pe_resource_t * rsc); void group_free(pe_resource_t * rsc); void clone_free(pe_resource_t * rsc); void pe__free_bundle(pe_resource_t *rsc); enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e group_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e clone_resource_state(const pe_resource_t * rsc, gboolean current); enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current); void pe__count_common(pe_resource_t *rsc); void pe__count_bundle(pe_resource_t *rsc); void common_free(pe_resource_t * rsc); pe_node_t *pe__copy_node(const pe_node_t *this_node); extern time_t get_effective_time(pe_working_set_t * data_set); /* Failure handling utilities (from failcounts.c) */ // bit flags for fail count handling options enum pe_fc_flags_e { pe_fc_default = (1 << 0), pe_fc_effective = (1 << 1), // don't count expired failures pe_fc_fillers = (1 << 2), // if container, include filler failures in count }; int pe_get_failcount(pe_node_t *node, pe_resource_t *rsc, time_t *last_failure, uint32_t flags, xmlNode *xml_op, pe_working_set_t *data_set); pe_action_t *pe__clear_failcount(pe_resource_t *rsc, pe_node_t *node, const char *reason, pe_working_set_t *data_set); /* Functions for finding/counting a resource's active nodes */ pe_node_t *pe__find_active_on(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); pe_node_t *pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count); static inline pe_node_t * pe__current_node(const pe_resource_t *rsc) { return pe__find_active_on(rsc, NULL, NULL); } /* Binary like operators for lists of nodes */ extern void node_list_exclude(GHashTable * list, GList *list2, gboolean merge_scores); GHashTable *pe__node_list2table(GList *list); static inline gpointer pe_hash_table_lookup(GHashTable * hash, gconstpointer key) { if (hash) { return g_hash_table_lookup(hash, key); } return NULL; } extern pe_action_t *get_pseudo_op(const char *name, pe_working_set_t * data_set); extern gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order); /* Printing functions for debug */ extern void print_str_str(gpointer key, gpointer value, gpointer user_data); extern void pe__output_node(pe_node_t * node, gboolean details, pcmk__output_t *out); void pe__show_node_weights_as(const char *file, const char *function, int line, bool to_log, pe_resource_t *rsc, const char *comment, GHashTable *nodes, pe_working_set_t *data_set); #define pe__show_node_weights(level, rsc, text, nodes, data_set) \ pe__show_node_weights_as(__FILE__, __func__, __LINE__, \ (level), (rsc), (text), (nodes), (data_set)) xmlNode *find_rsc_op_entry(const pe_resource_t *rsc, const char *key); pe_action_t *custom_action(pe_resource_t *rsc, char *key, const char *task, const pe_node_t *on_node, gboolean optional, gboolean foo, pe_working_set_t *data_set); # define delete_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DELETE, 0) # define delete_action(rsc, node, optional) custom_action( \ rsc, delete_key(rsc), CRMD_ACTION_DELETE, node, \ optional, TRUE, rsc->cluster); # define stopped_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOPPED, 0) # define stopped_action(rsc, node, optional) custom_action( \ rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node, \ optional, TRUE, rsc->cluster); # define stop_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOP, 0) # define stop_action(rsc, node, optional) custom_action( \ rsc, stop_key(rsc), CRMD_ACTION_STOP, node, \ optional, TRUE, rsc->cluster); # define reload_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_RELOAD_AGENT, 0) # define start_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_START, 0) # define start_action(rsc, node, optional) custom_action( \ rsc, start_key(rsc), CRMD_ACTION_START, node, \ optional, TRUE, rsc->cluster) # define started_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STARTED, 0) # define started_action(rsc, node, optional) custom_action( \ rsc, started_key(rsc), CRMD_ACTION_STARTED, node, \ optional, TRUE, rsc->cluster) # define promote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTE, 0) # define promote_action(rsc, node, optional) custom_action( \ rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node, \ optional, TRUE, rsc->cluster) # define promoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTED, 0) # define promoted_action(rsc, node, optional) custom_action( \ rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node, \ optional, TRUE, rsc->cluster) # define demote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTE, 0) # define demote_action(rsc, node, optional) custom_action( \ rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node, \ optional, TRUE, rsc->cluster) # define demoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTED, 0) # define demoted_action(rsc, node, optional) custom_action( \ rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node, \ optional, TRUE, rsc->cluster) extern int pe_get_configured_timeout(pe_resource_t *rsc, const char *action, pe_working_set_t *data_set); extern pe_action_t *find_first_action(GList *input, const char *uuid, const char *task, pe_node_t * on_node); extern enum action_tasks get_complex_task(pe_resource_t * rsc, const char *name, gboolean allow_non_atomic); extern GList *find_actions(GList *input, const char *key, const pe_node_t *on_node); GList *find_actions_exact(GList *input, const char *key, const pe_node_t *on_node); GList *pe__resource_actions(const pe_resource_t *rsc, const pe_node_t *node, const char *task, bool require_node); extern void pe_free_action(pe_action_t * action); extern void resource_location(pe_resource_t * rsc, pe_node_t * node, int score, const char *tag, pe_working_set_t * data_set); extern int pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b, bool same_node_default); extern gint sort_op_by_callid(gconstpointer a, gconstpointer b); extern gboolean get_target_role(pe_resource_t * rsc, enum rsc_role_e *role); void pe__set_next_role(pe_resource_t *rsc, enum rsc_role_e role, const char *why); extern pe_resource_t *find_clone_instance(pe_resource_t * rsc, const char *sub_id, pe_working_set_t * data_set); extern void destroy_ticket(gpointer data); extern pe_ticket_t *ticket_new(const char *ticket_id, pe_working_set_t * data_set); // Resources for manipulating resource names const char *pe_base_name_end(const char *id); char *clone_strip(const char *last_rsc_id); char *clone_zero(const char *last_rsc_id); static inline bool pe_base_name_eq(pe_resource_t *rsc, const char *id) { if (id && rsc && rsc->id) { // Number of characters in rsc->id before any clone suffix size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1; return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len); } return false; } int pe__target_rc_from_xml(xmlNode *xml_op); gint pe__cmp_node_name(gconstpointer a, gconstpointer b); bool is_set_recursive(pe_resource_t * rsc, long long flag, bool any); enum rsc_digest_cmp_val { /*! Digests are the same */ RSC_DIGEST_MATCH = 0, /*! Params that require a restart changed */ RSC_DIGEST_RESTART, /*! Some parameter changed. */ RSC_DIGEST_ALL, /*! rsc op didn't have a digest associated with it, so * it is unknown if parameters changed or not. */ RSC_DIGEST_UNKNOWN, }; typedef struct op_digest_cache_s { enum rsc_digest_cmp_val rc; xmlNode *params_all; xmlNode *params_secure; xmlNode *params_restart; char *digest_all_calc; char *digest_secure_calc; char *digest_restart_calc; } op_digest_cache_t; op_digest_cache_t *pe__calculate_digests(pe_resource_t *rsc, const char *task, guint *interval_ms, pe_node_t *node, xmlNode *xml_op, GHashTable *overrides, bool calc_secure, pe_working_set_t *data_set); void pe__free_digests(gpointer ptr); op_digest_cache_t *rsc_action_digest_cmp(pe_resource_t * rsc, xmlNode * xml_op, pe_node_t * node, pe_working_set_t * data_set); pe_action_t *pe_fence_op(pe_node_t * node, const char *op, bool optional, const char *reason, bool priority_delay, pe_working_set_t * data_set); void trigger_unfencing( pe_resource_t * rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t * data_set); char *pe__action2reason(pe_action_t *action, enum pe_action_flags flag); void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite); void pe__add_action_expected_result(pe_action_t *action, int expected_result); void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags); void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags); void pe__clear_resource_flags_on_all(pe_working_set_t *data_set, uint64_t flag); gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref); void print_rscs_brief(GList *rsc_list, const char * pre_text, long options, void * print_data, gboolean print_all); int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, unsigned int options); void pe_fence_node(pe_working_set_t * data_set, pe_node_t * node, const char *reason, bool priority_delay); pe_node_t *pe_create_node(const char *id, const char *uname, const char *type, const char *score, pe_working_set_t * data_set); void common_print(pe_resource_t * rsc, const char *pre_text, const char *name, pe_node_t *node, long options, void *print_data); int pe__common_output_text(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, unsigned int options); int pe__common_output_html(pcmk__output_t *out, pe_resource_t * rsc, const char *name, pe_node_t *node, unsigned int options); pe_resource_t *pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node); bool pe__bundle_needs_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set); const char *pe__add_bundle_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set, xmlNode *xml, const char *field); const char *pe_node_attribute_calculated(const pe_node_t *node, const char *name, const pe_resource_t *rsc); const char *pe_node_attribute_raw(pe_node_t *node, const char *name); bool pe__is_universal_clone(pe_resource_t *rsc, pe_working_set_t *data_set); void pe__add_param_check(xmlNode *rsc_op, pe_resource_t *rsc, pe_node_t *node, enum pe_check_parameters, pe_working_set_t *data_set); void pe__foreach_param_check(pe_working_set_t *data_set, void (*cb)(pe_resource_t*, pe_node_t*, xmlNode*, enum pe_check_parameters, pe_working_set_t*)); void pe__free_param_checks(pe_working_set_t *data_set); bool pe__shutdown_requested(pe_node_t *node); void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set); /*! * \internal * \brief Register xml formatting message functions. */ void pe__register_messages(pcmk__output_t *out); -void pe__unpack_dataset_nvpairs(xmlNode *xml_obj, const char *set_name, +void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, pe_working_set_t *data_set); bool pe__resource_is_disabled(pe_resource_t *rsc); pe_action_t *pe__clear_resource_history(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set); GList *pe__rscs_with_tag(pe_working_set_t *data_set, const char *tag_name); GList *pe__unames_with_tag(pe_working_set_t *data_set, const char *tag_name); bool pe__rsc_has_tag(pe_working_set_t *data_set, const char *rsc, const char *tag); bool pe__uname_has_tag(pe_working_set_t *data_set, const char *node, const char *tag); bool pe__rsc_running_on_any(pe_resource_t *rsc, GList *node_list); GList *pe__filter_rsc_list(GList *rscs, GList *filter); GList * pe__build_node_name_list(pe_working_set_t *data_set, const char *s); GList * pe__build_rsc_list(pe_working_set_t *data_set, const char *s); bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GList *only_node); gboolean pe__bundle_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__clone_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__group_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__native_is_filtered(pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); xmlNode *pe__failed_probe_for_rsc(pe_resource_t *rsc, const char *name); const char *pe__clone_child_id(pe_resource_t *rsc); int pe__sum_node_health_scores(const pe_node_t *node, int base_health); int pe__node_health(pe_node_t *node); static inline enum pcmk__health_strategy pe__health_strategy(pe_working_set_t *data_set) { return pcmk__parse_health_strategy(pe_pref(data_set->config_hash, PCMK__OPT_NODE_HEALTH_STRATEGY)); } static inline int pe__health_score(const char *option, pe_working_set_t *data_set) { return char2score(pe_pref(data_set->config_hash, option)); } #endif diff --git a/include/crm/pengine/rules.h b/include/crm/pengine/rules.h index aaa62625fb..6733990185 100644 --- a/include/crm/pengine/rules.h +++ b/include/crm/pengine/rules.h @@ -1,83 +1,83 @@ /* - * Copyright 2004-2021 the Pacemaker project contributors + * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_PENGINE_RULES__H # define PCMK__CRM_PENGINE_RULES__H # include # include # include # include #ifdef __cplusplus extern "C" { #endif enum expression_type { not_expr, nested_rule, attr_expr, loc_expr, role_expr, time_expr, version_expr, rsc_expr, op_expr }; enum expression_type find_expression_type(xmlNode * expr); gboolean pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now, crm_time_t *next_change); gboolean pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, crm_time_t *next_change, pe_match_data_t *match_data); gboolean pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, crm_time_t *next_change, pe_match_data_t *match_data); -void pe_eval_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, +void pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *next_change); void pe_unpack_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, GHashTable *node_hash, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *now, crm_time_t *next_change); #if ENABLE_VERSIONED_ATTRS -void pe_eval_versioned_attributes(xmlNode *top, xmlNode *xml_obj, +void pe_eval_versioned_attributes(xmlNode *top, const xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, xmlNode *hash, crm_time_t *next_change); GHashTable *pe_unpack_versioned_parameters(xmlNode *versioned_params, const char *ra_version); #endif char *pe_expand_re_matches(const char *string, pe_re_match_data_t * match_data); gboolean pe_eval_rules(xmlNode *ruleset, pe_rule_eval_data_t *rule_data, crm_time_t *next_change); gboolean pe_eval_expr(xmlNode *rule, pe_rule_eval_data_t *rule_data, crm_time_t *next_change); gboolean pe_eval_subexpr(xmlNode *expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change); #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus } #endif #endif diff --git a/include/crm/pengine/status.h b/include/crm/pengine/status.h index 80cb70cb32..dcd0e65c33 100644 --- a/include/crm/pengine/status.h +++ b/include/crm/pengine/status.h @@ -1,106 +1,106 @@ /* - * Copyright 2004-2020 the Pacemaker project contributors + * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_PENGINE_STATUS__H # define PCMK__CRM_PENGINE_STATUS__H # include // gboolean # include // bool # include // pcmk_is_set() # include # include # include // pe_node_t, pe_resource_t, etc. # include #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Cluster status and scheduling * \ingroup pengine */ const char *rsc_printable_id(pe_resource_t *rsc); gboolean cluster_status(pe_working_set_t * data_set); pe_working_set_t *pe_new_working_set(void); void pe_free_working_set(pe_working_set_t *data_set); void set_working_set_defaults(pe_working_set_t * data_set); void cleanup_calculations(pe_working_set_t * data_set); void pe_reset_working_set(pe_working_set_t *data_set); pe_resource_t *pe_find_resource(GList *rsc_list, const char *id_rh); pe_resource_t *pe_find_resource_with_flags(GList *rsc_list, const char *id, enum pe_find flags); pe_node_t *pe_find_node(GList *node_list, const char *uname); pe_node_t *pe_find_node_id(GList *node_list, const char *id); pe_node_t *pe_find_node_any(GList *node_list, const char *id, const char *uname); GList *find_operations(const char *rsc, const char *node, gboolean active_filter, pe_working_set_t * data_set); int pe_bundle_replicas(const pe_resource_t *rsc); #if ENABLE_VERSIONED_ATTRS pe_rsc_action_details_t *pe_rsc_action_details(pe_action_t *action); #endif /*! * \brief Check whether a resource is any clone type * * \param[in] rsc Resource to check * * \return TRUE if resource is clone, FALSE otherwise */ static inline bool -pe_rsc_is_clone(pe_resource_t *rsc) +pe_rsc_is_clone(const pe_resource_t *rsc) { return rsc && (rsc->variant == pe_clone); } /*! * \brief Check whether a resource is a globally unique clone * * \param[in] rsc Resource to check * * \return TRUE if resource is unique clone, FALSE otherwise */ static inline bool pe_rsc_is_unique_clone(pe_resource_t *rsc) { return pe_rsc_is_clone(rsc) && pcmk_is_set(rsc->flags, pe_rsc_unique); } /*! * \brief Check whether a resource is an anonymous clone * * \param[in] rsc Resource to check * * \return TRUE if resource is anonymous clone, FALSE otherwise */ static inline bool pe_rsc_is_anon_clone(pe_resource_t *rsc) { return pe_rsc_is_clone(rsc) && !pcmk_is_set(rsc->flags, pe_rsc_unique); } /*! * \brief Check whether a resource is part of a bundle * * \param[in] rsc Resource to check * * \return TRUE if resource is part of a bundle, FALSE otherwise */ static inline bool pe_rsc_is_bundled(pe_resource_t *rsc) { return uber_parent(rsc)->parent != NULL; } #ifdef __cplusplus } #endif #endif diff --git a/include/pcmki/pcmki_transition.h b/include/pcmki/pcmki_transition.h index 143450080a..af3b925fed 100644 --- a/include/pcmki/pcmki_transition.h +++ b/include/pcmki/pcmki_transition.h @@ -1,165 +1,165 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__PCMKI_PCMKI_TRANSITION__H # define PCMK__PCMKI_PCMKI_TRANSITION__H # include # include # include # include #ifdef __cplusplus extern "C" { #endif enum pcmk__graph_action_type { pcmk__pseudo_graph_action, pcmk__rsc_graph_action, pcmk__cluster_graph_action, }; enum pcmk__synapse_flags { pcmk__synapse_ready = (1 << 0), pcmk__synapse_failed = (1 << 1), pcmk__synapse_executed = (1 << 2), pcmk__synapse_confirmed = (1 << 3), }; typedef struct { int id; int priority; uint32_t flags; // Group of pcmk__synapse_flags GList *actions; /* pcmk__graph_action_t* */ GList *inputs; /* pcmk__graph_action_t* */ } pcmk__graph_synapse_t; #define pcmk__set_synapse_flags(synapse, flags_to_set) do { \ (synapse)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Synapse", "synapse", \ (synapse)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_synapse_flags(synapse, flags_to_clear) do { \ (synapse)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Synapse", "synapse", \ (synapse)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) enum pcmk__graph_action_flags { pcmk__graph_action_sent_update = (1 << 0), /* sent to the CIB */ pcmk__graph_action_executed = (1 << 1), /* sent to the CRM */ pcmk__graph_action_confirmed = (1 << 2), pcmk__graph_action_failed = (1 << 3), pcmk__graph_action_can_fail = (1 << 4), //! \deprecated Will be removed in a future release }; typedef struct { int id; int timeout; int timer; guint interval_ms; GHashTable *params; enum pcmk__graph_action_type type; pcmk__graph_synapse_t *synapse; uint32_t flags; // Group of pcmk__graph_action_flags xmlNode *xml; } pcmk__graph_action_t; #define pcmk__set_graph_action_flags(action, flags_to_set) do { \ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", "action", \ (action)->flags, (flags_to_set), #flags_to_set); \ } while (0) #define pcmk__clear_graph_action_flags(action, flags_to_clear) do { \ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, \ "Action", "action", \ (action)->flags, (flags_to_clear), #flags_to_clear); \ } while (0) // What to do after finished processing a transition graph enum pcmk__graph_next { // Order matters: lowest priority to highest pcmk__graph_done, // Transition complete, nothing further needed pcmk__graph_wait, // Transition interrupted, wait for further changes pcmk__graph_restart, // Transition interrupted, start a new one pcmk__graph_shutdown, // Transition interrupted, local shutdown needed }; typedef struct { int id; char *source; int abort_priority; bool complete; const char *abort_reason; enum pcmk__graph_next completion_action; int num_actions; int num_synapses; int batch_limit; guint network_delay; guint stonith_timeout; int fired; int pending; int skipped; int completed; int incomplete; GList *synapses; /* pcmk__graph_synapse_t* */ int migration_limit; } pcmk__graph_t; typedef struct { int (*pseudo) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*rsc) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*cluster) (pcmk__graph_t *graph, pcmk__graph_action_t *action); int (*fence) (pcmk__graph_t *graph, pcmk__graph_action_t *action); bool (*allowed) (pcmk__graph_t *graph, pcmk__graph_action_t *action); } pcmk__graph_functions_t; enum pcmk__graph_status { pcmk__graph_active, // Some actions have been performed pcmk__graph_pending, // No actions performed yet pcmk__graph_complete, pcmk__graph_terminated, }; void pcmk__set_graph_functions(pcmk__graph_functions_t *fns); pcmk__graph_t *pcmk__unpack_graph(xmlNode *xml_graph, const char *reference); enum pcmk__graph_status pcmk__execute_graph(pcmk__graph_t *graph); void pcmk__update_graph(pcmk__graph_t *graph, pcmk__graph_action_t *action); void pcmk__free_graph(pcmk__graph_t *graph); const char *pcmk__graph_status2text(enum pcmk__graph_status state); void pcmk__log_graph(unsigned int log_level, pcmk__graph_t *graph); void pcmk__log_graph_action(int log_level, pcmk__graph_action_t *action); -lrmd_event_data_t *pcmk__event_from_graph_action(xmlNode *resource, - pcmk__graph_action_t *action, +lrmd_event_data_t *pcmk__event_from_graph_action(const xmlNode *resource, + const pcmk__graph_action_t *action, int status, int rc, const char *exit_reason); #ifdef __cplusplus } #endif #endif diff --git a/lib/pacemaker/pcmk_graph_consumer.c b/lib/pacemaker/pcmk_graph_consumer.c index adf9d9b63f..2a01eebfc3 100644 --- a/lib/pacemaker/pcmk_graph_consumer.c +++ b/lib/pacemaker/pcmk_graph_consumer.c @@ -1,860 +1,861 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include /* * Functions for updating graph */ /*! * \internal * \brief Update synapse after completed prerequisite * * A synapse is ready to be executed once all its prerequisite actions (inputs) * complete. Given a completed action, check whether it is an input for a given * synapse, and if so, mark the input as confirmed, and mark the synapse as * ready if appropriate. * * \param[in] synapse Transition graph synapse to update * \param[in] action_id ID of an action that completed * * \note The only substantial effect here is confirming synapse inputs. * should_fire_synapse() will recalculate pcmk__synapse_ready, so the only * thing that uses the pcmk__synapse_ready from here is * synapse_state_str(). */ static void update_synapse_ready(pcmk__graph_synapse_t *synapse, int action_id) { if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) { return; // All inputs have already been confirmed } pcmk__set_synapse_flags(synapse, pcmk__synapse_ready); // Presume ready until proven otherwise for (GList *lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) { pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data; if (prereq->id == action_id) { crm_trace("Confirming input %d of synapse %d", action_id, synapse->id); pcmk__set_graph_action_flags(prereq, pcmk__graph_action_confirmed); } else if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) { pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready); crm_trace("Synapse %d still not ready after action %d", synapse->id, action_id); } } if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) { crm_trace("Synapse %d is now ready to execute", synapse->id); } } /*! * \internal * \brief Update action and synapse confirmation after action completion * * \param[in] synapse Transition graph synapse that action belongs to * \param[in] action_id ID of action that completed */ static void update_synapse_confirmed(pcmk__graph_synapse_t *synapse, int action_id) { bool all_confirmed = true; for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data; if (action->id == action_id) { crm_trace("Confirmed action %d of synapse %d", action_id, synapse->id); pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); } else if (all_confirmed && !(pcmk_is_set(action->flags, pcmk__graph_action_confirmed))) { all_confirmed = false; crm_trace("Synapse %d still not confirmed after action %d", synapse->id, action_id); } } if (all_confirmed && !(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) { crm_trace("Confirmed synapse %d", synapse->id); pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed); } } /*! * \internal * \brief Update the transition graph with a completed action result * * \param[in,out] graph Transition graph to update * \param[in] action Action that completed */ void pcmk__update_graph(pcmk__graph_t *graph, pcmk__graph_action_t *action) { for (GList *lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data; if (pcmk_any_flags_set(synapse->flags, pcmk__synapse_confirmed|pcmk__synapse_failed)) { continue; // This synapse already completed } else if (pcmk_is_set(synapse->flags, pcmk__synapse_executed)) { update_synapse_confirmed(synapse, action->id); } else if (!(pcmk_is_set(action->flags, pcmk__graph_action_failed)) || (synapse->priority == INFINITY)) { update_synapse_ready(synapse, action->id); } } } /* * Functions for executing graph */ /* A transition graph consists of various types of actions. The library caller * registers execution functions for each action type, which will be stored * here. */ static pcmk__graph_functions_t *graph_fns = NULL; /*! * \internal * \brief Set transition graph execution functions * * \param[in] Execution functions to use */ void pcmk__set_graph_functions(pcmk__graph_functions_t *fns) { crm_debug("Setting custom functions for executing transition graphs"); graph_fns = fns; CRM_ASSERT(graph_fns != NULL); CRM_ASSERT(graph_fns->rsc != NULL); CRM_ASSERT(graph_fns->cluster != NULL); CRM_ASSERT(graph_fns->pseudo != NULL); CRM_ASSERT(graph_fns->fence != NULL); } /*! * \internal * \brief Check whether a graph synapse is ready to be executed * * \param[in] graph Transition graph that synapse is part of * \param[in] synapse Synapse to check * * \return true if synapse is ready, false otherwise */ static bool should_fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse) { GList *lpc = NULL; pcmk__set_synapse_flags(synapse, pcmk__synapse_ready); for (lpc = synapse->inputs; lpc != NULL; lpc = lpc->next) { pcmk__graph_action_t *prereq = (pcmk__graph_action_t *) lpc->data; if (!(pcmk_is_set(prereq->flags, pcmk__graph_action_confirmed))) { crm_trace("Input %d for synapse %d not yet confirmed", prereq->id, synapse->id); pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready); break; } else if (pcmk_is_set(prereq->flags, pcmk__graph_action_failed) && !(pcmk_is_set(prereq->flags, pcmk__graph_action_can_fail))) { crm_trace("Input %d for synapse %d confirmed but failed", prereq->id, synapse->id); pcmk__clear_synapse_flags(synapse, pcmk__synapse_ready); break; } } if (pcmk_is_set(synapse->flags, pcmk__synapse_ready)) { crm_trace("Synapse %d is ready to execute", synapse->id); } else { return false; } for (lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { pcmk__graph_action_t *a = (pcmk__graph_action_t *) lpc->data; if (a->type == pcmk__pseudo_graph_action) { /* None of the below applies to pseudo ops */ } else if (synapse->priority < graph->abort_priority) { crm_trace("Skipping synapse %d: priority %d is less than " "abort priority %d", synapse->id, synapse->priority, graph->abort_priority); graph->skipped++; return false; } else if (graph_fns->allowed && !(graph_fns->allowed(graph, a))) { crm_trace("Deferring synapse %d: not allowed", synapse->id); return false; } } return true; } /*! * \internal * \brief Initiate an action from a transition graph * * \param[in] graph Transition graph containing action * \param[in] action Action to execute * * \return Standard Pacemaker return code */ static int initiate_action(pcmk__graph_t *graph, pcmk__graph_action_t *action) { const char *id = ID(action->xml); CRM_CHECK(id != NULL, return EINVAL); CRM_CHECK(!pcmk_is_set(action->flags, pcmk__graph_action_executed), return pcmk_rc_already); pcmk__set_graph_action_flags(action, pcmk__graph_action_executed); switch (action->type) { case pcmk__pseudo_graph_action: crm_trace("Executing pseudo-action %d (%s)", action->id, id); return graph_fns->pseudo(graph, action); case pcmk__rsc_graph_action: crm_trace("Executing resource action %d (%s)", action->id, id); return graph_fns->rsc(graph, action); case pcmk__cluster_graph_action: if (pcmk__str_eq(crm_element_value(action->xml, XML_LRM_ATTR_TASK), CRM_OP_FENCE, pcmk__str_casei)) { crm_trace("Executing fencing action %d (%s)", action->id, id); return graph_fns->fence(graph, action); } crm_trace("Executing cluster action %d (%s)", action->id, id); return graph_fns->cluster(graph, action); default: crm_err("Unsupported graph action type <%s id='%s'> (bug?)", crm_element_name(action->xml), id); return EINVAL; } } /*! * \internal * \brief Execute a graph synapse * * \param[in] graph Transition graph with synapse to execute * \param[in] synapse Synapse to execute * * \return Standard Pacemaker return value */ static int fire_synapse(pcmk__graph_t *graph, pcmk__graph_synapse_t *synapse) { pcmk__set_synapse_flags(synapse, pcmk__synapse_executed); for (GList *lpc = synapse->actions; lpc != NULL; lpc = lpc->next) { pcmk__graph_action_t *action = (pcmk__graph_action_t *) lpc->data; int rc = initiate_action(graph, action); if (rc != pcmk_rc_ok) { crm_err("Failed initiating <%s id=%d> in synapse %d: %s", crm_element_name(action->xml), action->id, synapse->id, pcmk_rc_str(rc)); pcmk__set_synapse_flags(synapse, pcmk__synapse_confirmed); pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed |pcmk__graph_action_failed); return pcmk_rc_error; } } return pcmk_rc_ok; } /*! * \internal * \brief Dummy graph method that can be used with simulations * * \param[in] graph Transition graph containing action * \param[in] action Graph action to be initiated * * \return Standard Pacemaker return code * \note If the PE_fail environment variable is set to the action ID, * then the graph action will be marked as failed. */ static int pseudo_action_dummy(pcmk__graph_t * graph, pcmk__graph_action_t *action) { static int fail = -1; if (fail < 0) { long long fail_ll; if ((pcmk__scan_ll(getenv("PE_fail"), &fail_ll, 0LL) == pcmk_rc_ok) && (fail_ll > 0LL) && (fail_ll <= INT_MAX)) { fail = (int) fail_ll; } else { fail = 0; } } if (action->id == fail) { crm_err("Dummy event handler: pretending action %d failed", action->id); pcmk__set_graph_action_flags(action, pcmk__graph_action_failed); graph->abort_priority = INFINITY; } else { crm_trace("Dummy event handler: action %d initiated", action->id); } pcmk__set_graph_action_flags(action, pcmk__graph_action_confirmed); pcmk__update_graph(graph, action); return pcmk_rc_ok; } static pcmk__graph_functions_t default_fns = { pseudo_action_dummy, pseudo_action_dummy, pseudo_action_dummy, pseudo_action_dummy }; /*! * \internal * \brief Execute all actions in a transition graph * * \param[in] graph Transition graph to execute * * \return Status of transition after execution */ enum pcmk__graph_status pcmk__execute_graph(pcmk__graph_t *graph) { GList *lpc = NULL; int log_level = LOG_DEBUG; enum pcmk__graph_status pass_result = pcmk__graph_active; const char *status = "In progress"; if (graph_fns == NULL) { graph_fns = &default_fns; } if (graph == NULL) { return pcmk__graph_complete; } graph->fired = 0; graph->pending = 0; graph->skipped = 0; graph->completed = 0; graph->incomplete = 0; // Count completed and in-flight synapses for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data; if (pcmk_is_set(synapse->flags, pcmk__synapse_confirmed)) { graph->completed++; } else if (!(pcmk_is_set(synapse->flags, pcmk__synapse_failed)) && pcmk_is_set(synapse->flags, pcmk__synapse_executed)) { graph->pending++; } } crm_trace("Executing graph %d (%d synapses already completed, %d pending)", graph->id, graph->completed, graph->pending); // Execute any synapses that are ready for (lpc = graph->synapses; lpc != NULL; lpc = lpc->next) { pcmk__graph_synapse_t *synapse = (pcmk__graph_synapse_t *) lpc->data; if ((graph->batch_limit > 0) && (graph->pending >= graph->batch_limit)) { crm_debug("Throttling graph execution: batch limit (%d) reached", graph->batch_limit); break; } else if (pcmk_is_set(synapse->flags, pcmk__synapse_failed)) { graph->skipped++; continue; } else if (pcmk_any_flags_set(synapse->flags, pcmk__synapse_confirmed|pcmk__synapse_executed)) { continue; // Already handled } else if (should_fire_synapse(graph, synapse)) { graph->fired++; if (fire_synapse(graph, synapse) != pcmk_rc_ok) { crm_err("Synapse %d failed to fire", synapse->id); log_level = LOG_ERR; graph->abort_priority = INFINITY; graph->incomplete++; graph->fired--; } if (!(pcmk_is_set(synapse->flags, pcmk__synapse_confirmed))) { graph->pending++; } } else { crm_trace("Synapse %d cannot fire", synapse->id); graph->incomplete++; } } if ((graph->pending == 0) && (graph->fired == 0)) { graph->complete = true; if ((graph->incomplete != 0) && (graph->abort_priority <= 0)) { log_level = LOG_WARNING; pass_result = pcmk__graph_terminated; status = "Terminated"; } else if (graph->skipped != 0) { log_level = LOG_NOTICE; pass_result = pcmk__graph_complete; status = "Stopped"; } else { log_level = LOG_NOTICE; pass_result = pcmk__graph_complete; status = "Complete"; } } else if (graph->fired == 0) { pass_result = pcmk__graph_pending; } do_crm_log(log_level, "Transition %d (Complete=%d, Pending=%d," " Fired=%d, Skipped=%d, Incomplete=%d, Source=%s): %s", graph->id, graph->completed, graph->pending, graph->fired, graph->skipped, graph->incomplete, graph->source, status); return pass_result; } /* * Functions for unpacking transition graph XML into structs */ /*! * \internal * \brief Unpack a transition graph action from XML * * \param[in] parent Synapse that action is part of * \param[in] xml_action Action XML to unparse * * \return Newly allocated action on success, or NULL otherwise */ static pcmk__graph_action_t * unpack_action(pcmk__graph_synapse_t *parent, xmlNode *xml_action) { enum pcmk__graph_action_type action_type; pcmk__graph_action_t *action = NULL; const char *element = TYPE(xml_action); const char *value = ID(xml_action); if (value == NULL) { crm_err("Ignoring transition graph action without id (bug?)"); crm_log_xml_trace(xml_action, "invalid"); return NULL; } if (pcmk__str_eq(element, XML_GRAPH_TAG_RSC_OP, pcmk__str_casei)) { action_type = pcmk__rsc_graph_action; } else if (pcmk__str_eq(element, XML_GRAPH_TAG_PSEUDO_EVENT, pcmk__str_casei)) { action_type = pcmk__pseudo_graph_action; } else if (pcmk__str_eq(element, XML_GRAPH_TAG_CRM_EVENT, pcmk__str_casei)) { action_type = pcmk__cluster_graph_action; } else { crm_err("Ignoring transition graph action of unknown type '%s' (bug?)", element); crm_log_xml_trace(xml_action, "invalid"); return NULL; } action = calloc(1, sizeof(pcmk__graph_action_t)); if (action == NULL) { crm_perror(LOG_CRIT, "Cannot unpack transition graph action"); crm_log_xml_trace(xml_action, "lost"); return NULL; } pcmk__scan_min_int(value, &(action->id), -1); action->type = pcmk__rsc_graph_action; action->xml = copy_xml(xml_action); action->synapse = parent; action->type = action_type; action->params = xml2list(action->xml); value = g_hash_table_lookup(action->params, "CRM_meta_timeout"); pcmk__scan_min_int(value, &(action->timeout), 0); /* Take start-delay into account for the timeout of the action timer */ value = g_hash_table_lookup(action->params, "CRM_meta_start_delay"); { int start_delay; pcmk__scan_min_int(value, &start_delay, 0); action->timeout += start_delay; } if (pcmk__guint_from_hash(action->params, CRM_META "_" XML_LRM_ATTR_INTERVAL, 0, &(action->interval_ms)) != pcmk_rc_ok) { action->interval_ms = 0; } value = g_hash_table_lookup(action->params, "CRM_meta_can_fail"); if (value != NULL) { gboolean can_fail = FALSE; crm_str_to_boolean(value, &can_fail); if (can_fail) { pcmk__set_graph_action_flags(action, pcmk__graph_action_can_fail); } else { pcmk__clear_graph_action_flags(action, pcmk__graph_action_can_fail); } #ifndef PCMK__COMPAT_2_0 if (pcmk_is_set(action->flags, pcmk__graph_action_can_fail)) { crm_warn("Support for the can_fail meta-attribute is deprecated" " and will be removed in a future release"); } #endif } crm_trace("Action %d has timer set to %dms", action->id, action->timeout); return action; } /*! * \internal * \brief Unpack transition graph synapse from XML * * \param[in] new_graph Transition graph that synapse is part of * \param[in] xml_synapse Synapse XML * * \return Newly allocated synapse on success, or NULL otherwise */ static pcmk__graph_synapse_t * unpack_synapse(pcmk__graph_t *new_graph, xmlNode *xml_synapse) { const char *value = NULL; xmlNode *action_set = NULL; pcmk__graph_synapse_t *new_synapse = NULL; crm_trace("Unpacking synapse %s", ID(xml_synapse)); new_synapse = calloc(1, sizeof(pcmk__graph_synapse_t)); if (new_synapse == NULL) { return NULL; } pcmk__scan_min_int(ID(xml_synapse), &(new_synapse->id), 0); value = crm_element_value(xml_synapse, XML_CIB_ATTR_PRIORITY); pcmk__scan_min_int(value, &(new_synapse->priority), 0); CRM_CHECK(new_synapse->id >= 0, free(new_synapse); return NULL); new_graph->num_synapses++; crm_trace("Unpacking synapse %s action sets", crm_element_value(xml_synapse, XML_ATTR_ID)); for (action_set = first_named_child(xml_synapse, "action_set"); action_set != NULL; action_set = crm_next_same_xml(action_set)) { for (xmlNode *action = pcmk__xml_first_child(action_set); action != NULL; action = pcmk__xml_next(action)) { pcmk__graph_action_t *new_action = unpack_action(new_synapse, action); if (new_action == NULL) { continue; } crm_trace("Adding action %d to synapse %d", new_action->id, new_synapse->id); new_graph->num_actions++; new_synapse->actions = g_list_append(new_synapse->actions, new_action); } } crm_trace("Unpacking synapse %s inputs", ID(xml_synapse)); for (xmlNode *inputs = first_named_child(xml_synapse, "inputs"); inputs != NULL; inputs = crm_next_same_xml(inputs)) { for (xmlNode *trigger = first_named_child(inputs, "trigger"); trigger != NULL; trigger = crm_next_same_xml(trigger)) { for (xmlNode *input = pcmk__xml_first_child(trigger); input != NULL; input = pcmk__xml_next(input)) { pcmk__graph_action_t *new_input = unpack_action(new_synapse, input); if (new_input == NULL) { continue; } crm_trace("Adding input %d to synapse %d", new_input->id, new_synapse->id); new_synapse->inputs = g_list_append(new_synapse->inputs, new_input); } } } return new_synapse; } /*! * \internal * \brief Unpack transition graph XML * * \param[in] xml_graph Transition graph XML to unpack * \param[in] reference Where the XML came from (for logging) * * \return Newly allocated transition graph on success, NULL otherwise * \note The caller is responsible for freeing the return value using * pcmk__free_graph(). * \note The XML is expected to be structured like: ... ... */ pcmk__graph_t * pcmk__unpack_graph(xmlNode *xml_graph, const char *reference) { pcmk__graph_t *new_graph = NULL; const char *t_id = NULL; const char *time = NULL; new_graph = calloc(1, sizeof(pcmk__graph_t)); if (new_graph == NULL) { return NULL; } new_graph->source = strdup((reference == NULL)? "unknown" : reference); if (new_graph->source == NULL) { free(new_graph); return NULL; } new_graph->id = -1; new_graph->abort_priority = 0; new_graph->network_delay = 0; new_graph->stonith_timeout = 0; new_graph->completion_action = pcmk__graph_done; // Parse top-level attributes from if (xml_graph != NULL) { t_id = crm_element_value(xml_graph, "transition_id"); CRM_CHECK(t_id != NULL, free(new_graph); return NULL); pcmk__scan_min_int(t_id, &(new_graph->id), -1); time = crm_element_value(xml_graph, "cluster-delay"); CRM_CHECK(time != NULL, free(new_graph); return NULL); new_graph->network_delay = crm_parse_interval_spec(time); time = crm_element_value(xml_graph, "stonith-timeout"); if (time == NULL) { new_graph->stonith_timeout = new_graph->network_delay; } else { new_graph->stonith_timeout = crm_parse_interval_spec(time); } // Use 0 (dynamic limit) as default/invalid, -1 (no limit) as minimum t_id = crm_element_value(xml_graph, "batch-limit"); if ((t_id == NULL) || (pcmk__scan_min_int(t_id, &(new_graph->batch_limit), -1) != pcmk_rc_ok)) { new_graph->batch_limit = 0; } t_id = crm_element_value(xml_graph, "migration-limit"); pcmk__scan_min_int(t_id, &(new_graph->migration_limit), -1); } // Unpack each child element for (xmlNode *synapse_xml = first_named_child(xml_graph, "synapse"); synapse_xml != NULL; synapse_xml = crm_next_same_xml(synapse_xml)) { pcmk__graph_synapse_t *new_synapse = unpack_synapse(new_graph, synapse_xml); if (new_synapse != NULL) { new_graph->synapses = g_list_append(new_graph->synapses, new_synapse); } } crm_debug("Unpacked transition %d from %s: %d actions in %d synapses", new_graph->id, new_graph->source, new_graph->num_actions, new_graph->num_synapses); return new_graph; } /* * Functions for freeing transition graph objects */ /*! * \internal * \brief Free a transition graph action object * * \param[in] user_data Action to free */ static void free_graph_action(gpointer user_data) { pcmk__graph_action_t *action = user_data; if (action->timer != 0) { crm_warn("Cancelling timer for graph action %d", action->id); g_source_remove(action->timer); } if (action->params != NULL) { g_hash_table_destroy(action->params); } free_xml(action->xml); free(action); } /*! * \internal * \brief Free a transition graph synapse object * * \param[in] user_data Synapse to free */ static void free_graph_synapse(gpointer user_data) { pcmk__graph_synapse_t *synapse = user_data; g_list_free_full(synapse->actions, free_graph_action); g_list_free_full(synapse->inputs, free_graph_action); free(synapse); } /*! * \internal * \brief Free a transition graph object * * \param[in] graph Transition graph to free */ void pcmk__free_graph(pcmk__graph_t *graph) { if (graph != NULL) { g_list_free_full(graph->synapses, free_graph_synapse); free(graph->source); free(graph); } } /* * Other transition graph utilities */ /*! * \internal * \brief Synthesize an executor event from a graph action * * \param[in] resource If not NULL, use greater call ID than in this XML * \param[in] action Graph action * \param[in] status What to use as event execution status * \param[in] rc What to use as event exit status * \param[in] exit_reason What to use as event exit reason * * \return Newly allocated executor event on success, or NULL otherwise */ lrmd_event_data_t * -pcmk__event_from_graph_action(xmlNode *resource, pcmk__graph_action_t *action, +pcmk__event_from_graph_action(const xmlNode *resource, + const pcmk__graph_action_t *action, int status, int rc, const char *exit_reason) { lrmd_event_data_t *op = NULL; GHashTableIter iter; const char *name = NULL; const char *value = NULL; xmlNode *action_resource = NULL; CRM_CHECK(action != NULL, return NULL); CRM_CHECK(action->type == pcmk__rsc_graph_action, return NULL); action_resource = first_named_child(action->xml, XML_CIB_TAG_RESOURCE); CRM_CHECK(action_resource != NULL, crm_log_xml_warn(action->xml, "invalid"); return NULL); op = lrmd_new_event(ID(action_resource), crm_element_value(action->xml, XML_LRM_ATTR_TASK), action->interval_ms); lrmd__set_result(op, rc, status, exit_reason); op->t_run = time(NULL); op->t_rcchange = op->t_run; op->params = pcmk__strkey_table(free, free); g_hash_table_iter_init(&iter, action->params); while (g_hash_table_iter_next(&iter, (void **)&name, (void **)&value)) { g_hash_table_insert(op->params, strdup(name), strdup(value)); } for (xmlNode *xop = pcmk__xml_first_child(resource); xop != NULL; xop = pcmk__xml_next(xop)) { int tmp = 0; crm_element_value_int(xop, XML_LRM_ATTR_CALLID, &tmp); crm_debug("Got call_id=%d for %s", tmp, ID(resource)); if (tmp > op->call_id) { op->call_id = tmp; } } op->call_id++; return op; } diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index 9cafb6d788..c17669eefe 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -1,1154 +1,1155 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include "pe_status_private.h" void populate_hash(xmlNode * nvpair_list, GHashTable * hash, const char **attrs, int attrs_length); resource_object_functions_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, }, { 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, }, { 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, }, { 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, } }; static enum pe_obj_types get_resource_type(const char *name) { if (pcmk__str_eq(name, XML_CIB_TAG_RESOURCE, pcmk__str_casei)) { return pe_native; } else if (pcmk__str_eq(name, XML_CIB_TAG_GROUP, pcmk__str_casei)) { return pe_group; } else if (pcmk__str_eq(name, XML_CIB_TAG_INCARNATION, pcmk__str_casei)) { return pe_clone; } else if (pcmk__str_eq(name, PCMK_XE_PROMOTABLE_LEGACY, pcmk__str_casei)) { // @COMPAT deprecated since 2.0.0 return pe_clone; } else if (pcmk__str_eq(name, XML_CIB_TAG_CONTAINER, pcmk__str_casei)) { return pe_container; } return pe_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(pe_resource_t * rsc, pe_rule_eval_data_t * rule_data, GHashTable * meta_hash, pe_working_set_t * data_set) { GHashTable *parent_orig_meta = pcmk__strkey_table(free, free); pe_resource_t *p = rsc->parent; if (p == NULL) { return ; } /* Search all parent resources, get the fixed value of "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, XML_TAG_META_SETS, rule_data, parent_orig_meta, NULL, FALSE, data_set); p = p->parent; } /* If there is a fixed value of "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, pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set) { pe_rsc_eval_data_t rsc_rule_data = { .standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS), .provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER), .agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE) }; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = data_set->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 = crm_element_value(rsc->xml, prop_name); add_hash_param(meta_hash, prop_name, prop_value); } pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_META_SETS, &rule_data, meta_hash, NULL, FALSE, data_set); /* Set the "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, data_set); } /* check the defaults */ pe__unpack_dataset_nvpairs(data_set->rsc_defaults, XML_TAG_META_SETS, &rule_data, meta_hash, NULL, FALSE, data_set); /* If there is "meta_attributes" that the parent resource has not explicitly set, set a value that is not set from rsc_default 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, pe_resource_t * rsc, - pe_node_t * node, pe_working_set_t * data_set) +get_rsc_attributes(GHashTable *meta_hash, const pe_resource_t *rsc, + const pe_node_t *node, pe_working_set_t *data_set) { pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = data_set->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, XML_TAG_ATTR_SETS, &rule_data, meta_hash, NULL, FALSE, data_set); /* set anything else based on the parent */ if (rsc->parent != NULL) { get_rsc_attributes(meta_hash, rsc->parent, node, data_set); } else { /* and finally check the defaults */ pe__unpack_dataset_nvpairs(data_set->rsc_defaults, XML_TAG_ATTR_SETS, &rule_data, meta_hash, NULL, FALSE, data_set); } } #if ENABLE_VERSIONED_ATTRS void pe_get_versioned_attributes(xmlNode * meta_hash, pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set) { pe_rule_eval_data_t rule_data = { .node_hash = (node == NULL)? NULL : node->details->attrs, .role = RSC_ROLE_UNKNOWN, .now = data_set->now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; pe_eval_versioned_attributes(data_set->input, rsc->xml, XML_TAG_ATTR_SETS, &rule_data, meta_hash, NULL); /* set anything else based on the parent */ if (rsc->parent != NULL) { pe_get_versioned_attributes(meta_hash, rsc->parent, node, data_set); } else { /* and finally check the defaults */ pe_eval_versioned_attributes(data_set->input, data_set->rsc_defaults, XML_TAG_ATTR_SETS, &rule_data, meta_hash, NULL); } } #endif static char * template_op_key(xmlNode * op) { const char *name = crm_element_value(op, "name"); const char *role = crm_element_value(op, "role"); char *key = NULL; if ((role == NULL) || pcmk__strcase_any_of(role, RSC_ROLE_STARTED_S, RSC_ROLE_UNPROMOTED_S, RSC_ROLE_UNPROMOTED_LEGACY_S, NULL)) { role = RSC_ROLE_UNKNOWN_S; } key = crm_strdup_printf("%s-%s", name, role); return key; } static gboolean unpack_template(xmlNode * xml_obj, xmlNode ** expanded_xml, pe_working_set_t * data_set) { 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 *clone = NULL; const char *id = NULL; if (xml_obj == NULL) { pe_err("No resource object for template unpacking"); return FALSE; } template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE); if (template_ref == NULL) { return TRUE; } id = ID(xml_obj); if (id == NULL) { pe_err("'%s' object must have a id", crm_element_name(xml_obj)); return FALSE; } if (pcmk__str_eq(template_ref, id, pcmk__str_none)) { pe_err("The resource object '%s' should not reference itself", id); return FALSE; } cib_resources = get_xpath_object("//"XML_CIB_TAG_RESOURCES, data_set->input, LOG_TRACE); if (cib_resources == NULL) { pe_err("No resources configured"); return FALSE; } template = pcmk__xe_match(cib_resources, XML_CIB_TAG_RSC_TEMPLATE, XML_ATTR_ID, template_ref); if (template == NULL) { pe_err("No template named '%s'", template_ref); return FALSE; } new_xml = copy_xml(template); xmlNodeSetName(new_xml, xml_obj->name); crm_xml_replace(new_xml, XML_ATTR_ID, id); clone = crm_element_value(xml_obj, XML_RSC_ATTR_INCARNATION); if(clone) { crm_xml_add(new_xml, XML_RSC_ATTR_INCARNATION, clone); } template_ops = find_xml_node(new_xml, "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__str_eq((const char *)new_child->name, "operations", pcmk__str_none)) { 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; /* Disable multi-level templates for now */ /*if(unpack_template(new_xml, expanded_xml, data_set) == FALSE) { free_xml(*expanded_xml); *expanded_xml = NULL; return FALSE; } */ return TRUE; } static gboolean add_template_rsc(xmlNode * xml_obj, pe_working_set_t * data_set) { const char *template_ref = NULL; const char *id = NULL; if (xml_obj == NULL) { pe_err("No resource object for processing resource list of template"); return FALSE; } template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE); if (template_ref == NULL) { return TRUE; } id = ID(xml_obj); if (id == NULL) { pe_err("'%s' object must have a id", crm_element_name(xml_obj)); return FALSE; } if (pcmk__str_eq(template_ref, id, pcmk__str_none)) { pe_err("The resource object '%s' should not reference itself", id); return FALSE; } if (add_tag_ref(data_set->template_rsc_sets, template_ref, id) == FALSE) { return FALSE; } return TRUE; } static bool detect_promotable(pe_resource_t *rsc) { const char *promotable = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROMOTABLE); if (crm_is_true(promotable)) { return TRUE; } // @COMPAT deprecated since 2.0.0 if (pcmk__str_eq(crm_element_name(rsc->xml), PCMK_XE_PROMOTABLE_LEGACY, pcmk__str_casei)) { /* @TODO in some future version, pe_warn_once() here, * then drop support in even later version */ g_hash_table_insert(rsc->meta, strdup(XML_RSC_ATTR_PROMOTABLE), strdup(XML_BOOLEAN_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] rsc Resource to query - * \param[in] node Node for evaluating rules (NULL for defaults) - * \param[in] data_set Cluster working set + * \param[in,out] rsc Resource to query + * \param[in] node Node for evaluating rules (NULL for defaults) + * \param[in] data_set Cluster working set * * \return Hash table containing resource parameter names and values * (or NULL if \p rsc or \p data_set is NULL) * \note The returned table will be destroyed when the resource is freed, so * callers should not destroy it. */ GHashTable * -pe_rsc_params(pe_resource_t *rsc, pe_node_t *node, pe_working_set_t *data_set) +pe_rsc_params(pe_resource_t *rsc, const pe_node_t *node, + pe_working_set_t *data_set) { 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) || (data_set == 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, data_set); g_hash_table_insert(rsc->parameter_cache, strdup(node_name), params_on_node); } return params_on_node; } /*! * \internal * \brief Unpack a resource's "requires" meta-attribute * * \param[in] rsc Resource being unpacked * \param[in] value Value of "requires" meta-attribute * \param[in] is_default Whether \p value was selected by default */ static void unpack_requires(pe_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)) { pe__set_resource_flags(rsc, pe_rsc_needs_quorum); } else if (pcmk__str_eq(value, PCMK__VALUE_FENCING, pcmk__str_casei)) { pe__set_resource_flags(rsc, pe_rsc_needs_fencing); if (!pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_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, pe_rsc_fence_device)) { pcmk__config_warn("Resetting \"" XML_RSC_ATTR_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, pe_flag_stonith_enabled)) { pcmk__config_warn("Resetting \"" XML_RSC_ATTR_REQUIRES "\" for %s " "to \"" PCMK__VALUE_QUORUM "\" because fencing " "is disabled", rsc->id); unpack_requires(rsc, PCMK__VALUE_QUORUM, true); return; } else { pe__set_resource_flags(rsc, pe_rsc_needs_fencing|pe_rsc_needs_unfencing); } } else { const char *orig_value = value; if (pcmk_is_set(rsc->flags, pe_rsc_fence_device)) { value = PCMK__VALUE_QUORUM; } else if ((rsc->variant == pe_native) && xml_contains_remote_node(rsc->xml)) { value = PCMK__VALUE_QUORUM; } else if (pcmk_is_set(rsc->cluster->flags, pe_flag_enable_unfencing)) { value = PCMK__VALUE_UNFENCING; } else if (pcmk_is_set(rsc->cluster->flags, pe_flag_stonith_enabled)) { value = PCMK__VALUE_FENCING; } else if (rsc->cluster->no_quorum_policy == no_quorum_ignore) { value = PCMK__VALUE_NOTHING; } else { value = PCMK__VALUE_QUORUM; } if (orig_value != NULL) { pcmk__config_err("Resetting '" XML_RSC_ATTR_REQUIRES "' for %s " "to '%s' because '%s' is not valid", rsc->id, value, orig_value); } unpack_requires(rsc, value, true); return; } pe_rsc_trace(rsc, "\tRequired to start: %s%s", value, (is_default? " (default)" : "")); } /*! * \internal * \brief Unpack configuration XML for a given resource * * Unpack the XML object containing a resource's configuration into a new * \c pe_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] data_set Cluster working set * * \return Standard Pacemaker return code */ int pe__unpack_resource(xmlNode *xml_obj, pe_resource_t **rsc, pe_resource_t *parent, pe_working_set_t *data_set) { xmlNode *expanded_xml = NULL; xmlNode *ops = NULL; const char *value = NULL; const char *rclass = NULL; /* Look for this after any templates have been expanded */ const char *id = crm_element_value(xml_obj, XML_ATTR_ID); bool guest_node = false; bool remote_node = false; bool has_versioned_params = false; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = data_set->now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; crm_log_xml_trace(xml_obj, "Processing resource input..."); CRM_CHECK(rsc != NULL, return EINVAL); if (id == NULL) { pe_err("Must specify id tag in "); return pcmk_rc_unpack_error; } if (unpack_template(xml_obj, &expanded_xml, data_set) == FALSE) { return pcmk_rc_unpack_error; } *rsc = calloc(1, sizeof(pe_resource_t)); (*rsc)->cluster = data_set; if (expanded_xml) { crm_log_xml_trace(expanded_xml, "Expanded resource..."); (*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 */ rclass = crm_element_value((*rsc)->xml, XML_AGENT_ATTR_CLASS); (*rsc)->parent = parent; ops = find_xml_node((*rsc)->xml, "operations", FALSE); (*rsc)->ops_xml = expand_idref(ops, data_set->input); (*rsc)->variant = get_resource_type(crm_element_name((*rsc)->xml)); if ((*rsc)->variant == pe_unknown) { pe_err("Unknown resource type: %s", crm_element_name((*rsc)->xml)); free(*rsc); return pcmk_rc_unpack_error; } #if ENABLE_VERSIONED_ATTRS (*rsc)->versioned_parameters = create_xml_node(NULL, XML_TAG_RSC_VER_ATTRS); #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, XML_RSC_ATTR_INCARNATION); if (value) { (*rsc)->id = crm_strdup_printf("%s:%s", id, value); add_hash_param((*rsc)->meta, XML_RSC_ATTR_INCARNATION, value); } else { (*rsc)->id = strdup(id); } (*rsc)->fns = &resource_class_functions[(*rsc)->variant]; pe_rsc_trace((*rsc), "Unpacking resource..."); get_meta_attributes((*rsc)->meta, *rsc, NULL, data_set); (*rsc)->parameters = pe_rsc_params(*rsc, NULL, data_set); // \deprecated #if ENABLE_VERSIONED_ATTRS pe_get_versioned_attributes((*rsc)->versioned_parameters, *rsc, NULL, data_set); #endif (*rsc)->flags = 0; pe__set_resource_flags(*rsc, pe_rsc_runnable|pe_rsc_provisional); if (!pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { pe__set_resource_flags(*rsc, pe_rsc_managed); } (*rsc)->rsc_cons = NULL; (*rsc)->rsc_tickets = NULL; (*rsc)->actions = NULL; (*rsc)->role = RSC_ROLE_STOPPED; (*rsc)->next_role = RSC_ROLE_UNKNOWN; (*rsc)->recovery_type = recovery_stop_start; (*rsc)->stickiness = 0; (*rsc)->migration_threshold = INFINITY; (*rsc)->failure_timeout = 0; value = g_hash_table_lookup((*rsc)->meta, XML_CIB_ATTR_PRIORITY); (*rsc)->priority = char2score(value); value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_CRITICAL); if ((value == NULL) || crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_critical); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_NOTIFY); if (crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_notify); } if (xml_contains_remote_node((*rsc)->xml)) { (*rsc)->is_remote_node = TRUE; if (g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_CONTAINER)) { guest_node = true; } else { remote_node = true; } } value = g_hash_table_lookup((*rsc)->meta, XML_OP_ATTR_ALLOW_MIGRATE); #if ENABLE_VERSIONED_ATTRS has_versioned_params = xml_has_children((*rsc)->versioned_parameters); #endif if (crm_is_true(value) && has_versioned_params) { pe_rsc_trace((*rsc), "Migration is disabled for resources with versioned parameters"); } else if (crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_allow_migrate); } else if ((value == NULL) && remote_node && !has_versioned_params) { /* 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 * allow-migrate=false. * We don't support migration for versioned resources, though. */ pe__set_resource_flags(*rsc, pe_rsc_allow_migrate); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MANAGED); if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) { if (crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_managed); } else { pe__clear_resource_flags(*rsc, pe_rsc_managed); } } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MAINTENANCE); if (crm_is_true(value)) { pe__clear_resource_flags(*rsc, pe_rsc_managed); pe__set_resource_flags(*rsc, pe_rsc_maintenance); } if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { pe__clear_resource_flags(*rsc, pe_rsc_managed); pe__set_resource_flags(*rsc, pe_rsc_maintenance); } if (pe_rsc_is_clone(uber_parent(*rsc))) { value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_UNIQUE); if (crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_unique); } if (detect_promotable(*rsc)) { pe__set_resource_flags(*rsc, pe_rsc_promotable); } } else { pe__set_resource_flags(*rsc, pe_rsc_unique); } pe_rsc_trace((*rsc), "Options for %s", (*rsc)->id); value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_RESTART); if (pcmk__str_eq(value, "restart", pcmk__str_casei)) { (*rsc)->restart_type = pe_restart_restart; pe_rsc_trace((*rsc), "\tDependency restart handling: restart"); pe_warn_once(pe_wo_restart_type, "Support for restart-type is deprecated and will be removed in a future release"); } else { (*rsc)->restart_type = pe_restart_ignore; pe_rsc_trace((*rsc), "\tDependency restart handling: ignore"); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MULTIPLE); if (pcmk__str_eq(value, "stop_only", pcmk__str_casei)) { (*rsc)->recovery_type = recovery_stop_only; pe_rsc_trace((*rsc), "\tMultiple running resource recovery: stop only"); } else if (pcmk__str_eq(value, "block", pcmk__str_casei)) { (*rsc)->recovery_type = recovery_block; pe_rsc_trace((*rsc), "\tMultiple running resource recovery: block"); } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) { (*rsc)->recovery_type = recovery_stop_unexpected; pe_rsc_trace((*rsc), "\tMultiple running resource recovery: " "stop unexpected instances"); } else { // "stop_start" if (!pcmk__str_eq(value, "stop_start", pcmk__str_casei|pcmk__str_null_matches)) { pe_warn("%s is not a valid value for " XML_RSC_ATTR_MULTIPLE ", using default of \"stop_start\"", value); } (*rsc)->recovery_type = recovery_stop_start; pe_rsc_trace((*rsc), "\tMultiple running resource recovery: stop/start"); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_STICKINESS); if (value != NULL && !pcmk__str_eq("default", value, pcmk__str_casei)) { (*rsc)->stickiness = char2score(value); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_STICKINESS); if (value != NULL && !pcmk__str_eq("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. */ pe_warn_once(pe_wo_neg_threshold, XML_RSC_ATTR_FAIL_STICKINESS " must be non-negative, using 1 instead"); (*rsc)->migration_threshold = 1; } } if (pcmk__str_eq(rclass, PCMK_RESOURCE_CLASS_STONITH, pcmk__str_casei)) { pe__set_working_set_flags(data_set, pe_flag_have_stonith_resource); pe__set_resource_flags(*rsc, pe_rsc_fence_device); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_REQUIRES); unpack_requires(*rsc, value, false); value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_TIMEOUT); if (value != NULL) { // Stored as seconds (*rsc)->failure_timeout = (int) (crm_parse_interval_spec(value) / 1000); } if (remote_node) { GHashTable *params = pe_rsc_params(*rsc, NULL, data_set); /* Grabbing the value now means that any rules based on node attributes * will evaluate to false, so such rules should not be used with * reconnect_interval. * * @TODO Evaluate per node before using */ value = g_hash_table_lookup(params, XML_REMOTE_ATTR_RECONNECT_INTERVAL); if (value) { /* reconnect delay works by setting failure_timeout and preventing the * connection from starting until the failure is cleared. */ (*rsc)->remote_reconnect_ms = crm_parse_interval_spec(value); /* we want to override any default failure_timeout in use when remote * reconnect_interval is in use. */ (*rsc)->failure_timeout = (*rsc)->remote_reconnect_ms / 1000; } } get_target_role(*rsc, &((*rsc)->next_role)); pe_rsc_trace((*rsc), "\tDesired next state: %s", (*rsc)->next_role != RSC_ROLE_UNKNOWN ? role2text((*rsc)->next_role) : "default"); if ((*rsc)->fns->unpack(*rsc, data_set) == FALSE) { return pcmk_rc_unpack_error; } if (pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)) { // This tag must stay exactly the same because it is tested elsewhere resource_location(*rsc, NULL, 0, "symmetric_default", data_set); } 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", data_set); } pe_rsc_trace((*rsc), "\tAction notification: %s", pcmk_is_set((*rsc)->flags, pe_rsc_notify)? "required" : "not required"); (*rsc)->utilization = pcmk__strkey_table(free, free); pe__unpack_dataset_nvpairs((*rsc)->xml, XML_TAG_UTILIZATION, &rule_data, (*rsc)->utilization, NULL, FALSE, data_set); /* data_set->resources = g_list_append(data_set->resources, (*rsc)); */ if (expanded_xml) { if (add_template_rsc(xml_obj, data_set) == FALSE) { return pcmk_rc_unpack_error; } } return pcmk_rc_ok; } void common_update_score(pe_resource_t * rsc, const char *id, int score) { pe_node_t *node = NULL; node = pe_hash_table_lookup(rsc->allowed_nodes, id); if (node != NULL) { pe_rsc_trace(rsc, "Updating score for %s on %s: %d + %d", rsc->id, id, node->weight, score); node->weight = pcmk__add_scores(node->weight, score); } if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; common_update_score(child_rsc, id, score); } } } gboolean is_parent(pe_resource_t *child, pe_resource_t *rsc) { pe_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; } pe_resource_t * uber_parent(pe_resource_t * rsc) { pe_resource_t *parent = rsc; if (parent == NULL) { return NULL; } while (parent->parent != NULL && parent->parent->variant != pe_container) { parent = parent->parent; } return parent; } void common_free(pe_resource_t * rsc) { if (rsc == NULL) { return; } pe_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 ENABLE_VERSIONED_ATTRS if (rsc->versioned_parameters != NULL) { free_xml(rsc->versioned_parameters); } #endif 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, pe_rsc_orphan)) { 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); pe_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); } /*! * \brief * \internal Find a node (and optionally count all) where resource is active * * \param[in] rsc Resource to check * \param[out] count_all If not NULL, will be set to count of active nodes * \param[out] count_clean If not NULL, will be set to count of clean nodes * * \return An active node (or NULL if resource is not active anywhere) * * \note The order of preference is: an active node that is the resource's * partial migration source; if the resource's "requires" is "quorum" or * "nothing", the first active node in the list that is clean and online; * the first active node in the list. */ pe_node_t * pe__find_active_on(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean) { pe_node_t *active = NULL; pe_node_t *node = NULL; bool keep_looking = FALSE; bool is_happy = FALSE; if (count_all) { *count_all = 0; } if (count_clean) { *count_clean = 0; } if (rsc == NULL) { return NULL; } for (GList *node_iter = rsc->running_on; node_iter != NULL; node_iter = node_iter->next) { node = node_iter->data; keep_looking = FALSE; is_happy = node->details->online && !node->details->unclean; if (count_all) { ++*count_all; } if (count_clean && is_happy) { ++*count_clean; } if (count_all || count_clean) { // If we're counting, we need to go through entire list keep_looking = TRUE; } if (rsc->partial_migration_source != NULL) { if (node->details == rsc->partial_migration_source->details) { // This is the migration source active = node; } else { keep_looking = TRUE; } } else if (!pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) { if (is_happy && (!active || !active->details->online || active->details->unclean)) { // This is the first clean node active = node; } else { keep_looking = TRUE; } } if (active == NULL) { // This is first node in list active = node; } if (keep_looking == FALSE) { // Don't waste time iterating if we don't have to break; } } return active; } /*! * \brief * \internal Find and count active nodes according to "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 pe__find_active_on() where the count * of all active nodes or only clean active nodes is desired according to * the "requires" meta-attribute. */ pe_node_t * pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count) { if (rsc && !pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) { return pe__find_active_on(rsc, NULL, count); } return pe__find_active_on(rsc, count, NULL); } void pe__count_common(pe_resource_t *rsc) { if (rsc->children != NULL) { for (GList *item = rsc->children; item != NULL; item = item->next) { ((pe_resource_t *) item->data)->fns->count(item->data); } } else if (!pcmk_is_set(rsc->flags, pe_rsc_orphan) || (rsc->role > RSC_ROLE_STOPPED)) { rsc->cluster->ninstances++; if (pe__resource_is_disabled(rsc)) { rsc->cluster->disabled_resources++; } if (pcmk_is_set(rsc->flags, pe_rsc_block)) { 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(pe_resource_t *rsc, enum rsc_role_e role, const char *why) { CRM_ASSERT((rsc != NULL) && (why != NULL)); if (rsc->next_role != role) { pe_rsc_trace(rsc, "Resetting next role for %s from %s to %s (%s)", rsc->id, role2text(rsc->next_role), role2text(role), why); rsc->next_role = role; } } diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c index 8b36fb2697..3c945c6a0e 100644 --- a/lib/pengine/rules.c +++ b/lib/pengine/rules.c @@ -1,1420 +1,1421 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include CRM_TRACE_INIT_DATA(pe_rules); /*! * \brief Evaluate any rules contained by given XML element * * \param[in] xml XML element to check for rules * \param[in] node_hash Node attributes to use when evaluating expressions * \param[in] now Time to use when evaluating expressions * \param[out] next_change If not NULL, set to when evaluation will change * * \return TRUE if no rules, or any of rules present is in effect, else FALSE */ gboolean pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now, crm_time_t *next_change) { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; return pe_eval_rules(ruleset, &rule_data, next_change); } gboolean pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, crm_time_t *next_change, pe_match_data_t *match_data) { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, .role = role, .now = now, .match_data = match_data, .rsc_data = NULL, .op_data = NULL }; return pe_eval_expr(rule, &rule_data, next_change); } /*! * \brief Evaluate one rule subelement (pass/fail) * * A rule element may contain another rule, a node attribute expression, or a * date expression. Given any one of those, evaluate it and return whether it * passed. * * \param[in] expr Rule subelement XML * \param[in] node_hash Node attributes to use when evaluating expression * \param[in] role Resource role to use when evaluating expression * \param[in] now Time to use when evaluating expression * \param[out] next_change If not NULL, set to when evaluation will change * \param[in] match_data If not NULL, resource back-references and params * * \return TRUE if expression is in effect under given conditions, else FALSE */ gboolean pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, crm_time_t *next_change, pe_match_data_t *match_data) { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, .role = role, .now = now, .match_data = match_data, .rsc_data = NULL, .op_data = NULL }; return pe_eval_subexpr(expr, &rule_data, next_change); } enum expression_type find_expression_type(xmlNode * expr) { const char *tag = NULL; const char *attr = NULL; attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); tag = crm_element_name(expr); if (pcmk__str_eq(tag, "date_expression", pcmk__str_casei)) { return time_expr; } else if (pcmk__str_eq(tag, "rsc_expression", pcmk__str_casei)) { return rsc_expr; } else if (pcmk__str_eq(tag, "op_expression", pcmk__str_casei)) { return op_expr; } else if (pcmk__str_eq(tag, XML_TAG_RULE, pcmk__str_casei)) { return nested_rule; } else if (!pcmk__str_eq(tag, "expression", pcmk__str_casei)) { return not_expr; } else if (pcmk__strcase_any_of(attr, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID, NULL)) { return loc_expr; } else if (pcmk__str_eq(attr, CRM_ATTR_ROLE, pcmk__str_casei)) { return role_expr; #if ENABLE_VERSIONED_ATTRS } else if (pcmk__str_eq(attr, CRM_ATTR_RA_VERSION, pcmk__str_casei)) { return version_expr; #endif } return attr_expr; } /* As per the nethack rules: * * moon period = 29.53058 days ~= 30, year = 365.2422 days * days moon phase advances on first day of year compared to preceding year * = 365.2422 - 12*29.53058 ~= 11 * years in Metonic cycle (time until same phases fall on the same days of * the month) = 18.6 ~= 19 * moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30 * (29 as initial condition) * current phase in days = first day phase + days elapsed in year * 6 moons ~= 177 days * 177 ~= 8 reported phases * 22 * + 11/22 for rounding * * 0-7, with 0: new, 4: full */ static int phase_of_the_moon(crm_time_t * now) { uint32_t epact, diy, goldn; uint32_t y; crm_time_get_ordinal(now, &y, &diy); goldn = (y % 19) + 1; epact = (11 * goldn + 18) % 30; if ((epact == 25 && goldn > 11) || epact == 24) epact++; return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7); } static int check_one(xmlNode *cron_spec, const char *xml_field, uint32_t time_field) { int rc = pcmk_rc_undetermined; const char *value = crm_element_value(cron_spec, xml_field); long long low, high; if (value == NULL) { /* Return pe_date_result_undetermined if the field is missing. */ goto bail; } if (pcmk__parse_ll_range(value, &low, &high) == pcmk_rc_unknown_format) { goto bail; } else if (low == high) { /* A single number was given, not a range. */ if (time_field < low) { rc = pcmk_rc_before_range; } else if (time_field > high) { rc = pcmk_rc_after_range; } else { rc = pcmk_rc_within_range; } } else if (low != -1 && high != -1) { /* This is a range with both bounds. */ if (time_field < low) { rc = pcmk_rc_before_range; } else if (time_field > high) { rc = pcmk_rc_after_range; } else { rc = pcmk_rc_within_range; } } else if (low == -1) { /* This is a range with no starting value. */ rc = time_field <= high ? pcmk_rc_within_range : pcmk_rc_after_range; } else if (high == -1) { /* This is a range with no ending value. */ rc = time_field >= low ? pcmk_rc_within_range : pcmk_rc_before_range; } bail: if (rc == pcmk_rc_within_range) { crm_debug("Condition '%s' in %s: passed", value, xml_field); } else { crm_debug("Condition '%s' in %s: failed", value, xml_field); } return rc; } static gboolean check_passes(int rc) { /* _within_range is obvious. _undetermined is a pass because * this is the return value if a field is not given. In this * case, we just want to ignore it and check other fields to * see if they place some restriction on what can pass. */ return rc == pcmk_rc_within_range || rc == pcmk_rc_undetermined; } #define CHECK_ONE(spec, name, var) do { \ int subpart_rc = check_one(spec, name, var); \ if (check_passes(subpart_rc) == FALSE) { \ return subpart_rc; \ } \ } while (0) int pe_cron_range_satisfied(crm_time_t * now, xmlNode * cron_spec) { uint32_t h, m, s, y, d, w; CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied); crm_time_get_gregorian(now, &y, &m, &d); CHECK_ONE(cron_spec, "years", y); CHECK_ONE(cron_spec, "months", m); CHECK_ONE(cron_spec, "monthdays", d); crm_time_get_timeofday(now, &h, &m, &s); CHECK_ONE(cron_spec, "hours", h); CHECK_ONE(cron_spec, "minutes", m); CHECK_ONE(cron_spec, "seconds", s); crm_time_get_ordinal(now, &y, &d); CHECK_ONE(cron_spec, "yeardays", d); crm_time_get_isoweek(now, &y, &w, &d); CHECK_ONE(cron_spec, "weekyears", y); CHECK_ONE(cron_spec, "weeks", w); CHECK_ONE(cron_spec, "weekdays", d); CHECK_ONE(cron_spec, "moon", phase_of_the_moon(now)); /* If we get here, either no fields were specified (which is success), or all * the fields that were specified had their conditions met (which is also a * success). Thus, the result is success. */ return pcmk_rc_ok; } static void update_field(crm_time_t *t, xmlNode *xml, const char *attr, void (*time_fn)(crm_time_t *, int)) { long long value; if ((pcmk__scan_ll(crm_element_value(xml, attr), &value, 0LL) == pcmk_rc_ok) && (value != 0LL) && (value >= INT_MIN) && (value <= INT_MAX)) { time_fn(t, (int) value); } } crm_time_t * pe_parse_xml_duration(crm_time_t * start, xmlNode * duration_spec) { crm_time_t *end = crm_time_new_undefined(); crm_time_set(end, start); update_field(end, duration_spec, "years", crm_time_add_years); update_field(end, duration_spec, "months", crm_time_add_months); update_field(end, duration_spec, "weeks", crm_time_add_weeks); update_field(end, duration_spec, "days", crm_time_add_days); update_field(end, duration_spec, "hours", crm_time_add_hours); update_field(end, duration_spec, "minutes", crm_time_add_minutes); update_field(end, duration_spec, "seconds", crm_time_add_seconds); return end; } // Set next_change to t if t is earlier static void crm_time_set_if_earlier(crm_time_t *next_change, crm_time_t *t) { if ((next_change != NULL) && (t != NULL)) { if (!crm_time_is_defined(next_change) || (crm_time_compare(t, next_change) < 0)) { crm_time_set(next_change, t); } } } // Information about a block of nvpair elements typedef struct sorted_set_s { int score; // This block's score for sorting const char *name; // This block's ID const char *special_name; // ID that should sort first xmlNode *attr_set; // This block } sorted_set_t; static gint sort_pairs(gconstpointer a, gconstpointer b) { const sorted_set_t *pair_a = a; const sorted_set_t *pair_b = b; if (a == NULL && b == NULL) { return 0; } else if (a == NULL) { return 1; } else if (b == NULL) { return -1; } if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) { return -1; } else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) { return 1; } if (pair_a->score < pair_b->score) { return 1; } else if (pair_a->score > pair_b->score) { return -1; } return 0; } static void populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top) { const char *name = NULL; const char *value = NULL; const char *old_value = NULL; xmlNode *list = nvpair_list; xmlNode *an_attr = NULL; name = crm_element_name(list->children); if (pcmk__str_eq(XML_TAG_ATTRS, name, pcmk__str_casei)) { list = list->children; } for (an_attr = pcmk__xe_first_child(list); an_attr != NULL; an_attr = pcmk__xe_next(an_attr)) { if (pcmk__str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, pcmk__str_none)) { xmlNode *ref_nvpair = expand_idref(an_attr, top); name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME); if (name == NULL) { name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME); } value = crm_element_value(an_attr, XML_NVPAIR_ATTR_VALUE); if (value == NULL) { value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE); } if (name == NULL || value == NULL) { continue; } old_value = g_hash_table_lookup(hash, name); if (pcmk__str_eq(value, "#default", pcmk__str_casei)) { if (old_value) { crm_trace("Letting %s default (removing explicit value \"%s\")", name, value); g_hash_table_remove(hash, name); } continue; } else if (old_value == NULL) { crm_trace("Setting %s=\"%s\"", name, value); g_hash_table_insert(hash, strdup(name), strdup(value)); } else if (overwrite) { crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")", name, value, old_value); g_hash_table_replace(hash, strdup(name), strdup(value)); } } } } #if ENABLE_VERSIONED_ATTRS static xmlNode* get_versioned_rule(xmlNode * attr_set) { xmlNode * rule = NULL; xmlNode * expr = NULL; for (rule = pcmk__xe_first_child(attr_set); rule != NULL; rule = pcmk__xe_next(rule)) { if (pcmk__str_eq((const char *)rule->name, XML_TAG_RULE, pcmk__str_none)) { for (expr = pcmk__xe_first_child(rule); expr != NULL; expr = pcmk__xe_next(expr)) { if (find_expression_type(expr) == version_expr) { return rule; } } } } return NULL; } static void add_versioned_attributes(xmlNode * attr_set, xmlNode * versioned_attrs) { xmlNode *attr_set_copy = NULL; xmlNode *rule = NULL; xmlNode *expr = NULL; if (!attr_set || !versioned_attrs) { return; } attr_set_copy = copy_xml(attr_set); rule = get_versioned_rule(attr_set_copy); if (!rule) { free_xml(attr_set_copy); return; } expr = pcmk__xe_first_child(rule); while (expr != NULL) { if (find_expression_type(expr) != version_expr) { xmlNode *node = expr; expr = pcmk__xe_next(expr); free_xml(node); } else { expr = pcmk__xe_next(expr); } } add_node_nocopy(versioned_attrs, NULL, attr_set_copy); } #endif typedef struct unpack_data_s { gboolean overwrite; void *hash; crm_time_t *next_change; pe_rule_eval_data_t *rule_data; xmlNode *top; } unpack_data_t; static void unpack_attr_set(gpointer data, gpointer user_data) { sorted_set_t *pair = data; unpack_data_t *unpack_data = user_data; if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data, unpack_data->next_change)) { return; } #if ENABLE_VERSIONED_ATTRS if (get_versioned_rule(pair->attr_set) && !(unpack_data->rule_data->node_hash && g_hash_table_lookup_extended(unpack_data->rule_data->node_hash, CRM_ATTR_RA_VERSION, NULL, NULL))) { // we haven't actually tested versioned expressions yet return; } #endif crm_trace("Adding attributes from %s (score %d) %s overwrite", pair->name, pair->score, (unpack_data->overwrite? "with" : "without")); populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top); } #if ENABLE_VERSIONED_ATTRS static void unpack_versioned_attr_set(gpointer data, gpointer user_data) { sorted_set_t *pair = data; unpack_data_t *unpack_data = user_data; if (pe_eval_rules(pair->attr_set, unpack_data->rule_data, unpack_data->next_change)) { add_versioned_attributes(pair->attr_set, unpack_data->hash); } } #endif /*! * \internal * \brief Create a sorted list of nvpair blocks * * \param[in] top XML document root (used to expand id-ref's) * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name If not NULL, only get blocks of this element type * \param[in] always_first If not NULL, sort block with this ID as first * * \return List of sorted_set_t entries for nvpair blocks */ static GList * -make_pairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, +make_pairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, const char *always_first) { GList *unsorted = NULL; if (xml_obj == NULL) { return NULL; } for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL; attr_set = pcmk__xe_next(attr_set)) { if (pcmk__str_eq(set_name, (const char *) attr_set->name, pcmk__str_null_matches)) { const char *score = NULL; sorted_set_t *pair = NULL; xmlNode *expanded_attr_set = expand_idref(attr_set, top); if (expanded_attr_set == NULL) { // Schema (if not "none") prevents this continue; } pair = calloc(1, sizeof(sorted_set_t)); pair->name = ID(expanded_attr_set); pair->special_name = always_first; pair->attr_set = expanded_attr_set; score = crm_element_value(expanded_attr_set, XML_RULE_ATTR_SCORE); pair->score = char2score(score); unsorted = g_list_prepend(unsorted, pair); } } return g_list_sort(unsorted, sort_pairs); } /*! * \internal * \brief Extract nvpair blocks contained by an XML element into a hash table * * \param[in] top XML document root (used to expand id-ref's) * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name If not NULL, only use blocks of this element type * \param[out] hash Where to store extracted name/value pairs * \param[in] always_first If not NULL, process block with this ID first * \param[in] overwrite Whether to replace existing values with same name * \param[in] rule_data Matching parameters to use when unpacking * \param[out] next_change If not NULL, set to when rule evaluation will change * \param[in] unpack_func Function to call to unpack each block */ static void -unpack_nvpair_blocks(xmlNode *top, xmlNode *xml_obj, const char *set_name, +unpack_nvpair_blocks(xmlNode *top, const xmlNode *xml_obj, const char *set_name, void *hash, const char *always_first, gboolean overwrite, pe_rule_eval_data_t *rule_data, crm_time_t *next_change, GFunc unpack_func) { GList *pairs = make_pairs(top, xml_obj, set_name, always_first); if (pairs) { unpack_data_t data = { .hash = hash, .overwrite = overwrite, .next_change = next_change, .top = top, .rule_data = rule_data }; g_list_foreach(pairs, unpack_func, &data); g_list_free_full(pairs, free); } } void -pe_eval_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, +pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *next_change) { unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first, overwrite, rule_data, next_change, unpack_attr_set); } /*! * \brief Extract nvpair blocks contained by an XML element into a hash table * * \param[in] top XML document root (used to expand id-ref's) * \param[in] xml_obj XML element containing blocks of nvpair elements * \param[in] set_name Element name to identify nvpair blocks * \param[in] node_hash Node attributes to use when evaluating rules * \param[out] hash Where to store extracted name/value pairs * \param[in] always_first If not NULL, process block with this ID first * \param[in] overwrite Whether to replace existing values with same name * \param[in] now Time to use when evaluating rules * \param[out] next_change If not NULL, set to when rule evaluation will change */ void pe_unpack_nvpairs(xmlNode *top, xmlNode *xml_obj, const char *set_name, GHashTable *node_hash, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *now, crm_time_t *next_change) { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, always_first, overwrite, next_change); } #if ENABLE_VERSIONED_ATTRS void -pe_eval_versioned_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, +pe_eval_versioned_attributes(xmlNode *top, const xmlNode *xml_obj, + const char *set_name, pe_rule_eval_data_t *rule_data, xmlNode *hash, crm_time_t *next_change) { unpack_nvpair_blocks(top, xml_obj, set_name, hash, NULL, FALSE, rule_data, next_change, unpack_versioned_attr_set); } #endif char * pe_expand_re_matches(const char *string, pe_re_match_data_t *match_data) { size_t len = 0; int i; const char *p, *last_match_index; char *p_dst, *result = NULL; if (pcmk__str_empty(string) || !match_data) { return NULL; } p = last_match_index = string; while (*p) { if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) { i = *(p + 1) - '0'; if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 && match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) { len += p - last_match_index + (match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so); last_match_index = p + 2; } p++; } p++; } len += p - last_match_index + 1; /* FIXME: Excessive? */ if (len - 1 <= 0) { return NULL; } p_dst = result = calloc(1, len); p = string; while (*p) { if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) { i = *(p + 1) - '0'; if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 && match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) { /* rm_eo can be equal to rm_so, but then there is nothing to do */ int match_len = match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so; memcpy(p_dst, match_data->string + match_data->pmatch[i].rm_so, match_len); p_dst += match_len; } p++; } else { *(p_dst) = *(p); p_dst++; } p++; } return result; } #if ENABLE_VERSIONED_ATTRS GHashTable* pe_unpack_versioned_parameters(xmlNode *versioned_params, const char *ra_version) { GHashTable *hash = pcmk__strkey_table(free, free); if (versioned_params && ra_version) { GHashTable *node_hash = pcmk__strkey_table(free, free); xmlNode *attr_set = pcmk__xe_first_child(versioned_params); if (attr_set) { g_hash_table_insert(node_hash, strdup(CRM_ATTR_RA_VERSION), strdup(ra_version)); pe_unpack_nvpairs(NULL, versioned_params, crm_element_name(attr_set), node_hash, hash, NULL, FALSE, NULL, NULL); } g_hash_table_destroy(node_hash); } return hash; } #endif gboolean pe_eval_rules(xmlNode *ruleset, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) { // If there are no rules, pass by default gboolean ruleset_default = TRUE; for (xmlNode *rule = first_named_child(ruleset, XML_TAG_RULE); rule != NULL; rule = crm_next_same_xml(rule)) { ruleset_default = FALSE; if (pe_eval_expr(rule, rule_data, next_change)) { /* Only the deprecated "lifetime" element of location constraints * may contain more than one rule at the top level -- the schema * limits a block of nvpairs to a single top-level rule. So, this * effectively means that a lifetime is active if any rule it * contains is active. */ return TRUE; } } return ruleset_default; } gboolean pe_eval_expr(xmlNode *rule, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) { xmlNode *expr = NULL; gboolean test = TRUE; gboolean empty = TRUE; gboolean passed = TRUE; gboolean do_and = TRUE; const char *value = NULL; rule = expand_idref(rule, NULL); value = crm_element_value(rule, XML_RULE_ATTR_BOOLEAN_OP); if (pcmk__str_eq(value, "or", pcmk__str_casei)) { do_and = FALSE; passed = FALSE; } crm_trace("Testing rule %s", ID(rule)); for (expr = pcmk__xe_first_child(rule); expr != NULL; expr = pcmk__xe_next(expr)) { test = pe_eval_subexpr(expr, rule_data, next_change); empty = FALSE; if (test && do_and == FALSE) { crm_trace("Expression %s/%s passed", ID(rule), ID(expr)); return TRUE; } else if (test == FALSE && do_and) { crm_trace("Expression %s/%s failed", ID(rule), ID(expr)); return FALSE; } } if (empty) { crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule)); } crm_trace("Rule %s %s", ID(rule), passed ? "passed" : "failed"); return passed; } gboolean pe_eval_subexpr(xmlNode *expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) { gboolean accept = FALSE; const char *uname = NULL; switch (find_expression_type(expr)) { case nested_rule: accept = pe_eval_expr(expr, rule_data, next_change); break; case attr_expr: case loc_expr: /* these expressions can never succeed if there is * no node to compare with */ if (rule_data->node_hash != NULL) { accept = pe__eval_attr_expr(expr, rule_data); } break; case time_expr: switch (pe__eval_date_expr(expr, rule_data, next_change)) { case pcmk_rc_within_range: case pcmk_rc_ok: accept = TRUE; break; default: accept = FALSE; break; } break; case role_expr: accept = pe__eval_role_expr(expr, rule_data); break; case rsc_expr: accept = pe__eval_rsc_expr(expr, rule_data); break; case op_expr: accept = pe__eval_op_expr(expr, rule_data); break; #if ENABLE_VERSIONED_ATTRS case version_expr: if (rule_data->node_hash && g_hash_table_lookup_extended(rule_data->node_hash, CRM_ATTR_RA_VERSION, NULL, NULL)) { accept = pe__eval_attr_expr(expr, rule_data); } else { // we are going to test it when we have ra-version accept = TRUE; } break; #endif default: CRM_CHECK(FALSE /* bad type */ , return FALSE); accept = FALSE; } if (rule_data->node_hash) { uname = g_hash_table_lookup(rule_data->node_hash, CRM_ATTR_UNAME); } crm_trace("Expression %s %s on %s", ID(expr), accept ? "passed" : "failed", uname ? uname : "all nodes"); return accept; } /*! * \internal * \brief Compare two values in a rule's node attribute expression * * \param[in] l_val Value on left-hand side of comparison * \param[in] r_val Value on right-hand side of comparison * \param[in] type How to interpret the values (allowed values: * \c "string", \c "integer", \c "number", * \c "version", \c NULL) * \param[in] op Type of comparison * * \return -1 if (l_val < r_val), * 0 if (l_val == r_val), * 1 if (l_val > r_val) */ static int compare_attr_expr_vals(const char *l_val, const char *r_val, const char *type, const char *op) { int cmp = 0; if (l_val != NULL && r_val != NULL) { if (type == NULL) { if (pcmk__strcase_any_of(op, "lt", "lte", "gt", "gte", NULL)) { if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) { type = "number"; } else { type = "integer"; } } else { type = "string"; } crm_trace("Defaulting to %s based comparison for '%s' op", type, op); } if (pcmk__str_eq(type, "string", pcmk__str_casei)) { cmp = strcasecmp(l_val, r_val); } else if (pcmk__str_eq(type, "integer", pcmk__str_casei)) { long long l_val_num; int rc1 = pcmk__scan_ll(l_val, &l_val_num, 0LL); long long r_val_num; int rc2 = pcmk__scan_ll(r_val, &r_val_num, 0LL); if ((rc1 == pcmk_rc_ok) && (rc2 == pcmk_rc_ok)) { if (l_val_num < r_val_num) { cmp = -1; } else if (l_val_num > r_val_num) { cmp = 1; } else { cmp = 0; } } else { crm_debug("Integer parse error. Comparing %s and %s as strings", l_val, r_val); cmp = compare_attr_expr_vals(l_val, r_val, "string", op); } } else if (pcmk__str_eq(type, "number", pcmk__str_casei)) { double l_val_num; double r_val_num; int rc1 = pcmk__scan_double(l_val, &l_val_num, NULL, NULL); int rc2 = pcmk__scan_double(r_val, &r_val_num, NULL, NULL); if (rc1 == pcmk_rc_ok && rc2 == pcmk_rc_ok) { if (l_val_num < r_val_num) { cmp = -1; } else if (l_val_num > r_val_num) { cmp = 1; } else { cmp = 0; } } else { crm_debug("Floating-point parse error. Comparing %s and %s as " "strings", l_val, r_val); cmp = compare_attr_expr_vals(l_val, r_val, "string", op); } } else if (pcmk__str_eq(type, "version", pcmk__str_casei)) { cmp = compare_version(l_val, r_val); } } else if (l_val == NULL && r_val == NULL) { cmp = 0; } else if (r_val == NULL) { cmp = 1; } else { // l_val == NULL && r_val != NULL cmp = -1; } return cmp; } /*! * \internal * \brief Check whether an attribute expression evaluates to \c true * * \param[in] l_val Value on left-hand side of comparison * \param[in] r_val Value on right-hand side of comparison * \param[in] type How to interpret the values (allowed values: * \c "string", \c "integer", \c "number", * \c "version", \c NULL) * \param[in] op Type of comparison. * * \return \c true if expression evaluates to \c true, \c false * otherwise */ static bool accept_attr_expr(const char *l_val, const char *r_val, const char *type, const char *op) { int cmp; if (pcmk__str_eq(op, "defined", pcmk__str_casei)) { return (l_val != NULL); } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) { return (l_val == NULL); } cmp = compare_attr_expr_vals(l_val, r_val, type, op); if (pcmk__str_eq(op, "eq", pcmk__str_casei)) { return (cmp == 0); } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) { return (cmp != 0); } else if (l_val == NULL || r_val == NULL) { // The comparison is meaningless from this point on return false; } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) { return (cmp < 0); } else if (pcmk__str_eq(op, "lte", pcmk__str_casei)) { return (cmp <= 0); } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { return (cmp > 0); } else if (pcmk__str_eq(op, "gte", pcmk__str_casei)) { return (cmp >= 0); } return false; // Should never reach this point } /*! * \internal * \brief Get correct value according to value-source * * \param[in] value value given in rule expression * \param[in] value_source value-source given in rule expressions * \param[in] match_data If not NULL, resource back-references and params */ static const char * expand_value_source(const char *value, const char *value_source, pe_match_data_t *match_data) { GHashTable *table = NULL; if (pcmk__str_empty(value)) { return NULL; // value_source is irrelevant } else if (pcmk__str_eq(value_source, "param", pcmk__str_casei)) { table = match_data->params; } else if (pcmk__str_eq(value_source, "meta", pcmk__str_casei)) { table = match_data->meta; } else { // literal return value; } if (table == NULL) { return NULL; } return (const char *) g_hash_table_lookup(table, value); } /*! * \internal * \brief Evaluate a node attribute expression based on #uname, #id, #kind, * or a generic node attribute * * \param[in] expr XML of rule expression * \param[in] rule_data The match_data and node_hash members are used * * \return TRUE if rule_data satisfies the expression, FALSE otherwise */ gboolean pe__eval_attr_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) { gboolean attr_allocated = FALSE; const char *h_val = NULL; const char *op = NULL; const char *type = NULL; const char *attr = NULL; const char *value = NULL; const char *value_source = NULL; attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE); op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); value_source = crm_element_value(expr, XML_EXPR_ATTR_VALUE_SOURCE); if (attr == NULL) { pe_err("Expression %s invalid: " XML_EXPR_ATTR_ATTRIBUTE " not specified", pcmk__s(ID(expr), "without ID")); return FALSE; } else if (op == NULL) { pe_err("Expression %s invalid: " XML_EXPR_ATTR_OPERATION " not specified", pcmk__s(ID(expr), "without ID")); } if (rule_data->match_data != NULL) { // Expand any regular expression submatches (%0-%9) in attribute name if (rule_data->match_data->re != NULL) { char *resolved_attr = pe_expand_re_matches(attr, rule_data->match_data->re); if (resolved_attr != NULL) { attr = (const char *) resolved_attr; attr_allocated = TRUE; } } // Get value appropriate to value-source value = expand_value_source(value, value_source, rule_data->match_data); } if (rule_data->node_hash != NULL) { h_val = (const char *)g_hash_table_lookup(rule_data->node_hash, attr); } if (attr_allocated) { free((char *)attr); attr = NULL; } return accept_attr_expr(h_val, value, type, op); } /*! * \internal * \brief Evaluate a date_expression * * \param[in] expr XML of rule expression * \param[in] rule_data Only the now member is used * \param[out] next_change If not NULL, set to when evaluation will change * * \return Standard Pacemaker return code */ int pe__eval_date_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data, crm_time_t *next_change) { crm_time_t *start = NULL; crm_time_t *end = NULL; const char *value = NULL; const char *op = crm_element_value(expr, "operation"); xmlNode *duration_spec = NULL; xmlNode *date_spec = NULL; // "undetermined" will also be returned for parsing errors int rc = pcmk_rc_undetermined; crm_trace("Testing expression: %s", ID(expr)); duration_spec = first_named_child(expr, "duration"); date_spec = first_named_child(expr, "date_spec"); value = crm_element_value(expr, "start"); if (value != NULL) { start = crm_time_new(value); } value = crm_element_value(expr, "end"); if (value != NULL) { end = crm_time_new(value); } if (start != NULL && end == NULL && duration_spec != NULL) { end = pe_parse_xml_duration(start, duration_spec); } if (pcmk__str_eq(op, "in_range", pcmk__str_null_matches | pcmk__str_casei)) { if ((start == NULL) && (end == NULL)) { // in_range requires at least one of start or end } else if ((start != NULL) && (crm_time_compare(rule_data->now, start) < 0)) { rc = pcmk_rc_before_range; crm_time_set_if_earlier(next_change, start); } else if ((end != NULL) && (crm_time_compare(rule_data->now, end) > 0)) { rc = pcmk_rc_after_range; } else { rc = pcmk_rc_within_range; if (end && next_change) { // Evaluation doesn't change until second after end crm_time_add_seconds(end, 1); crm_time_set_if_earlier(next_change, end); } } } else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) { rc = pe_cron_range_satisfied(rule_data->now, date_spec); // @TODO set next_change appropriately } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { if (start == NULL) { // gt requires start } else if (crm_time_compare(rule_data->now, start) > 0) { rc = pcmk_rc_within_range; } else { rc = pcmk_rc_before_range; // Evaluation doesn't change until second after start crm_time_add_seconds(start, 1); crm_time_set_if_earlier(next_change, start); } } else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) { if (end == NULL) { // lt requires end } else if (crm_time_compare(rule_data->now, end) < 0) { rc = pcmk_rc_within_range; crm_time_set_if_earlier(next_change, end); } else { rc = pcmk_rc_after_range; } } crm_time_free(start); crm_time_free(end); return rc; } gboolean pe__eval_op_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) { const char *name = crm_element_value(expr, XML_NVPAIR_ATTR_NAME); const char *interval_s = crm_element_value(expr, XML_LRM_ATTR_INTERVAL); guint interval; crm_trace("Testing op_defaults expression: %s", ID(expr)); if (rule_data->op_data == NULL) { crm_trace("No operations data provided"); return FALSE; } interval = crm_parse_interval_spec(interval_s); if (interval == 0 && errno != 0) { crm_trace("Could not parse interval: %s", interval_s); return FALSE; } if (interval_s != NULL && interval != rule_data->op_data->interval) { crm_trace("Interval doesn't match: %d != %d", interval, rule_data->op_data->interval); return FALSE; } if (!pcmk__str_eq(name, rule_data->op_data->op_name, pcmk__str_none)) { crm_trace("Name doesn't match: %s != %s", name, rule_data->op_data->op_name); return FALSE; } return TRUE; } /*! * \internal * \brief Evaluate a node attribute expression based on #role * * \param[in] expr XML of rule expression * \param[in] rule_data Only the role member is used * * \return TRUE if rule_data->role satisfies the expression, FALSE otherwise */ gboolean pe__eval_role_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) { gboolean accept = FALSE; const char *op = NULL; const char *value = NULL; if (rule_data->role == RSC_ROLE_UNKNOWN) { return accept; } value = crm_element_value(expr, XML_EXPR_ATTR_VALUE); op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION); if (pcmk__str_eq(op, "defined", pcmk__str_casei)) { if (rule_data->role > RSC_ROLE_STARTED) { accept = TRUE; } } else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) { if ((rule_data->role > RSC_ROLE_UNKNOWN) && (rule_data->role < RSC_ROLE_UNPROMOTED)) { accept = TRUE; } } else if (pcmk__str_eq(op, "eq", pcmk__str_casei)) { if (text2role(value) == rule_data->role) { accept = TRUE; } } else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) { // Test "ne" only with promotable clone roles if ((rule_data->role > RSC_ROLE_UNKNOWN) && (rule_data->role < RSC_ROLE_UNPROMOTED)) { accept = FALSE; } else if (text2role(value) != rule_data->role) { accept = TRUE; } } return accept; } gboolean pe__eval_rsc_expr(xmlNodePtr expr, pe_rule_eval_data_t *rule_data) { const char *class = crm_element_value(expr, XML_AGENT_ATTR_CLASS); const char *provider = crm_element_value(expr, XML_AGENT_ATTR_PROVIDER); const char *type = crm_element_value(expr, XML_EXPR_ATTR_TYPE); crm_trace("Testing rsc_defaults expression: %s", ID(expr)); if (rule_data->rsc_data == NULL) { crm_trace("No resource data provided"); return FALSE; } if (class != NULL && !pcmk__str_eq(class, rule_data->rsc_data->standard, pcmk__str_none)) { crm_trace("Class doesn't match: %s != %s", class, rule_data->rsc_data->standard); return FALSE; } if ((provider == NULL && rule_data->rsc_data->provider != NULL) || (provider != NULL && rule_data->rsc_data->provider == NULL) || !pcmk__str_eq(provider, rule_data->rsc_data->provider, pcmk__str_none)) { crm_trace("Provider doesn't match: %s != %s", provider, rule_data->rsc_data->provider); return FALSE; } if (type != NULL && !pcmk__str_eq(type, rule_data->rsc_data->agent, pcmk__str_none)) { crm_trace("Agent doesn't match: %s != %s", type, rule_data->rsc_data->agent); return FALSE; } return TRUE; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include gboolean test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now) { return pe_evaluate_rules(ruleset, node_hash, now, NULL); } gboolean test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { return pe_test_rule(rule, node_hash, role, now, NULL, NULL); } gboolean pe_test_rule_re(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data) { pe_match_data_t match_data = { .re = re_match_data, .params = NULL, .meta = NULL, }; return pe_test_rule(rule, node_hash, role, now, NULL, &match_data); } gboolean pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, pe_match_data_t *match_data) { return pe_test_rule(rule, node_hash, role, now, NULL, match_data); } gboolean test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { return pe_test_expression(expr, node_hash, role, now, NULL, NULL); } gboolean pe_test_expression_re(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data) { pe_match_data_t match_data = { .re = re_match_data, .params = NULL, .meta = NULL, }; return pe_test_expression(expr, node_hash, role, now, NULL, &match_data); } gboolean pe_test_expression_full(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now, pe_match_data_t *match_data) { return pe_test_expression(expr, node_hash, role, now, NULL, match_data); } void unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name, GHashTable *node_hash, GHashTable *hash, const char *always_first, gboolean overwrite, crm_time_t *now) { pe_rule_eval_data_t rule_data = { .node_hash = node_hash, .role = RSC_ROLE_UNKNOWN, .now = now, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; unpack_nvpair_blocks(top, xml_obj, set_name, hash, always_first, overwrite, &rule_data, NULL, unpack_attr_set); } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c index 4b009a0332..a3b110c5fd 100644 --- a/lib/pengine/utils.c +++ b/lib/pengine/utils.c @@ -1,914 +1,914 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include "pe_status_private.h" extern bool pcmk__is_daemon; void print_str_str(gpointer key, gpointer value, gpointer user_data); gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data); /*! * \internal * \brief Check whether we can fence a particular node * * \param[in] data_set Working set for cluster * \param[in] node Name of node to check * * \return true if node can be fenced, false otherwise */ bool pe_can_fence(pe_working_set_t *data_set, pe_node_t *node) { if (pe__is_guest_node(node)) { /* Guest nodes are fenced by stopping their container resource. We can * do that if the container's host is either online or fenceable. */ pe_resource_t *rsc = node->details->remote_rsc->container; for (GList *n = rsc->running_on; n != NULL; n = n->next) { pe_node_t *container_node = n->data; if (!container_node->details->online && !pe_can_fence(data_set, container_node)) { return false; } } return true; } else if (!pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)) { return false; /* Turned off */ } else if (!pcmk_is_set(data_set->flags, pe_flag_have_stonith_resource)) { return false; /* No devices */ } else if (pcmk_is_set(data_set->flags, pe_flag_have_quorum)) { return true; } else if (data_set->no_quorum_policy == no_quorum_ignore) { return true; } else if(node == NULL) { return false; } else if(node->details->online) { crm_notice("We can fence %s without quorum because they're in our membership", node->details->uname); return true; } crm_trace("Cannot fence %s", node->details->uname); return false; } /*! * \internal * \brief Copy a node object * * \param[in] this_node Node object to copy * * \return Newly allocated shallow copy of this_node * \note This function asserts on errors and is guaranteed to return non-NULL. */ pe_node_t * pe__copy_node(const pe_node_t *this_node) { pe_node_t *new_node = NULL; CRM_ASSERT(this_node != NULL); new_node = calloc(1, sizeof(pe_node_t)); CRM_ASSERT(new_node != NULL); new_node->rsc_discover_mode = this_node->rsc_discover_mode; new_node->weight = this_node->weight; new_node->fixed = this_node->fixed; new_node->details = this_node->details; return new_node; } /* any node in list1 or list2 and not in the other gets a score of -INFINITY */ void node_list_exclude(GHashTable * hash, GList *list, gboolean merge_scores) { GHashTable *result = hash; pe_node_t *other_node = NULL; GList *gIter = list; GHashTableIter iter; pe_node_t *node = NULL; g_hash_table_iter_init(&iter, hash); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { other_node = pe_find_node_id(list, node->details->id); if (other_node == NULL) { node->weight = -INFINITY; } else if (merge_scores) { node->weight = pcmk__add_scores(node->weight, other_node->weight); } } for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; other_node = pe_hash_table_lookup(result, node->details->id); if (other_node == NULL) { pe_node_t *new_node = pe__copy_node(node); new_node->weight = -INFINITY; g_hash_table_insert(result, (gpointer) new_node->details->id, new_node); } } } /*! * \internal * \brief Create a node hash table from a node list * * \param[in] list Node list * * \return Hash table equivalent of node list */ GHashTable * pe__node_list2table(GList *list) { GHashTable *result = NULL; result = pcmk__strkey_table(NULL, free); for (GList *gIter = list; gIter != NULL; gIter = gIter->next) { pe_node_t *new_node = pe__copy_node((pe_node_t *) gIter->data); g_hash_table_insert(result, (gpointer) new_node->details->id, new_node); } return result; } /*! * \internal * \brief Compare two nodes by name, with numeric portions sorted numerically * * Sort two node names case-insensitively like strcasecmp(), but with any * numeric portions of the name sorted numerically. For example, "node10" will * sort higher than "node9" but lower than "remotenode9". * * \param[in] a First node to compare (can be \c NULL) * \param[in] b Second node to compare (can be \c NULL) * * \retval -1 \c a comes before \c b (or \c a is \c NULL and \c b is not) * \retval 0 \c a and \c b are equal (or both are \c NULL) * \retval 1 \c a comes after \c b (or \c b is \c NULL and \c a is not) */ gint pe__cmp_node_name(gconstpointer a, gconstpointer b) { const pe_node_t *node1 = (const pe_node_t *) a; const pe_node_t *node2 = (const pe_node_t *) b; if ((node1 == NULL) && (node2 == NULL)) { return 0; } if (node1 == NULL) { return -1; } if (node2 == NULL) { return 1; } return pcmk__numeric_strcasecmp(node1->details->uname, node2->details->uname); } /*! * \internal * \brief Output node weights to stdout * * \param[in] rsc Use allowed nodes for this resource * \param[in] comment Text description to prefix lines with * \param[in] nodes If rsc is not specified, use these nodes */ static void pe__output_node_weights(pe_resource_t *rsc, const char *comment, GHashTable *nodes, pe_working_set_t *data_set) { pcmk__output_t *out = data_set->priv; // Sort the nodes so the output is consistent for regression tests GList *list = g_list_sort(g_hash_table_get_values(nodes), pe__cmp_node_name); for (GList *gIter = list; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; out->message(out, "node-weight", rsc, comment, node->details->uname, pcmk_readable_score(node->weight)); } g_list_free(list); } /*! * \internal * \brief Log node weights at trace level * * \param[in] file Caller's filename * \param[in] function Caller's function name * \param[in] line Caller's line number * \param[in] rsc Use allowed nodes for this resource * \param[in] comment Text description to prefix lines with * \param[in] nodes If rsc is not specified, use these nodes */ static void pe__log_node_weights(const char *file, const char *function, int line, pe_resource_t *rsc, const char *comment, GHashTable *nodes) { GHashTableIter iter; pe_node_t *node = NULL; // Don't waste time if we're not tracing at this point pcmk__log_else(LOG_TRACE, return); g_hash_table_iter_init(&iter, nodes); while (g_hash_table_iter_next(&iter, NULL, (void **) &node)) { if (rsc) { qb_log_from_external_source(function, file, "%s: %s allocation score on %s: %s", LOG_TRACE, line, 0, comment, rsc->id, node->details->uname, pcmk_readable_score(node->weight)); } else { qb_log_from_external_source(function, file, "%s: %s = %s", LOG_TRACE, line, 0, comment, node->details->uname, pcmk_readable_score(node->weight)); } } } /*! * \internal * \brief Log or output node weights * * \param[in] file Caller's filename * \param[in] function Caller's function name * \param[in] line Caller's line number * \param[in] to_log Log if true, otherwise output * \param[in] rsc Use allowed nodes for this resource * \param[in] comment Text description to prefix lines with * \param[in] nodes Use these nodes */ void pe__show_node_weights_as(const char *file, const char *function, int line, bool to_log, pe_resource_t *rsc, const char *comment, GHashTable *nodes, pe_working_set_t *data_set) { if (rsc != NULL && pcmk_is_set(rsc->flags, pe_rsc_orphan)) { // Don't show allocation scores for orphans return; } if (nodes == NULL) { // Nothing to show return; } if (to_log) { pe__log_node_weights(file, function, line, rsc, comment, nodes); } else { pe__output_node_weights(rsc, comment, nodes, data_set); } // If this resource has children, repeat recursively for each if (rsc && rsc->children) { for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; pe__show_node_weights_as(file, function, line, to_log, child, comment, child->allowed_nodes, data_set); } } } /*! * \internal * \brief Compare two resources by priority * * \param[in] a First resource to compare (can be \c NULL) * \param[in] b Second resource to compare (can be \c NULL) * * \retval -1 \c a->priority > \c b->priority (or \c b is \c NULL and \c a is * not) * \retval 0 \c a->priority == \c b->priority (or both \c a and \c b are * \c NULL) * \retval 1 \c a->priority < \c b->priority (or \c a is \c NULL and \c b is * not) */ gint pe__cmp_rsc_priority(gconstpointer a, gconstpointer b) { const pe_resource_t *resource1 = (const pe_resource_t *)a; const pe_resource_t *resource2 = (const pe_resource_t *)b; if (a == NULL && b == NULL) { return 0; } if (a == NULL) { return 1; } if (b == NULL) { return -1; } if (resource1->priority > resource2->priority) { return -1; } if (resource1->priority < resource2->priority) { return 1; } return 0; } /* * Used by the HashTable for-loop */ void print_str_str(gpointer key, gpointer value, gpointer user_data) { crm_trace("%s%s %s ==> %s", user_data == NULL ? "" : (char *)user_data, user_data == NULL ? "" : ": ", (char *)key, (char *)value); } static void resource_node_score(pe_resource_t * rsc, pe_node_t * node, int score, const char *tag) { pe_node_t *match = NULL; if ((rsc->exclusive_discover || (node->rsc_discover_mode == pe_discover_never)) && pcmk__str_eq(tag, "symmetric_default", pcmk__str_casei)) { /* This string comparision may be fragile, but exclusive resources and * exclusive nodes should not have the symmetric_default constraint * applied to them. */ return; } else if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; resource_node_score(child_rsc, node, score, tag); } } pe_rsc_trace(rsc, "Setting %s for %s on %s: %d", tag, rsc->id, node->details->uname, score); match = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id); if (match == NULL) { match = pe__copy_node(node); g_hash_table_insert(rsc->allowed_nodes, (gpointer) match->details->id, match); } match->weight = pcmk__add_scores(match->weight, score); } void resource_location(pe_resource_t * rsc, pe_node_t * node, int score, const char *tag, pe_working_set_t * data_set) { if (node != NULL) { resource_node_score(rsc, node, score, tag); } else if (data_set != NULL) { GList *gIter = data_set->nodes; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node_iter = (pe_node_t *) gIter->data; resource_node_score(rsc, node_iter, score, tag); } } else { GHashTableIter iter; pe_node_t *node_iter = NULL; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node_iter)) { resource_node_score(rsc, node_iter, score, tag); } } if (node == NULL && score == -INFINITY) { if (rsc->allocated_to) { crm_info("Deallocating %s from %s", rsc->id, rsc->allocated_to->details->uname); free(rsc->allocated_to); rsc->allocated_to = NULL; } } } time_t get_effective_time(pe_working_set_t * data_set) { if(data_set) { if (data_set->now == NULL) { crm_trace("Recording a new 'now'"); data_set->now = crm_time_new(NULL); } return crm_time_get_seconds_since_epoch(data_set->now); } crm_trace("Defaulting to 'now'"); return time(NULL); } gboolean get_target_role(pe_resource_t * rsc, enum rsc_role_e * role) { enum rsc_role_e local_role = RSC_ROLE_UNKNOWN; const char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); CRM_CHECK(role != NULL, return FALSE); if (pcmk__str_eq(value, "started", pcmk__str_null_matches | pcmk__str_casei) || pcmk__str_eq("default", value, pcmk__str_casei)) { return FALSE; } local_role = text2role(value); if (local_role == RSC_ROLE_UNKNOWN) { pcmk__config_err("Ignoring '" XML_RSC_ATTR_TARGET_ROLE "' for %s " "because '%s' is not valid", rsc->id, value); return FALSE; } else if (local_role > RSC_ROLE_STARTED) { if (pcmk_is_set(uber_parent(rsc)->flags, pe_rsc_promotable)) { if (local_role > RSC_ROLE_UNPROMOTED) { /* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */ return FALSE; } } else { pcmk__config_err("Ignoring '" XML_RSC_ATTR_TARGET_ROLE "' for %s " "because '%s' only makes sense for promotable " "clones", rsc->id, value); return FALSE; } } *role = local_role; return TRUE; } gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order) { GList *gIter = NULL; pe_action_wrapper_t *wrapper = NULL; GList *list = NULL; if (order == pe_order_none) { return FALSE; } if (lh_action == NULL || rh_action == NULL) { return FALSE; } crm_trace("Creating action wrappers for ordering: %s then %s", lh_action->uuid, rh_action->uuid); /* Ensure we never create a dependency on ourselves... it's happened */ CRM_ASSERT(lh_action != rh_action); /* Filter dups, otherwise update_action_states() has too much work to do */ gIter = lh_action->actions_after; for (; gIter != NULL; gIter = gIter->next) { pe_action_wrapper_t *after = (pe_action_wrapper_t *) gIter->data; if (after->action == rh_action && (after->type & order)) { return FALSE; } } wrapper = calloc(1, sizeof(pe_action_wrapper_t)); wrapper->action = rh_action; wrapper->type = order; list = lh_action->actions_after; list = g_list_prepend(list, wrapper); lh_action->actions_after = list; wrapper = calloc(1, sizeof(pe_action_wrapper_t)); wrapper->action = lh_action; wrapper->type = order; list = rh_action->actions_before; list = g_list_prepend(list, wrapper); rh_action->actions_before = list; return TRUE; } void destroy_ticket(gpointer data) { pe_ticket_t *ticket = data; if (ticket->state) { g_hash_table_destroy(ticket->state); } free(ticket->id); free(ticket); } pe_ticket_t * ticket_new(const char *ticket_id, pe_working_set_t * data_set) { pe_ticket_t *ticket = NULL; if (pcmk__str_empty(ticket_id)) { return NULL; } if (data_set->tickets == NULL) { data_set->tickets = pcmk__strkey_table(free, destroy_ticket); } ticket = g_hash_table_lookup(data_set->tickets, ticket_id); if (ticket == NULL) { ticket = calloc(1, sizeof(pe_ticket_t)); if (ticket == NULL) { crm_err("Cannot allocate ticket '%s'", ticket_id); return NULL; } crm_trace("Creaing ticket entry for %s", ticket_id); ticket->id = strdup(ticket_id); ticket->granted = FALSE; ticket->last_granted = -1; ticket->standby = FALSE; ticket->state = pcmk__strkey_table(free, free); g_hash_table_insert(data_set->tickets, strdup(ticket->id), ticket); } return ticket; } const char *rsc_printable_id(pe_resource_t *rsc) { if (!pcmk_is_set(rsc->flags, pe_rsc_unique)) { return ID(rsc->xml); } return rsc->id; } void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags) { pe__clear_resource_flags(rsc, flags); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe__clear_resource_flags_recursive((pe_resource_t *) gIter->data, flags); } } void pe__clear_resource_flags_on_all(pe_working_set_t *data_set, uint64_t flag) { for (GList *lpc = data_set->resources; lpc != NULL; lpc = lpc->next) { pe_resource_t *r = (pe_resource_t *) lpc->data; pe__clear_resource_flags_recursive(r, flag); } } void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags) { pe__set_resource_flags(rsc, flags); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe__set_resource_flags_recursive((pe_resource_t *) gIter->data, flags); } } void trigger_unfencing( pe_resource_t * rsc, pe_node_t *node, const char *reason, pe_action_t *dependency, pe_working_set_t * data_set) { if (!pcmk_is_set(data_set->flags, pe_flag_enable_unfencing)) { /* No resources require it */ return; } else if ((rsc != NULL) && !pcmk_is_set(rsc->flags, pe_rsc_fence_device)) { /* Wasn't a stonith device */ return; } else if(node && node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) { pe_action_t *unfence = pe_fence_op(node, "on", FALSE, reason, FALSE, data_set); if(dependency) { order_actions(unfence, dependency, pe_order_optional); } } else if(rsc) { GHashTableIter iter; g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) { if(node->details->online && node->details->unclean == FALSE && node->details->shutdown == FALSE) { trigger_unfencing(rsc, node, reason, dependency, data_set); } } } } gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref) { pe_tag_t *tag = NULL; GList *gIter = NULL; gboolean is_existing = FALSE; CRM_CHECK(tags && tag_name && obj_ref, return FALSE); tag = g_hash_table_lookup(tags, tag_name); if (tag == NULL) { tag = calloc(1, sizeof(pe_tag_t)); if (tag == NULL) { return FALSE; } tag->id = strdup(tag_name); tag->refs = NULL; g_hash_table_insert(tags, strdup(tag_name), tag); } for (gIter = tag->refs; gIter != NULL; gIter = gIter->next) { const char *existing_ref = (const char *) gIter->data; if (pcmk__str_eq(existing_ref, obj_ref, pcmk__str_none)){ is_existing = TRUE; break; } } if (is_existing == FALSE) { tag->refs = g_list_append(tag->refs, strdup(obj_ref)); crm_trace("Added: tag=%s ref=%s", tag->id, obj_ref); } return TRUE; } /*! * \internal * \brief Check whether shutdown has been requested for a node * * \param[in] node Node to check * * \return TRUE if node has shutdown attribute set and nonzero, FALSE otherwise * \note This differs from simply using node->details->shutdown in that it can * be used before that has been determined (and in fact to determine it), * and it can also be used to distinguish requested shutdown from implicit * shutdown of remote nodes by virtue of their connection stopping. */ bool pe__shutdown_requested(pe_node_t *node) { const char *shutdown = pe_node_attribute_raw(node, XML_CIB_ATTR_SHUTDOWN); return !pcmk__str_eq(shutdown, "0", pcmk__str_null_matches); } /*! * \internal * \brief Update a data set's "recheck by" time * * \param[in] recheck Epoch time when recheck should happen * \param[in,out] data_set Current working set */ void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set) { if ((recheck > get_effective_time(data_set)) && ((data_set->recheck_by == 0) || (data_set->recheck_by > recheck))) { data_set->recheck_by = recheck; } } /*! * \internal * \brief Wrapper for pe_unpack_nvpairs() using a cluster working set */ void -pe__unpack_dataset_nvpairs(xmlNode *xml_obj, const char *set_name, +pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, pe_rule_eval_data_t *rule_data, GHashTable *hash, const char *always_first, gboolean overwrite, pe_working_set_t *data_set) { crm_time_t *next_change = crm_time_new_undefined(); pe_eval_nvpairs(data_set->input, xml_obj, set_name, rule_data, hash, always_first, overwrite, next_change); if (crm_time_is_defined(next_change)) { time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change); pe__update_recheck_time(recheck, data_set); } crm_time_free(next_change); } bool pe__resource_is_disabled(pe_resource_t *rsc) { const char *target_role = NULL; CRM_CHECK(rsc != NULL, return false); target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); if ((target_role_e == RSC_ROLE_STOPPED) || ((target_role_e == RSC_ROLE_UNPROMOTED) && pcmk_is_set(uber_parent(rsc)->flags, pe_rsc_promotable))) { return true; } } return false; } bool pe__rsc_running_on_any(pe_resource_t *rsc, GList *node_list) { for (GList *ele = rsc->running_on; ele; ele = ele->next) { pe_node_t *node = (pe_node_t *) ele->data; if (pcmk__str_in_list(node->details->uname, node_list, pcmk__str_star_matches|pcmk__str_casei)) { return true; } } return false; } bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GList *only_node) { return (rsc->fns->active(rsc, FALSE) && !pe__rsc_running_on_any(rsc, only_node)); } GList * pe__filter_rsc_list(GList *rscs, GList *filter) { GList *retval = NULL; for (GList *gIter = rscs; gIter; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; /* I think the second condition is safe here for all callers of this * function. If not, it needs to move into pe__node_text. */ if (pcmk__str_in_list(rsc_printable_id(rsc), filter, pcmk__str_star_matches) || (rsc->parent && pcmk__str_in_list(rsc_printable_id(rsc->parent), filter, pcmk__str_star_matches))) { retval = g_list_prepend(retval, rsc); } } return retval; } GList * pe__build_node_name_list(pe_working_set_t *data_set, const char *s) { GList *nodes = NULL; if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) { /* Nothing was given so return a list of all node names. Or, '*' was * given. This would normally fall into the pe__unames_with_tag branch * where it will return an empty list. Catch it here instead. */ nodes = g_list_prepend(nodes, strdup("*")); } else { pe_node_t *node = pe_find_node(data_set->nodes, s); if (node) { /* The given string was a valid uname for a node. Return a * singleton list containing just that uname. */ nodes = g_list_prepend(nodes, strdup(s)); } else { /* The given string was not a valid uname. It's either a tag or * it's a typo or something. In the first case, we'll return a * list of all the unames of the nodes with the given tag. In the * second case, we'll return a NULL pointer and nothing will * get displayed. */ nodes = pe__unames_with_tag(data_set, s); } } return nodes; } GList * pe__build_rsc_list(pe_working_set_t *data_set, const char *s) { GList *resources = NULL; if (pcmk__str_eq(s, "*", pcmk__str_null_matches)) { resources = g_list_prepend(resources, strdup("*")); } else { pe_resource_t *rsc = pe_find_resource_with_flags(data_set->resources, s, pe_find_renamed|pe_find_any); if (rsc) { /* A colon in the name we were given means we're being asked to filter * on a specific instance of a cloned resource. Put that exact string * into the filter list. Otherwise, use the printable ID of whatever * resource was found that matches what was asked for. */ if (strstr(s, ":") != NULL) { resources = g_list_prepend(resources, strdup(rsc->id)); } else { resources = g_list_prepend(resources, strdup(rsc_printable_id(rsc))); } } else { /* The given string was not a valid resource name. It's either * a tag or it's a typo or something. See build_uname_list for * more detail. */ resources = pe__rscs_with_tag(data_set, s); } } return resources; } xmlNode * pe__failed_probe_for_rsc(pe_resource_t *rsc, const char *name) { pe_resource_t *parent = uber_parent(rsc); const char *rsc_id = rsc->id; if (rsc->variant == pe_clone) { rsc_id = pe__clone_child_id(rsc); } else if (parent->variant == pe_clone) { rsc_id = pe__clone_child_id(parent); } for (xmlNode *xml_op = pcmk__xml_first_child(rsc->cluster->failed); xml_op != NULL; xml_op = pcmk__xml_next(xml_op)) { const char *value = NULL; char *op_id = NULL; /* This resource operation is not a failed probe. */ if (!pcmk_xe_mask_probe_failure(xml_op)) { continue; } /* This resource operation was not run on the given node. Note that if name is * NULL, this will always succeed. */ value = crm_element_value(xml_op, XML_LRM_ATTR_TARGET); if (value == NULL || !pcmk__str_eq(value, name, pcmk__str_casei|pcmk__str_null_matches)) { continue; } /* This resource operation has no operation_key. */ value = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); if (!parse_op_key(value ? value : ID(xml_op), &op_id, NULL, NULL)) { continue; } /* This resource operation's ID does not match the rsc_id we are looking for. */ if (!pcmk__str_eq(op_id, rsc_id, pcmk__str_none)) { free(op_id); continue; } free(op_id); return xml_op; } return NULL; }