diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index 1b5f6f1d8d..87ae25f22b 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -1,724 +1,723 @@ /* * Copyright 2004-2023 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 # include const char *pe__resource_description(const pe_resource_t *rsc, uint32_t show_opts); 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(const pe_resource_t *clone); int pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag); enum pe__group_flags { pe__group_ordered = (1 << 0), // Members start sequentially pe__group_colocated = (1 << 1), // Members must be on same node }; bool pe__group_flag_is_set(const pe_resource_t *group, uint32_t flags); pe_resource_t *pe__last_group_member(const pe_resource_t *group); # 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), pe_wo_order_inst = (1 << 9), pe_wo_coloc_inst = (1 << 10), pe_wo_group_order = (1 << 11), pe_wo_group_coloc = (1 << 12), pe_wo_upstart = (1 << 13), pe_wo_nagios = (1 << 14), }; 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; uint32_t flags; // Group of enum pe_ordering flags 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; const pe_resource_t *pe__const_top_resource(const pe_resource_t *rsc, bool include_bundle); int pe__clone_max(const pe_resource_t *clone); int pe__clone_node_max(const pe_resource_t *clone); int pe__clone_promoted_max(const pe_resource_t *clone); int pe__clone_promoted_node_max(const pe_resource_t *clone); void pe__create_clone_notifications(pe_resource_t *clone); void pe__free_clone_notification_data(pe_resource_t *clone); void pe__create_clone_notif_pseudo_ops(pe_resource_t *clone, pe_action_t *start, pe_action_t *started, pe_action_t *stop, pe_action_t *stopped); 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(const pe_working_set_t *data_set, const 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 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); //! \deprecated This function will be removed in a future release void native_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); //! \deprecated This function will be removed in a future release void group_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); //! \deprecated This function will be removed in a future release void clone_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); //! \deprecated This function will be removed in a future release void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); gchar *pcmk__native_output_string(const pe_resource_t *rsc, const char *name, const 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__order_notifs_after_fencing(const pe_action_t *action, pe_resource_t *rsc, pe_action_t *stonith_op); static inline const char * pe__rsc_bool_str(const 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(const pe_node_t *node, pe_resource_t *rsc, time_t *last_failure, uint32_t flags, const xmlNode *xml_op); pe_action_t *pe__clear_failcount(pe_resource_t *rsc, const pe_node_t *node, const char *reason, pe_working_set_t *data_set); /* Functions for finding/counting a resource's active nodes */ bool pe__count_active_node(const pe_resource_t *rsc, pe_node_t *node, pe_node_t **active, 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 (rsc == NULL)? NULL : rsc->fns->active_node(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(const 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); void pe__show_node_weights_as(const char *file, const char *function, int line, bool to_log, const 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); pe_action_t *find_first_action(const GList *input, const char *uuid, const char *task, const pe_node_t *on_node); enum action_tasks get_complex_task(const pe_resource_t *rsc, const char *name); 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); void resource_location(pe_resource_t *rsc, const 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); gboolean get_target_role(const 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); pe_resource_t *find_clone_instance(const pe_resource_t *rsc, const char *sub_id); 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(const 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(const xmlNode *xml_op); gint pe__cmp_node_name(gconstpointer a, gconstpointer b); bool is_set_recursive(const 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, const pe_node_t *node, const 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, const 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(const 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); //! \deprecated This function will be removed in a future release void print_rscs_brief(GList *rsc_list, const char * pre_text, long options, void * print_data, gboolean print_all); 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); //! \deprecated This function will be removed in a future release void common_print(pe_resource_t *rsc, const char *pre_text, const char *name, const pe_node_t *node, long options, void *print_data); int pe__common_output_text(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, unsigned int options); int pe__common_output_html(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, unsigned int options); GList *pe__bundle_containers(const pe_resource_t *bundle); int pe__bundle_max(const pe_resource_t *rsc); -int pe__bundle_max_per_node(const pe_resource_t *rsc); 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); 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(const pe_node_t *node, const char *name); bool pe__is_universal_clone(const pe_resource_t *rsc, const pe_working_set_t *data_set); void pe__add_param_check(const 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*, const xmlNode*, enum pe_check_parameters)); void pe__free_param_checks(pe_working_set_t *data_set); bool pe__shutdown_requested(const 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. * * \param[in,out] out Output object to register messages with */ void pe__register_messages(pcmk__output_t *out); void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, const 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(const pe_resource_t *rsc); pe_action_t *pe__clear_resource_history(pe_resource_t *rsc, const 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_only(const pe_resource_t *rsc, const pe_node_t *node); 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(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__clone_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__group_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); gboolean pe__native_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent); xmlNode *pe__failed_probe_for_rsc(const pe_resource_t *rsc, const char *name); const char *pe__clone_child_id(const 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)); } /*! * \internal * \brief Return a string suitable for logging as a node name * * \param[in] node Node to return a node name string for * * \return Node name if available, otherwise node ID if available, * otherwise "unspecified node" if node is NULL or "unidentified node" * if node has neither a name nor ID. */ static inline const char * pe__node_name(const pe_node_t *node) { if (node == NULL) { return "unspecified node"; } else if (node->details->uname != NULL) { return node->details->uname; } else if (node->details->id != NULL) { return node->details->id; } else { return "unidentified node"; } } /*! * \internal * \brief Check whether two node objects refer to the same node * * \param[in] node1 First node object to compare * \param[in] node2 Second node object to compare * * \return true if \p node1 and \p node2 refer to the same node */ static inline bool pe__same_node(const pe_node_t *node1, const pe_node_t *node2) { return (node1 != NULL) && (node2 != NULL) && (node1->details == node2->details); } /*! * \internal * \brief Get the operation key from an action history entry * * \param[in] xml Action history entry * * \return Entry's operation key */ static inline const char * pe__xe_history_key(const xmlNode *xml) { if (xml == NULL) { return NULL; } else { /* @COMPAT Pacemaker <= 1.1.5 did not add the key, and used the ID * instead. Checking for that allows us to process old saved CIBs, * including some regression tests. */ const char *key = crm_element_value(xml, XML_LRM_ATTR_TASK_KEY); return pcmk__str_empty(key)? ID(xml) : key; } } #endif diff --git a/include/crm/pengine/pe_types.h b/include/crm/pengine/pe_types.h index cc626c890b..29944a1b3a 100644 --- a/include/crm/pengine/pe_types.h +++ b/include/crm/pengine/pe_types.h @@ -1,568 +1,576 @@ /* * Copyright 2004-2023 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_PE_TYPES__H # define PCMK__CRM_PENGINE_PE_TYPES__H # include // bool # include // time_t # include // xmlNode # include // gboolean, guint, GList, GHashTable # include # include #ifdef __cplusplus extern "C" { #endif /*! * \file * \brief Data types for cluster status * \ingroup pengine */ typedef struct pe_node_s pe_node_t; typedef struct pe_action_s pe_action_t; typedef struct pe_resource_s pe_resource_t; typedef struct pe_working_set_s pe_working_set_t; enum pe_obj_types { pe_unknown = -1, pe_native = 0, pe_group = 1, pe_clone = 2, pe_container = 3, }; typedef struct resource_object_functions_s { gboolean (*unpack) (pe_resource_t*, pe_working_set_t*); pe_resource_t *(*find_rsc) (pe_resource_t *parent, const char *search, const pe_node_t *node, int flags); /* parameter result must be free'd */ char *(*parameter) (pe_resource_t*, pe_node_t*, gboolean, const char*, pe_working_set_t*); //! \deprecated will be removed in a future release void (*print) (pe_resource_t*, const char*, long, void*); gboolean (*active) (pe_resource_t*, gboolean); enum rsc_role_e (*state) (const pe_resource_t*, gboolean); pe_node_t *(*location) (const pe_resource_t*, GList**, int); void (*free) (pe_resource_t*); void (*count) (pe_resource_t*); gboolean (*is_filtered) (const pe_resource_t*, GList *, gboolean); /*! - * \brief - * \internal Find a node (and optionally count all) where resource is active + * \brief Find a node (and optionally count all) where resource is active * * \param[in] rsc Resource to check * \param[out] count_all If not NULL, set this to count of active nodes * \param[out] count_clean If not NULL, set this to count of clean nodes * * \return A node where the resource is active, preferring the source node * if the resource is involved in a partial migration or a clean, * online node if the resource's "requires" is "quorum" or * "nothing", or NULL if the resource is inactive. */ pe_node_t *(*active_node)(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); + + /*! + * \brief Get maximum resource instances per node + * + * \param[in] rsc Resource to check + * + * \return Maximum number of \p rsc instances that can be active on one node + */ + unsigned int (*max_per_node)(const pe_resource_t *rsc); } resource_object_functions_t; typedef struct resource_alloc_functions_s resource_alloc_functions_t; enum pe_quorum_policy { no_quorum_freeze, no_quorum_stop, no_quorum_ignore, no_quorum_suicide, no_quorum_demote }; enum node_type { node_ping, //! \deprecated Do not use node_member, node_remote }; //! \deprecated will be removed in a future release enum pe_restart { pe_restart_restart, //! \deprecated will be removed in a future release pe_restart_ignore //! \deprecated will be removed in a future release }; //! Determine behavior of pe_find_resource_with_flags() enum pe_find { pe_find_renamed = 0x001, //!< match resource ID or LRM history ID pe_find_anon = 0x002, //!< match base name of anonymous clone instances pe_find_clone = 0x004, //!< match only clone instances pe_find_current = 0x008, //!< match resource active on specified node pe_find_inactive = 0x010, //!< match resource not running anywhere pe_find_any = 0x020, //!< match base name of any clone instance }; // @TODO Make these an enum # define pe_flag_have_quorum 0x00000001ULL # define pe_flag_symmetric_cluster 0x00000002ULL # define pe_flag_maintenance_mode 0x00000008ULL # define pe_flag_stonith_enabled 0x00000010ULL # define pe_flag_have_stonith_resource 0x00000020ULL # define pe_flag_enable_unfencing 0x00000040ULL # define pe_flag_concurrent_fencing 0x00000080ULL # define pe_flag_stop_rsc_orphans 0x00000100ULL # define pe_flag_stop_action_orphans 0x00000200ULL # define pe_flag_stop_everything 0x00000400ULL # define pe_flag_start_failure_fatal 0x00001000ULL //! \deprecated # define pe_flag_remove_after_stop 0x00002000ULL # define pe_flag_startup_fencing 0x00004000ULL # define pe_flag_shutdown_lock 0x00008000ULL # define pe_flag_startup_probes 0x00010000ULL # define pe_flag_have_status 0x00020000ULL # define pe_flag_have_remote_nodes 0x00040000ULL # define pe_flag_quick_location 0x00100000ULL # define pe_flag_sanitized 0x00200000ULL //! \deprecated # define pe_flag_stdout 0x00400000ULL //! Don't count total, disabled and blocked resource instances # define pe_flag_no_counts 0x00800000ULL /*! Skip deprecated code that is kept solely for backward API compatibility. * (Internal code should always set this.) */ # define pe_flag_no_compat 0x01000000ULL # define pe_flag_show_scores 0x02000000ULL # define pe_flag_show_utilization 0x04000000ULL /*! * When scheduling, only unpack the CIB (including constraints), calculate * as much cluster status as possible, and apply node health. */ # define pe_flag_check_config 0x08000000ULL struct pe_working_set_s { xmlNode *input; crm_time_t *now; /* options extracted from the input */ char *dc_uuid; pe_node_t *dc_node; const char *stonith_action; const char *placement_strategy; unsigned long long flags; int stonith_timeout; enum pe_quorum_policy no_quorum_policy; GHashTable *config_hash; GHashTable *tickets; // Actions for which there can be only one (e.g. fence nodeX) GHashTable *singletons; GList *nodes; GList *resources; GList *placement_constraints; GList *ordering_constraints; GList *colocation_constraints; GList *ticket_constraints; GList *actions; xmlNode *failed; xmlNode *op_defaults; xmlNode *rsc_defaults; /* stats */ int num_synapse; int max_valid_nodes; //! Deprecated (will be removed in a future release) int order_id; int action_id; /* final output */ xmlNode *graph; GHashTable *template_rsc_sets; const char *localhost; GHashTable *tags; int blocked_resources; int disabled_resources; GList *param_check; // History entries that need to be checked GList *stop_needed; // Containers that need stop actions time_t recheck_by; // Hint to controller to re-run scheduler by this time int ninstances; // Total number of resource instances guint shutdown_lock;// How long (seconds) to lock resources to shutdown node int priority_fencing_delay; // Priority fencing delay void *priv; }; enum pe_check_parameters { /* Clear fail count if parameters changed for un-expired start or monitor * last_failure. */ pe_check_last_failure, /* Clear fail count if parameters changed for start, monitor, promote, or * migrate_from actions for active resources. */ pe_check_active, }; struct pe_node_shared_s { const char *id; const char *uname; enum node_type type; /* @TODO convert these flags into a bitfield */ gboolean online; gboolean standby; gboolean standby_onfail; gboolean pending; gboolean unclean; gboolean unseen; gboolean shutdown; gboolean expected_up; gboolean is_dc; gboolean maintenance; gboolean rsc_discovery_enabled; gboolean remote_requires_reset; gboolean remote_was_fenced; gboolean remote_maintenance; /* what the remote-rsc is thinking */ gboolean unpacked; int num_resources; pe_resource_t *remote_rsc; GList *running_rsc; /* pe_resource_t* */ GList *allocated_rsc; /* pe_resource_t* */ GHashTable *attrs; /* char* => char* */ GHashTable *utilization; GHashTable *digest_cache; //!< cache of calculated resource digests int priority; // calculated based on the priority of resources running on the node pe_working_set_t *data_set; //!< Cluster that this node is part of }; struct pe_node_s { int weight; gboolean fixed; //!< \deprecated Will be removed in a future release int count; struct pe_node_shared_s *details; int rsc_discover_mode; }; # define pe_rsc_orphan 0x00000001ULL # define pe_rsc_managed 0x00000002ULL # define pe_rsc_block 0x00000004ULL # define pe_rsc_orphan_container_filler 0x00000008ULL # define pe_rsc_notify 0x00000010ULL # define pe_rsc_unique 0x00000020ULL # define pe_rsc_fence_device 0x00000040ULL # define pe_rsc_promotable 0x00000080ULL # define pe_rsc_provisional 0x00000100ULL # define pe_rsc_allocating 0x00000200ULL # define pe_rsc_merging 0x00000400ULL # define pe_rsc_restarting 0x00000800ULL # define pe_rsc_stop 0x00001000ULL # define pe_rsc_reload 0x00002000ULL # define pe_rsc_allow_remote_remotes 0x00004000ULL # define pe_rsc_critical 0x00008000ULL # define pe_rsc_failed 0x00010000ULL # define pe_rsc_detect_loop 0x00020000ULL # define pe_rsc_runnable 0x00040000ULL # define pe_rsc_start_pending 0x00080000ULL //!< \deprecated Do not use # define pe_rsc_starting 0x00100000ULL //!< \deprecated Do not use # define pe_rsc_stopping 0x00200000ULL # define pe_rsc_stop_unexpected 0x00400000ULL # define pe_rsc_allow_migrate 0x00800000ULL # define pe_rsc_failure_ignored 0x01000000ULL # define pe_rsc_replica_container 0x02000000ULL # define pe_rsc_maintenance 0x04000000ULL # define pe_rsc_is_container 0x08000000ULL # define pe_rsc_needs_quorum 0x10000000ULL # define pe_rsc_needs_fencing 0x20000000ULL # define pe_rsc_needs_unfencing 0x40000000ULL /* *INDENT-OFF* */ enum pe_action_flags { pe_action_pseudo = 0x00001, pe_action_runnable = 0x00002, pe_action_optional = 0x00004, pe_action_print_always = 0x00008, pe_action_have_node_attrs = 0x00010, pe_action_implied_by_stonith = 0x00040, pe_action_migrate_runnable = 0x00080, pe_action_dumped = 0x00100, pe_action_processed = 0x00200, #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) pe_action_clear = 0x00400, //! \deprecated Unused #endif pe_action_dangle = 0x00800, /* This action requires one or more of its dependencies to be runnable. * We use this to clear the runnable flag before checking dependencies. */ pe_action_requires_any = 0x01000, pe_action_reschedule = 0x02000, pe_action_tracking = 0x04000, pe_action_dedup = 0x08000, //! Internal state tracking when creating graph pe_action_dc = 0x10000, //! Action may run on DC instead of target }; /* *INDENT-ON* */ struct pe_resource_s { char *id; char *clone_name; xmlNode *xml; xmlNode *orig_xml; xmlNode *ops_xml; pe_working_set_t *cluster; pe_resource_t *parent; enum pe_obj_types variant; void *variant_opaque; resource_object_functions_t *fns; resource_alloc_functions_t *cmds; enum rsc_recovery_type recovery_type; enum pe_restart restart_type; //!< \deprecated will be removed in future release int priority; int stickiness; int sort_index; int failure_timeout; int migration_threshold; guint remote_reconnect_ms; char *pending_task; unsigned long long flags; // @TODO merge these into flags gboolean is_remote_node; gboolean exclusive_discover; /* Pay special attention to whether you want to use rsc_cons_lhs and * rsc_cons directly, which include only colocations explicitly involving * this resource, or call libpacemaker's pcmk__with_this_colocations() and * pcmk__this_with_colocations() functions, which may return relevant * colocations involving the resource's ancestors as well. */ //!@{ //! This field should be treated as internal to Pacemaker GList *rsc_cons_lhs; // List of pcmk__colocation_t* GList *rsc_cons; // List of pcmk__colocation_t* GList *rsc_location; // List of pe__location_t* GList *actions; // List of pe_action_t* GList *rsc_tickets; // List of rsc_ticket* //!@} pe_node_t *allocated_to; pe_node_t *partial_migration_target; pe_node_t *partial_migration_source; GList *running_on; /* pe_node_t* */ GHashTable *known_on; /* pe_node_t* */ GHashTable *allowed_nodes; /* pe_node_t* */ enum rsc_role_e role; enum rsc_role_e next_role; GHashTable *meta; GHashTable *parameters; //! \deprecated Use pe_rsc_params() instead GHashTable *utilization; GList *children; /* pe_resource_t* */ GList *dangling_migrations; /* pe_node_t* */ pe_resource_t *container; GList *fillers; // @COMPAT These should be made const at next API compatibility break pe_node_t *pending_node; // Node on which pending_task is happening pe_node_t *lock_node; // Resource is shutdown-locked to this node time_t lock_time; // When shutdown lock started /* Resource parameters may have node-attribute-based rules, which means the * values can vary by node. This table is a cache of parameter name/value * tables for each node (as needed). Use pe_rsc_params() to get the table * for a given node. */ GHashTable *parameter_cache; // Key = node name, value = parameters table }; struct pe_action_s { int id; int priority; pe_resource_t *rsc; pe_node_t *node; xmlNode *op_entry; char *task; char *uuid; char *cancel_task; char *reason; enum pe_action_flags flags; enum rsc_start_requirement needs; enum action_fail_response on_fail; enum rsc_role_e fail_role; GHashTable *meta; GHashTable *extra; /* * These two varables are associated with the constraint logic * that involves first having one or more actions runnable before * then allowing this action to execute. * * These varables are used with features such as 'clone-min' which * requires at minimum X number of cloned instances to be running * before an order dependency can run. Another option that uses * this is 'require-all=false' in ordering constrants. This option * says "only require one instance of a resource to start before * allowing dependencies to start" -- basically, require-all=false is * the same as clone-min=1. */ /* current number of known runnable actions in the before list. */ int runnable_before; /* the number of "before" runnable actions required for this action * to be considered runnable */ int required_runnable_before; GList *actions_before; /* pe_action_wrapper_t* */ GList *actions_after; /* pe_action_wrapper_t* */ /* Some of the above fields could be moved to the details, * except for API backward compatibility. */ void *action_details; // varies by type of action }; typedef struct pe_ticket_s { char *id; gboolean granted; time_t last_granted; gboolean standby; GHashTable *state; } pe_ticket_t; typedef struct pe_tag_s { char *id; GList *refs; } pe_tag_t; //! Internal tracking for transition graph creation enum pe_link_state { pe_link_not_dumped, //! Internal tracking for transition graph creation pe_link_dumped, //! Internal tracking for transition graph creation pe_link_dup, //! \deprecated No longer used by Pacemaker }; enum pe_discover_e { pe_discover_always = 0, pe_discover_never, pe_discover_exclusive, }; /* *INDENT-OFF* */ enum pe_ordering { pe_order_none = 0x0, /* deleted */ pe_order_optional = 0x1, /* pure ordering, nothing implied */ pe_order_apply_first_non_migratable = 0x2, /* Only apply this constraint's ordering if first is not migratable. */ pe_order_implies_first = 0x10, /* If 'then' is required, ensure 'first' is too */ pe_order_implies_then = 0x20, /* If 'first' is required, ensure 'then' is too */ pe_order_promoted_implies_first = 0x40, /* If 'then' is required and then's rsc is promoted, ensure 'first' becomes required too */ /* first requires then to be both runnable and migrate runnable. */ pe_order_implies_first_migratable = 0x80, pe_order_runnable_left = 0x100, /* 'then' requires 'first' to be runnable */ pe_order_pseudo_left = 0x200, /* 'then' can only be pseudo if 'first' is runnable */ pe_order_implies_then_on_node = 0x400, /* If 'first' is required on 'nodeX', * ensure instances of 'then' on 'nodeX' are too. * Only really useful if 'then' is a clone and 'first' is not */ pe_order_probe = 0x800, /* If 'first->rsc' is * - running but about to stop, ignore the constraint * - otherwise, behave as runnable_left */ pe_order_restart = 0x1000, /* 'then' is runnable if 'first' is optional or runnable */ pe_order_stonith_stop = 0x2000, // #endif #ifdef __cplusplus } #endif #endif // PCMK__CRM_PENGINE_PE_TYPES__H diff --git a/include/pcmki/pcmki_sched_utils.h b/include/pcmki/pcmki_sched_utils.h index 3e6d52f316..154856aea1 100644 --- a/include/pcmki/pcmki_sched_utils.h +++ b/include/pcmki/pcmki_sched_utils.h @@ -1,33 +1,31 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__PCMKI_PCMKI_SCHED_UTILS__H # define PCMK__PCMKI_PCMKI_SCHED_UTILS__H #include // bool #include // GList, GHashTable, gboolean, guint #include // lrmd_event_data_t #include // cib_t #include #include #include #include #include #include /* Constraint helper functions */ GList *pcmk__copy_node_list(const GList *list, bool reset); -int copies_per_node(pe_resource_t * rsc); - xmlNode *pcmk__create_history_xml(xmlNode *parent, lrmd_event_data_t *event, const char *caller_version, int target_rc, const char *node, const char *origin); #endif diff --git a/lib/pacemaker/pcmk_sched_bundle.c b/lib/pacemaker/pcmk_sched_bundle.c index 5682744395..d1b76baafd 100644 --- a/lib/pacemaker/pcmk_sched_bundle.c +++ b/lib/pacemaker/pcmk_sched_bundle.c @@ -1,876 +1,835 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include "libpacemaker_private.h" #define PE__VARIANT_BUNDLE 1 #include static bool is_bundle_node(pe__bundle_variant_data_t *data, pe_node_t *node) { for (GList *gIter = data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (node->details == replica->node->details) { return TRUE; } } return FALSE; } /*! * \internal * \brief Assign a bundle resource to a node * * \param[in,out] rsc Resource to assign to a node * \param[in] prefer Node to prefer, if all else is equal * * \return Node that \p rsc is assigned to, if assigned entirely to one node */ pe_node_t * pcmk__bundle_allocate(pe_resource_t *rsc, const pe_node_t *prefer) { GList *containers = NULL; pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return NULL); get_bundle_variant_data(bundle_data, rsc); pe__set_resource_flags(rsc, pe_rsc_allocating); containers = pe__bundle_containers(rsc); pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_scores), rsc, __func__, rsc->allowed_nodes, rsc->cluster); containers = g_list_sort(containers, pcmk__cmp_instance); pcmk__assign_instances(rsc, containers, bundle_data->nreplicas, bundle_data->nreplicas_per_host); g_list_free(containers); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; pe_node_t *container_host = NULL; CRM_ASSERT(replica); if (replica->ip) { pe_rsc_trace(rsc, "Allocating bundle %s IP %s", rsc->id, replica->ip->id); replica->ip->cmds->assign(replica->ip, prefer); } container_host = replica->container->allocated_to; if (replica->remote && pe__is_guest_or_remote_node(container_host)) { /* We need 'nested' connection resources to be on the same * host because pacemaker-remoted only supports a single * active connection */ pcmk__new_colocation("child-remote-with-docker-remote", NULL, INFINITY, replica->remote, container_host->details->remote_rsc, NULL, NULL, true, rsc->cluster); } if (replica->remote) { pe_rsc_trace(rsc, "Allocating bundle %s connection %s", rsc->id, replica->remote->id); replica->remote->cmds->assign(replica->remote, prefer); } // Explicitly allocate replicas' children before bundle child if (replica->child) { pe_node_t *node = NULL; GHashTableIter iter; g_hash_table_iter_init(&iter, replica->child->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) { if (node->details != replica->node->details) { node->weight = -INFINITY; } else if (!pcmk__threshold_reached(replica->child, node, NULL)) { node->weight = INFINITY; } } pe__set_resource_flags(replica->child->parent, pe_rsc_allocating); pe_rsc_trace(rsc, "Allocating bundle %s replica child %s", rsc->id, replica->child->id); replica->child->cmds->assign(replica->child, replica->node); pe__clear_resource_flags(replica->child->parent, pe_rsc_allocating); } } if (bundle_data->child) { pe_node_t *node = NULL; GHashTableIter iter; g_hash_table_iter_init(&iter, bundle_data->child->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) & node)) { if (is_bundle_node(bundle_data, node)) { node->weight = 0; } else { node->weight = -INFINITY; } } pe_rsc_trace(rsc, "Allocating bundle %s child %s", rsc->id, bundle_data->child->id); bundle_data->child->cmds->assign(bundle_data->child, prefer); } pe__clear_resource_flags(rsc, pe_rsc_allocating|pe_rsc_provisional); return NULL; } void pcmk__bundle_create_actions(pe_resource_t *rsc) { pe_action_t *action = NULL; GList *containers = NULL; pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); containers = pe__bundle_containers(rsc); get_bundle_variant_data(bundle_data, rsc); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (replica->ip) { replica->ip->cmds->create_actions(replica->ip); } if (replica->container) { replica->container->cmds->create_actions(replica->container); } if (replica->remote) { replica->remote->cmds->create_actions(replica->remote); } } pcmk__create_instance_actions(rsc, containers); if (bundle_data->child) { bundle_data->child->cmds->create_actions(bundle_data->child); if (pcmk_is_set(bundle_data->child->flags, pe_rsc_promotable)) { /* promote */ pe__new_rsc_pseudo_action(rsc, RSC_PROMOTE, true, true); action = pe__new_rsc_pseudo_action(rsc, RSC_PROMOTED, true, true); action->priority = INFINITY; /* demote */ pe__new_rsc_pseudo_action(rsc, RSC_DEMOTE, true, true); action = pe__new_rsc_pseudo_action(rsc, RSC_DEMOTED, true, true); action->priority = INFINITY; } } g_list_free(containers); } void pcmk__bundle_internal_constraints(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); if (bundle_data->child) { pcmk__order_resource_actions(rsc, RSC_START, bundle_data->child, RSC_START, pe_order_implies_first_printed); pcmk__order_resource_actions(rsc, RSC_STOP, bundle_data->child, RSC_STOP, pe_order_implies_first_printed); if (bundle_data->child->children) { pcmk__order_resource_actions(bundle_data->child, RSC_STARTED, rsc, RSC_STARTED, pe_order_implies_then_printed); pcmk__order_resource_actions(bundle_data->child, RSC_STOPPED, rsc, RSC_STOPPED, pe_order_implies_then_printed); } else { pcmk__order_resource_actions(bundle_data->child, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed); pcmk__order_resource_actions(bundle_data->child, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed); } } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); CRM_ASSERT(replica->container); replica->container->cmds->internal_constraints(replica->container); pcmk__order_starts(rsc, replica->container, pe_order_runnable_left|pe_order_implies_first_printed); if (replica->child) { pcmk__order_stops(rsc, replica->child, pe_order_implies_first_printed); } pcmk__order_stops(rsc, replica->container, pe_order_implies_first_printed); pcmk__order_resource_actions(replica->container, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed); pcmk__order_resource_actions(replica->container, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed); if (replica->ip) { replica->ip->cmds->internal_constraints(replica->ip); // Start IP then container pcmk__order_starts(replica->ip, replica->container, pe_order_runnable_left|pe_order_preserve); pcmk__order_stops(replica->container, replica->ip, pe_order_implies_first|pe_order_preserve); pcmk__new_colocation("ip-with-docker", NULL, INFINITY, replica->ip, replica->container, NULL, NULL, true, rsc->cluster); } if (replica->remote) { /* This handles ordering and colocating remote relative to container * (via "resource-with-container"). Since IP is also ordered and * colocated relative to the container, we don't need to do anything * explicit here with IP. */ replica->remote->cmds->internal_constraints(replica->remote); } if (replica->child) { CRM_ASSERT(replica->remote); // "Start remote then child" is implicit in scheduler's remote logic } } if (bundle_data->child) { bundle_data->child->cmds->internal_constraints(bundle_data->child); if (pcmk_is_set(bundle_data->child->flags, pe_rsc_promotable)) { pcmk__promotable_restart_ordering(rsc); /* child demoted before global demoted */ pcmk__order_resource_actions(bundle_data->child, RSC_DEMOTED, rsc, RSC_DEMOTED, pe_order_implies_then_printed); /* global demote before child demote */ pcmk__order_resource_actions(rsc, RSC_DEMOTE, bundle_data->child, RSC_DEMOTE, pe_order_implies_first_printed); /* child promoted before global promoted */ pcmk__order_resource_actions(bundle_data->child, RSC_PROMOTED, rsc, RSC_PROMOTED, pe_order_implies_then_printed); /* global promote before child promote */ pcmk__order_resource_actions(rsc, RSC_PROMOTE, bundle_data->child, RSC_PROMOTE, pe_order_implies_first_printed); } } } static pe_resource_t * compatible_replica_for_node(const pe_resource_t *rsc_lh, const pe_node_t *candidate, const pe_resource_t *rsc, enum rsc_role_e filter, gboolean current) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(candidate != NULL, return NULL); get_bundle_variant_data(bundle_data, rsc); crm_trace("Looking for compatible child from %s for %s on %s", rsc_lh->id, rsc->id, pe__node_name(candidate)); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (pcmk__instance_matches(replica->container, candidate, filter, current)) { crm_trace("Pairing %s with %s on %s", rsc_lh->id, replica->container->id, pe__node_name(candidate)); return replica->container; } } crm_trace("Can't pair %s with %s", rsc_lh->id, rsc->id); return NULL; } static pe_resource_t * compatible_replica(const pe_resource_t *rsc_lh, const pe_resource_t *rsc, enum rsc_role_e filter, gboolean current, pe_working_set_t *data_set) { GList *scratch = NULL; pe_resource_t *pair = NULL; pe_node_t *active_node_lh = NULL; active_node_lh = rsc_lh->fns->location(rsc_lh, NULL, current); if (active_node_lh) { return compatible_replica_for_node(rsc_lh, active_node_lh, rsc, filter, current); } scratch = g_hash_table_get_values(rsc_lh->allowed_nodes); scratch = pcmk__sort_nodes(scratch, NULL); for (GList *gIter = scratch; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; pair = compatible_replica_for_node(rsc_lh, node, rsc, filter, current); if (pair) { goto done; } } pe_rsc_debug(rsc, "Can't pair %s with %s", rsc_lh->id, (rsc? rsc->id : "none")); done: g_list_free(scratch); return pair; } -int copies_per_node(pe_resource_t * rsc) -{ - /* Strictly speaking, there should be a 'copies_per_node' addition - * to the resource function table and each case would be a - * function. However that would be serious overkill to return an - * int. In fact, it seems to me that both function tables - * could/should be replaced by resources.{c,h} full of - * rsc_{some_operation} functions containing a switch as below - * which calls out to functions named {variant}_{some_operation} - * as needed. - */ - switch(rsc->variant) { - case pe_unknown: - return 0; - case pe_native: - case pe_group: - return 1; - case pe_clone: - { - const char *max_clones_node = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_NODEMAX); - - if (max_clones_node == NULL) { - return 1; - - } else { - int max_i; - - pcmk__scan_min_int(max_clones_node, &max_i, 0); - return max_i; - } - } - case pe_container: - { - pe__bundle_variant_data_t *data = NULL; - get_bundle_variant_data(data, rsc); - return data->nreplicas_per_host; - } - } - return 0; -} - /*! * \internal * \brief Apply a colocation's score to node weights or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node weights (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void pcmk__bundle_apply_coloc_score(pe_resource_t *dependent, const pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent) { GList *allocated_primaries = NULL; pe__bundle_variant_data_t *bundle_data = NULL; /* This should never be called for the bundle itself as a dependent. * Instead, we add its colocation constraints to its replicas and call the * apply_coloc_score() for the replicas as dependents. */ CRM_ASSERT(!for_dependent); CRM_CHECK((colocation != NULL) && (dependent != NULL) && (primary != NULL), return); CRM_ASSERT(dependent->variant == pe_native); if (pcmk_is_set(primary->flags, pe_rsc_provisional)) { pe_rsc_trace(primary, "%s is still provisional", primary->id); return; } else if (colocation->dependent->variant > pe_group) { pe_resource_t *primary_replica = compatible_replica(dependent, primary, RSC_ROLE_UNKNOWN, FALSE, dependent->cluster); if (primary_replica) { pe_rsc_debug(primary, "Pairing %s with %s", dependent->id, primary_replica->id); dependent->cmds->apply_coloc_score(dependent, primary_replica, colocation, true); } else if (colocation->score >= INFINITY) { crm_notice("Cannot pair %s with instance of %s", dependent->id, primary->id); pcmk__assign_resource(dependent, NULL, true); } else { pe_rsc_debug(primary, "Cannot pair %s with instance of %s", dependent->id, primary->id); } return; } get_bundle_variant_data(bundle_data, primary); pe_rsc_trace(primary, "Processing constraint %s: %s -> %s %d", colocation->id, dependent->id, primary->id, colocation->score); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (colocation->score < INFINITY) { replica->container->cmds->apply_coloc_score(dependent, replica->container, colocation, false); } else { pe_node_t *chosen = replica->container->fns->location(replica->container, NULL, FALSE); if ((chosen == NULL) || is_set_recursive(replica->container, pe_rsc_block, TRUE)) { continue; } if ((colocation->primary_role >= RSC_ROLE_PROMOTED) && (replica->child == NULL)) { continue; } if ((colocation->primary_role >= RSC_ROLE_PROMOTED) && (replica->child->next_role < RSC_ROLE_PROMOTED)) { continue; } pe_rsc_trace(primary, "Allowing %s: %s %d", colocation->id, pe__node_name(chosen), chosen->weight); allocated_primaries = g_list_prepend(allocated_primaries, chosen); } } if (colocation->score >= INFINITY) { node_list_exclude(dependent->allowed_nodes, allocated_primaries, FALSE); } g_list_free(allocated_primaries); } // Bundle implementation of resource_alloc_functions_t:with_this_colocations() void pcmk__with_bundle_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list) { CRM_CHECK((rsc != NULL) && (rsc->variant == pe_container) && (orig_rsc != NULL) && (list != NULL), return); if (rsc == orig_rsc) { // Colocations are wanted for bundle itself pcmk__add_with_this_list(list, rsc->rsc_cons_lhs); // Only the bundle replicas' containers get the bundle's constraints } else if (pcmk_is_set(orig_rsc->flags, pe_rsc_replica_container)) { pcmk__add_collective_constraints(list, orig_rsc, rsc, true); } } // Bundle implementation of resource_alloc_functions_t:this_with_colocations() void pcmk__bundle_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list) { CRM_CHECK((rsc != NULL) && (rsc->variant == pe_container) && (orig_rsc != NULL) && (list != NULL), return); if (rsc == orig_rsc) { // Colocations are wanted for bundle itself pcmk__add_this_with_list(list, rsc->rsc_cons); // Only the bundle replicas' containers get the bundle's constraints } else if (pcmk_is_set(orig_rsc->flags, pe_rsc_replica_container)) { pcmk__add_collective_constraints(list, orig_rsc, rsc, false); } } enum pe_action_flags pcmk__bundle_action_flags(pe_action_t *action, const pe_node_t *node) { GList *containers = NULL; enum pe_action_flags flags = 0; pe__bundle_variant_data_t *data = NULL; get_bundle_variant_data(data, action->rsc); if(data->child) { enum action_tasks task = get_complex_task(data->child, action->task); switch(task) { case no_action: case action_notify: case action_notified: case action_promote: case action_promoted: case action_demote: case action_demoted: return pcmk__collective_action_flags(action, data->child->children, node); default: break; } } containers = pe__bundle_containers(action->rsc); flags = pcmk__collective_action_flags(action, containers, node); g_list_free(containers); return flags; } /*! * \internal * \brief Get containerized resource corresponding to a given bundle container * * \param[in] instance Collective instance that might be a bundle container * * \return Bundled resource instance inside \p instance if it is a bundle * container instance, otherwise NULL */ const pe_resource_t * pcmk__get_rsc_in_container(const pe_resource_t *instance) { const pe__bundle_variant_data_t *data = NULL; const pe_resource_t *top = pe__const_top_resource(instance, true); if ((top == NULL) || (top->variant != pe_container)) { return NULL; } get_bundle_variant_data(data, top); for (const GList *iter = data->replicas; iter != NULL; iter = iter->next) { const pe__bundle_replica_t *replica = iter->data; if (instance == replica->container) { return replica->child; } } return NULL; } void pcmk__bundle_rsc_location(pe_resource_t *rsc, pe__location_t *constraint) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); pcmk__apply_location(rsc, constraint); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (replica->container) { replica->container->cmds->apply_location(replica->container, constraint); } if (replica->ip) { replica->ip->cmds->apply_location(replica->ip, constraint); } } if (bundle_data->child && ((constraint->role_filter == RSC_ROLE_UNPROMOTED) || (constraint->role_filter == RSC_ROLE_PROMOTED))) { bundle_data->child->cmds->apply_location(bundle_data->child, constraint); bundle_data->child->rsc_location = g_list_prepend(bundle_data->child->rsc_location, constraint); } } /*! * \internal * \brief Add a resource's actions to the transition graph * * \param[in,out] rsc Resource whose actions should be added */ void pcmk__bundle_expand(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); if (bundle_data->child) { bundle_data->child->cmds->add_actions_to_graph(bundle_data->child); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (replica->remote && replica->container && pe__bundle_needs_remote_name(replica->remote)) { /* REMOTE_CONTAINER_HACK: Allow remote nodes to run containers that * run pacemaker-remoted inside, without needing a separate IP for * the container. This is done by configuring the inner remote's * connection host as the magic string "#uname", then * replacing it with the underlying host when needed. */ xmlNode *nvpair = get_xpath_object("//nvpair[@name='" XML_RSC_ATTR_REMOTE_RA_ADDR "']", replica->remote->xml, LOG_ERR); const char *calculated_addr = NULL; // Replace the value in replica->remote->xml (if appropriate) calculated_addr = pe__add_bundle_remote_name(replica->remote, rsc->cluster, nvpair, "value"); if (calculated_addr) { /* Since this is for the bundle as a resource, and not any * particular action, replace the value in the default * parameters (not evaluated for node). create_graph_action() * will grab it from there to replace it in node-evaluated * parameters. */ GHashTable *params = pe_rsc_params(replica->remote, NULL, rsc->cluster); g_hash_table_replace(params, strdup(XML_RSC_ATTR_REMOTE_RA_ADDR), strdup(calculated_addr)); } else { /* The only way to get here is if the remote connection is * neither currently running nor scheduled to run. That means we * won't be doing any operations that require addr (only start * requires it; we additionally use it to compare digests when * unpacking status, promote, and migrate_from history, but * that's already happened by this point). */ crm_info("Unable to determine address for bundle %s remote connection", rsc->id); } } if (replica->ip) { replica->ip->cmds->add_actions_to_graph(replica->ip); } if (replica->container) { replica->container->cmds->add_actions_to_graph(replica->container); } if (replica->remote) { replica->remote->cmds->add_actions_to_graph(replica->remote); } } } /*! * \internal * * \brief Schedule any probes needed for a resource on a node * * \param[in,out] rsc Resource to create probe for * \param[in,out] node Node to create probe on * * \return true if any probe was created, otherwise false */ bool pcmk__bundle_create_probe(pe_resource_t *rsc, pe_node_t *node) { bool any_created = false; pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return false); get_bundle_variant_data(bundle_data, rsc); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if ((replica->ip != NULL) && replica->ip->cmds->create_probe(replica->ip, node)) { any_created = true; } if ((replica->child != NULL) && (node->details == replica->node->details) && replica->child->cmds->create_probe(replica->child, node)) { any_created = true; } if ((replica->container != NULL) && replica->container->cmds->create_probe(replica->container, node)) { any_created = true; /* If we're limited to one replica per host (due to * the lack of an IP range probably), then we don't * want any of our peer containers starting until * we've established that no other copies are already * running. * * Partly this is to ensure that nreplicas_per_host is * observed, but also to ensure that the containers * don't fail to start because the necessary port * mappings (which won't include an IP for uniqueness) * are already taken */ for (GList *tIter = bundle_data->replicas; tIter && (bundle_data->nreplicas_per_host == 1); tIter = tIter->next) { pe__bundle_replica_t *other = tIter->data; if ((other != replica) && (other != NULL) && (other->container != NULL)) { pcmk__new_ordering(replica->container, pcmk__op_key(replica->container->id, RSC_STATUS, 0), NULL, other->container, pcmk__op_key(other->container->id, RSC_START, 0), NULL, pe_order_optional|pe_order_same_node, rsc->cluster); } } } if ((replica->container != NULL) && (replica->remote != NULL) && replica->remote->cmds->create_probe(replica->remote, node)) { /* Do not probe the remote resource until we know where the * container is running. This is required for REMOTE_CONTAINER_HACK * to correctly probe remote resources. */ char *probe_uuid = pcmk__op_key(replica->remote->id, RSC_STATUS, 0); pe_action_t *probe = find_first_action(replica->remote->actions, probe_uuid, NULL, node); free(probe_uuid); if (probe != NULL) { any_created = true; crm_trace("Ordering %s probe on %s", replica->remote->id, pe__node_name(node)); pcmk__new_ordering(replica->container, pcmk__op_key(replica->container->id, RSC_START, 0), NULL, replica->remote, NULL, probe, pe_order_probe, rsc->cluster); } } } return any_created; } void pcmk__output_bundle_actions(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (replica->ip != NULL) { replica->ip->cmds->output_actions(replica->ip); } if (replica->container != NULL) { replica->container->cmds->output_actions(replica->container); } if (replica->remote != NULL) { replica->remote->cmds->output_actions(replica->remote); } if (replica->child != NULL) { replica->child->cmds->output_actions(replica->child); } } } // Bundle implementation of resource_alloc_functions_t:add_utilization() void pcmk__bundle_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization) { pe__bundle_variant_data_t *bundle_data = NULL; pe__bundle_replica_t *replica = NULL; if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { return; } get_bundle_variant_data(bundle_data, rsc); if (bundle_data->replicas == NULL) { return; } /* All bundle replicas are identical, so using the utilization of the first * is sufficient for any. Only the implicit container resource can have * utilization values. */ replica = (pe__bundle_replica_t *) bundle_data->replicas->data; if (replica->container != NULL) { replica->container->cmds->add_utilization(replica->container, orig_rsc, all_rscs, utilization); } } // Bundle implementation of resource_alloc_functions_t:shutdown_lock() void pcmk__bundle_shutdown_lock(pe_resource_t *rsc) { return; // Bundles currently don't support shutdown locks } diff --git a/lib/pacemaker/pcmk_sched_clone.c b/lib/pacemaker/pcmk_sched_clone.c index 89898d1934..41bd59d23a 100644 --- a/lib/pacemaker/pcmk_sched_clone.c +++ b/lib/pacemaker/pcmk_sched_clone.c @@ -1,668 +1,669 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include "libpacemaker_private.h" /*! * \internal * \brief Assign a clone resource's instances to nodes * * \param[in,out] rsc Clone resource to assign * \param[in] prefer Node to prefer, if all else is equal * * \return NULL (clones are not assigned to a single node) */ pe_node_t * pcmk__clone_assign(pe_resource_t *rsc, const pe_node_t *prefer) { CRM_ASSERT(pe_rsc_is_clone(rsc)); if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { return NULL; // Assignment has already been done } // Detect assignment loops if (pcmk_is_set(rsc->flags, pe_rsc_allocating)) { pe_rsc_debug(rsc, "Breaking assignment loop involving %s", rsc->id); return NULL; } pe__set_resource_flags(rsc, pe_rsc_allocating); // If this clone is promotable, consider nodes' promotion scores if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__add_promotion_scores(rsc); } /* If this clone is colocated with any other resources, assign those first. * Since the this_with_colocations() method boils down to a copy of rsc_cons * for clones, we can use that here directly for efficiency. */ for (GList *iter = rsc->rsc_cons; iter != NULL; iter = iter->next) { pcmk__colocation_t *constraint = (pcmk__colocation_t *) iter->data; pe_rsc_trace(rsc, "%s: Assigning colocation %s primary %s first", rsc->id, constraint->id, constraint->primary->id); constraint->primary->cmds->assign(constraint->primary, prefer); } /* If any resources are colocated with this one, consider their preferences. * Because the with_this_colocations() method boils down to a copy of * rsc_cons_lhs for clones, we can use that here directly for efficiency. */ g_list_foreach(rsc->rsc_cons_lhs, pcmk__add_dependent_scores, rsc); pe__show_node_weights(!pcmk_is_set(rsc->cluster->flags, pe_flag_show_scores), rsc, __func__, rsc->allowed_nodes, rsc->cluster); rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance); pcmk__assign_instances(rsc, rsc->children, pe__clone_max(rsc), pe__clone_node_max(rsc)); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__set_instance_roles(rsc); } pe__clear_resource_flags(rsc, pe_rsc_provisional|pe_rsc_allocating); pe_rsc_trace(rsc, "Assigned clone %s", rsc->id); return NULL; } /*! * \internal * \brief Create all actions needed for a given clone resource * * \param[in,out] rsc Clone resource to create actions for */ void pcmk__clone_create_actions(pe_resource_t *rsc) { CRM_ASSERT(pe_rsc_is_clone(rsc)); pe_rsc_trace(rsc, "Creating actions for clone %s", rsc->id); pcmk__create_instance_actions(rsc, rsc->children); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__create_promotable_actions(rsc); } } /*! * \internal * \brief Create implicit constraints needed for a clone resource * * \param[in,out] rsc Clone resource to create implicit constraints for */ void pcmk__clone_internal_constraints(pe_resource_t *rsc) { bool ordered = pe__clone_is_ordered(rsc); CRM_ASSERT(pe_rsc_is_clone(rsc)); pe_rsc_trace(rsc, "Creating internal constraints for clone %s", rsc->id); // Restart ordering: Stop -> stopped -> start -> started pcmk__order_resource_actions(rsc, RSC_STOPPED, rsc, RSC_START, pe_order_optional); pcmk__order_resource_actions(rsc, RSC_START, rsc, RSC_STARTED, pe_order_runnable_left); pcmk__order_resource_actions(rsc, RSC_STOP, rsc, RSC_STOPPED, pe_order_runnable_left); // Demoted -> stop and started -> promote if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__order_resource_actions(rsc, RSC_DEMOTED, rsc, RSC_STOP, pe_order_optional); pcmk__order_resource_actions(rsc, RSC_STARTED, rsc, RSC_PROMOTE, pe_order_runnable_left); } if (ordered) { /* Ordered clone instances must start and stop by instance number. The * instances might have been previously shuffled for assignment or * promotion purposes, so re-sort them. */ rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number); } for (GList *iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *instance = (pe_resource_t *) iter->data; instance->cmds->internal_constraints(instance); // Start clone -> start instance -> clone started pcmk__order_starts(rsc, instance, pe_order_runnable_left |pe_order_implies_first_printed); pcmk__order_resource_actions(instance, RSC_START, rsc, RSC_STARTED, pe_order_implies_then_printed); // Stop clone -> stop instance -> clone stopped pcmk__order_stops(rsc, instance, pe_order_implies_first_printed); pcmk__order_resource_actions(instance, RSC_STOP, rsc, RSC_STOPPED, pe_order_implies_then_printed); /* Instances of unique clones must be started and stopped by instance * number. Since only some instances may be starting or stopping, order * each instance relative to every later instance. */ if (ordered) { for (GList *later = iter->next; later != NULL; later = later->next) { pcmk__order_starts(instance, (pe_resource_t *) later->data, pe_order_optional); pcmk__order_stops((pe_resource_t *) later->data, instance, pe_order_optional); } } } if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__order_promotable_instances(rsc); } } /*! * \internal * \brief Check whether colocated resources can be interleaved * * \param[in] colocation Colocation constraint with clone as primary * * \return true if colocated resources can be interleaved, otherwise false */ static bool can_interleave(const pcmk__colocation_t *colocation) { + const pe_resource_t *dependent = colocation->dependent; + // Only colocations between clone or bundle resources use interleaving - if (colocation->dependent->variant <= pe_group) { + if (dependent->variant <= pe_group) { return false; } // Only the dependent needs to be marked for interleaving - if (!crm_is_true(g_hash_table_lookup(colocation->dependent->meta, + if (!crm_is_true(g_hash_table_lookup(dependent->meta, XML_RSC_ATTR_INTERLEAVE))) { return false; } /* @TODO Do we actually care about multiple primary instances sharing a * dependent instance? */ - if (copies_per_node(colocation->dependent) - != copies_per_node(colocation->primary)) { + if (dependent->fns->max_per_node(dependent) + != colocation->primary->fns->max_per_node(colocation->primary)) { pcmk__config_err("Cannot interleave %s and %s because they do not " "support the same number of instances per node", - colocation->dependent->id, - colocation->primary->id); + dependent->id, colocation->primary->id); return false; } return true; } /*! * \internal * \brief Apply a colocation's score to node weights or resource priority * * Given a colocation constraint, apply its score to the dependent's * allowed node weights (if we are still placing resources) or priority (if * we are choosing promotable clone instance roles). * * \param[in,out] dependent Dependent resource in colocation * \param[in] primary Primary resource in colocation * \param[in] colocation Colocation constraint to apply * \param[in] for_dependent true if called on behalf of dependent */ void pcmk__clone_apply_coloc_score(pe_resource_t *dependent, const pe_resource_t *primary, const pcmk__colocation_t *colocation, bool for_dependent) { GList *iter = NULL; /* This should never be called for the clone itself as a dependent. Instead, * we add its colocation constraints to its instances and call the * apply_coloc_score() method for the instances as dependents. */ CRM_ASSERT(!for_dependent); CRM_ASSERT((colocation != NULL) && pe_rsc_is_clone(primary) && (dependent != NULL) && (dependent->variant == pe_native)); if (pcmk_is_set(primary->flags, pe_rsc_provisional)) { pe_rsc_trace(primary, "Delaying processing colocation %s " "because cloned primary %s is still provisional", colocation->id, primary->id); return; } pe_rsc_trace(primary, "Processing colocation %s (%s with clone %s @%s)", colocation->id, dependent->id, primary->id, pcmk_readable_score(colocation->score)); // Apply role-specific colocations if (pcmk_is_set(primary->flags, pe_rsc_promotable) && (colocation->primary_role != RSC_ROLE_UNKNOWN)) { if (pcmk_is_set(dependent->flags, pe_rsc_provisional)) { // We're assigning the dependent to a node pcmk__update_dependent_with_promotable(primary, dependent, colocation); } else if (colocation->dependent_role == RSC_ROLE_PROMOTED) { // We're choosing a role for the dependent pcmk__update_promotable_dependent_priority(primary, dependent, colocation); } return; } // Apply interleaved colocations if (can_interleave(colocation)) { pe_resource_t *primary_instance = NULL; primary_instance = pcmk__find_compatible_instance(dependent, primary, RSC_ROLE_UNKNOWN, false); if (primary_instance != NULL) { pe_rsc_debug(primary, "Interleaving %s with %s", dependent->id, primary_instance->id); dependent->cmds->apply_coloc_score(dependent, primary_instance, colocation, true); } else if (colocation->score >= INFINITY) { crm_notice("%s cannot run because it cannot interleave with " "any instance of %s", dependent->id, primary->id); pcmk__assign_resource(dependent, NULL, true); } else { pe_rsc_debug(primary, "%s will not colocate with %s " "because no instance can interleave with it", dependent->id, primary->id); } return; } // Apply mandatory colocations if (colocation->score >= INFINITY) { GList *primary_nodes = NULL; // Dependent can run only where primary will have unblocked instances for (iter = primary->children; iter != NULL; iter = iter->next) { const pe_resource_t *instance = iter->data; pe_node_t *chosen = instance->fns->location(instance, NULL, 0); if ((chosen != NULL) && !is_set_recursive(instance, pe_rsc_block, TRUE)) { pe_rsc_trace(primary, "Allowing %s: %s %d", colocation->id, pe__node_name(chosen), chosen->weight); primary_nodes = g_list_prepend(primary_nodes, chosen); } } node_list_exclude(dependent->allowed_nodes, primary_nodes, FALSE); g_list_free(primary_nodes); return; } // Apply optional colocations for (iter = primary->children; iter != NULL; iter = iter->next) { pe_resource_t *instance = (pe_resource_t *) iter->data; instance->cmds->apply_coloc_score(dependent, instance, colocation, false); } } // Clone implementation of resource_alloc_functions_t:with_this_colocations() void pcmk__with_clone_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list) { CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return); if (rsc == orig_rsc) { // Colocations are wanted for clone itself pcmk__add_with_this_list(list, rsc->rsc_cons_lhs); } else { pcmk__add_collective_constraints(list, orig_rsc, rsc, true); } } // Clone implementation of resource_alloc_functions_t:this_with_colocations() void pcmk__clone_with_colocations(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList **list) { CRM_CHECK((rsc != NULL) && (orig_rsc != NULL) && (list != NULL), return); if (rsc == orig_rsc) { // Colocations are wanted for clone itself pcmk__add_this_with_list(list, rsc->rsc_cons); } else { pcmk__add_collective_constraints(list, orig_rsc, rsc, false); } } /*! * \internal * \brief Return action flags for a given clone resource action * * \param[in,out] action Action to get flags for * \param[in] node If not NULL, limit effects to this node * * \return Flags appropriate to \p action on \p node */ enum pe_action_flags pcmk__clone_action_flags(pe_action_t *action, const pe_node_t *node) { CRM_ASSERT((action != NULL) && pe_rsc_is_clone(action->rsc)); return pcmk__collective_action_flags(action, action->rsc->children, node); } /*! * \internal * \brief Apply a location constraint to a clone resource's allowed node scores * * \param[in,out] rsc Clone resource to apply constraint to * \param[in,out] location Location constraint to apply */ void pcmk__clone_apply_location(pe_resource_t *rsc, pe__location_t *constraint) { CRM_CHECK((constraint != NULL) && pe_rsc_is_clone(rsc), return); pcmk__apply_location(rsc, constraint); for (GList *iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *instance = (pe_resource_t *) iter->data; instance->cmds->apply_location(instance, constraint); } } /*! * \internal * \brief Add a clone resource's actions to the transition graph * * \param[in,out] rsc Resource whose actions should be added */ void pcmk__clone_add_actions_to_graph(pe_resource_t *rsc) { CRM_CHECK(pe_rsc_is_clone(rsc), return); g_list_foreach(rsc->actions, (GFunc) rsc->cmds->action_flags, NULL); pe__create_clone_notifications(rsc); for (GList *iter = rsc->children; iter != NULL; iter = iter->next) { pe_resource_t *child_rsc = (pe_resource_t *) iter->data; child_rsc->cmds->add_actions_to_graph(child_rsc); } pcmk__add_rsc_actions_to_graph(rsc); pe__free_clone_notification_data(rsc); } /*! * \internal * \brief Check whether a resource or any children have been probed on a node * * \param[in] rsc Resource to check * \param[in] node Node to check * * \return true if \p node is in the known_on table of \p rsc or any of its * children, otherwise false */ static bool rsc_probed_on(const pe_resource_t *rsc, const pe_node_t *node) { if (rsc->children != NULL) { for (GList *child_iter = rsc->children; child_iter != NULL; child_iter = child_iter->next) { pe_resource_t *child = (pe_resource_t *) child_iter->data; if (rsc_probed_on(child, node)) { return true; } } return false; } if (rsc->known_on != NULL) { GHashTableIter iter; pe_node_t *known_node = NULL; g_hash_table_iter_init(&iter, rsc->known_on); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &known_node)) { if (pe__same_node(node, known_node)) { return true; } } } return false; } /*! * \internal * \brief Find clone instance that has been probed on given node * * \param[in] clone Clone resource to check * \param[in] node Node to check * * \return Instance of \p clone that has been probed on \p node if any, * otherwise NULL */ static pe_resource_t * find_probed_instance_on(const pe_resource_t *clone, const pe_node_t *node) { for (GList *iter = clone->children; iter != NULL; iter = iter->next) { pe_resource_t *instance = (pe_resource_t *) iter->data; if (rsc_probed_on(instance, node)) { return instance; } } return NULL; } /*! * \internal * \brief Probe an anonymous clone on a node * * \param[in] clone Anonymous clone to probe * \param[in] node Node to probe \p clone on */ static bool probe_anonymous_clone(pe_resource_t *clone, pe_node_t *node) { // Check whether we already probed an instance on this node pe_resource_t *child = find_probed_instance_on(clone, node); // Otherwise, check if we plan to start an instance on this node for (GList *iter = clone->children; (iter != NULL) && (child == NULL); iter = iter->next) { pe_resource_t *instance = (pe_resource_t *) iter->data; const pe_node_t *instance_node = NULL; instance_node = instance->fns->location(instance, NULL, 0); if (pe__same_node(instance_node, node)) { child = instance; } } // Otherwise, use the first clone instance if (child == NULL) { child = clone->children->data; } // Anonymous clones only need to probe a single instance return child->cmds->create_probe(child, node); } /*! * \internal * \brief Schedule any probes needed for a resource on a node * * \param[in,out] rsc Resource to create probe for * \param[in,out] node Node to create probe on * * \return true if any probe was created, otherwise false */ bool pcmk__clone_create_probe(pe_resource_t *rsc, pe_node_t *node) { CRM_CHECK((node != NULL) && pe_rsc_is_clone(rsc), return false); if (rsc->exclusive_discover) { /* The clone is configured to be probed only where a location constraint * exists with resource-discovery set to exclusive. * * This check is not strictly necessary here since the instance's * create_probe() method would also check, but doing it here is more * efficient (especially for unique clones with a large number of * instances), and affects the CRM_meta_notify_available_uname variable * passed with notify actions. */ pe_node_t *allowed = g_hash_table_lookup(rsc->allowed_nodes, node->details->id); if ((allowed == NULL) || (allowed->rsc_discover_mode != pe_discover_exclusive)) { /* This node is not marked for resource discovery. Remove it from * allowed_nodes so that notifications contain only nodes that the * clone can possibly run on. */ pe_rsc_trace(rsc, "Skipping probe for %s on %s because resource has " "exclusive discovery but is not allowed on node", rsc->id, pe__node_name(node)); g_hash_table_remove(rsc->allowed_nodes, node->details->id); return false; } } rsc->children = g_list_sort(rsc->children, pcmk__cmp_instance_number); if (pcmk_is_set(rsc->flags, pe_rsc_unique)) { return pcmk__probe_resource_list(rsc->children, node); } else { return probe_anonymous_clone(rsc, node); } } /*! * \internal * \brief Add meta-attributes relevant to transition graph actions to XML * * Add clone-specific meta-attributes needed for transition graph actions. * * \param[in] rsc Clone resource whose meta-attributes should be added * \param[in,out] xml Transition graph action attributes XML to add to */ void pcmk__clone_add_graph_meta(const pe_resource_t *rsc, xmlNode *xml) { char *name = NULL; name = crm_meta_name(XML_RSC_ATTR_UNIQUE); crm_xml_add(xml, name, pe__rsc_bool_str(rsc, pe_rsc_unique)); free(name); name = crm_meta_name(XML_RSC_ATTR_NOTIFY); crm_xml_add(xml, name, pe__rsc_bool_str(rsc, pe_rsc_notify)); free(name); name = crm_meta_name(XML_RSC_ATTR_INCARNATION_MAX); crm_xml_add_int(xml, name, pe__clone_max(rsc)); free(name); name = crm_meta_name(XML_RSC_ATTR_INCARNATION_NODEMAX); crm_xml_add_int(xml, name, pe__clone_node_max(rsc)); free(name); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { int promoted_max = pe__clone_promoted_max(rsc); int promoted_node_max = pe__clone_promoted_node_max(rsc); name = crm_meta_name(XML_RSC_ATTR_PROMOTED_MAX); crm_xml_add_int(xml, name, promoted_max); free(name); name = crm_meta_name(XML_RSC_ATTR_PROMOTED_NODEMAX); crm_xml_add_int(xml, name, promoted_node_max); free(name); /* @COMPAT Maintain backward compatibility with resource agents that * expect the old names (deprecated since 2.0.0). */ name = crm_meta_name(PCMK_XA_PROMOTED_MAX_LEGACY); crm_xml_add_int(xml, name, promoted_max); free(name); name = crm_meta_name(PCMK_XA_PROMOTED_NODE_MAX_LEGACY); crm_xml_add_int(xml, name, promoted_node_max); free(name); } } // Clone implementation of resource_alloc_functions_t:add_utilization() void pcmk__clone_add_utilization(const pe_resource_t *rsc, const pe_resource_t *orig_rsc, GList *all_rscs, GHashTable *utilization) { bool existing = false; pe_resource_t *child = NULL; if (!pcmk_is_set(rsc->flags, pe_rsc_provisional)) { return; } // Look for any child already existing in the list for (GList *iter = rsc->children; iter != NULL; iter = iter->next) { child = (pe_resource_t *) iter->data; if (g_list_find(all_rscs, child)) { existing = true; // Keep checking remaining children } else { // If this is a clone of a group, look for group's members for (GList *member_iter = child->children; member_iter != NULL; member_iter = member_iter->next) { pe_resource_t *member = (pe_resource_t *) member_iter->data; if (g_list_find(all_rscs, member) != NULL) { // Add *child's* utilization, not group member's child->cmds->add_utilization(child, orig_rsc, all_rscs, utilization); existing = true; break; } } } } if (!existing && (rsc->children != NULL)) { // If nothing was found, still add first child's utilization child = (pe_resource_t *) rsc->children->data; child->cmds->add_utilization(child, orig_rsc, all_rscs, utilization); } } // Clone implementation of resource_alloc_functions_t:shutdown_lock() void pcmk__clone_shutdown_lock(pe_resource_t *rsc) { return; // Clones currently don't support shutdown locks } diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index ff1b365c1b..021c9f24a7 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1,2004 +1,2004 @@ /* * Copyright 2004-2023 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 #define PE__VARIANT_BUNDLE 1 #include "./variant.h" /*! * \internal * \brief Get maximum number of bundle replicas allowed to run * * \param[in] rsc Bundle or bundled resource to check * * \return Maximum replicas for bundle corresponding to \p rsc */ int pe__bundle_max(const pe_resource_t *rsc) { const pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); return bundle_data->nreplicas; } -/*! - * \internal - * \brief Get maximum number of bundle replicas allowed to run on one node - * - * \param[in] rsc Bundle or bundled resource to check - * - * \return Maximum replicas per node for bundle corresponding to \p rsc - */ -int -pe__bundle_max_per_node(const pe_resource_t *rsc) -{ - const pe__bundle_variant_data_t *bundle_data = NULL; - - get_bundle_variant_data(bundle_data, pe__const_top_resource(rsc, true)); - return bundle_data->nreplicas_per_host; -} - static char * next_ip(const char *last_ip) { unsigned int oct1 = 0; unsigned int oct2 = 0; unsigned int oct3 = 0; unsigned int oct4 = 0; int rc = sscanf(last_ip, "%u.%u.%u.%u", &oct1, &oct2, &oct3, &oct4); if (rc != 4) { /*@ TODO check for IPv6 */ return NULL; } else if (oct3 > 253) { return NULL; } else if (oct4 > 253) { ++oct3; oct4 = 1; } else { ++oct4; } return crm_strdup_printf("%u.%u.%u.%u", oct1, oct2, oct3, oct4); } static void allocate_ip(pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, GString *buffer) { if(data->ip_range_start == NULL) { return; } else if(data->ip_last) { replica->ipaddr = next_ip(data->ip_last); } else { replica->ipaddr = strdup(data->ip_range_start); } data->ip_last = replica->ipaddr; switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: if (data->add_host) { g_string_append_printf(buffer, " --add-host=%s-%d:%s", data->prefix, replica->offset, replica->ipaddr); } else { g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); } break; case PE__CONTAINER_AGENT_RKT: g_string_append_printf(buffer, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); break; default: // PE__CONTAINER_AGENT_UNKNOWN break; } } static xmlNode * create_resource(const char *name, const char *provider, const char *kind) { xmlNode *rsc = create_xml_node(NULL, XML_CIB_TAG_RESOURCE); crm_xml_add(rsc, XML_ATTR_ID, name); crm_xml_add(rsc, XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF); crm_xml_add(rsc, XML_AGENT_ATTR_PROVIDER, provider); crm_xml_add(rsc, XML_ATTR_TYPE, kind); return rsc; } /*! * \internal * \brief Check whether cluster can manage resource inside container * * \param[in,out] data Container variant data * * \return TRUE if networking configuration is acceptable, FALSE otherwise * * \note The resource is manageable if an IP range or control port has been * specified. If a control port is used without an IP range, replicas per * host must be 1. */ static bool valid_network(pe__bundle_variant_data_t *data) { if(data->ip_range_start) { return TRUE; } if(data->control_port) { if(data->nreplicas_per_host > 1) { pe_err("Specifying the 'control-port' for %s requires 'replicas-per-host=1'", data->prefix); data->nreplicas_per_host = 1; // @TODO to be sure: pe__clear_resource_flags(rsc, pe_rsc_unique); } return TRUE; } return FALSE; } static int create_ip_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica) { if(data->ip_range_start) { char *id = NULL; xmlNode *xml_ip = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-ip-%s", data->prefix, replica->ipaddr); crm_xml_sanitize_id(id); xml_ip = create_resource(id, "heartbeat", "IPaddr2"); free(id); xml_obj = create_xml_node(xml_ip, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "ip", replica->ipaddr); if(data->host_network) { crm_create_nvpair_xml(xml_obj, NULL, "nic", data->host_network); } if(data->host_netmask) { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", data->host_netmask); } else { crm_create_nvpair_xml(xml_obj, NULL, "cidr_netmask", "32"); } xml_obj = create_xml_node(xml_ip, "operations"); crm_create_op_xml(xml_obj, ID(xml_ip), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (pe__unpack_resource(xml_ip, &replica->ip, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } parent->children = g_list_append(parent->children, replica->ip); } return pcmk_rc_ok; } static const char* container_agent_str(enum pe__container_agent t) { switch (t) { case PE__CONTAINER_AGENT_DOCKER: return PE__CONTAINER_AGENT_DOCKER_S; case PE__CONTAINER_AGENT_RKT: return PE__CONTAINER_AGENT_RKT_S; case PE__CONTAINER_AGENT_PODMAN: return PE__CONTAINER_AGENT_PODMAN_S; default: // PE__CONTAINER_AGENT_UNKNOWN break; } return PE__CONTAINER_AGENT_UNKNOWN_S; } static int create_container_resource(pe_resource_t *parent, const pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica) { char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; // Agent-specific const char *hostname_opt = NULL; const char *env_opt = NULL; const char *agent_str = NULL; int volid = 0; // rkt-only GString *buffer = NULL; GString *dbuffer = NULL; // Where syntax differences are drop-in replacements, set them now switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: hostname_opt = "-h "; env_opt = "-e "; break; case PE__CONTAINER_AGENT_RKT: hostname_opt = "--hostname="; env_opt = "--environment="; break; default: // PE__CONTAINER_AGENT_UNKNOWN return pcmk_rc_unpack_error; } agent_str = container_agent_str(data->agent_type); buffer = g_string_sized_new(4096); id = crm_strdup_printf("%s-%s-%d", data->prefix, agent_str, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", agent_str); free(id); xml_obj = create_xml_node(xml_container, XML_TAG_ATTR_SETS); crm_xml_set_id(xml_obj, "%s-attributes-%d", data->prefix, replica->offset); crm_create_nvpair_xml(xml_obj, NULL, "image", data->image); crm_create_nvpair_xml(xml_obj, NULL, "allow_pull", XML_BOOLEAN_TRUE); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", XML_BOOLEAN_FALSE); crm_create_nvpair_xml(xml_obj, NULL, "reuse", XML_BOOLEAN_FALSE); if (data->agent_type == PE__CONTAINER_AGENT_DOCKER) { g_string_append(buffer, " --restart=no"); } /* Set a container hostname only if we have an IP to map it to. The user can * set -h or --uts=host themselves if they want a nicer name for logs, but * this makes applications happy who need their hostname to match the IP * they bind to. */ if (data->ip_range_start != NULL) { g_string_append_printf(buffer, " %s%s-%d", hostname_opt, data->prefix, replica->offset); } pcmk__g_strcat(buffer, " ", env_opt, "PCMK_stderr=1", NULL); if (data->container_network != NULL) { pcmk__g_strcat(buffer, " --net=", data->container_network, NULL); } if (data->control_port != NULL) { pcmk__g_strcat(buffer, " ", env_opt, "PCMK_remote_port=", data->control_port, NULL); } else { g_string_append_printf(buffer, " %sPCMK_remote_port=%d", env_opt, DEFAULT_REMOTE_PORT); } for (GList *iter = data->mounts; iter != NULL; iter = iter->next) { pe__bundle_mount_t *mount = (pe__bundle_mount_t *) iter->data; char *source = NULL; if (pcmk_is_set(mount->flags, pe__bundle_mount_subdir)) { source = crm_strdup_printf("%s/%s-%d", mount->source, data->prefix, replica->offset); pcmk__add_separated_word(&dbuffer, 1024, source, ","); } switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: pcmk__g_strcat(buffer, " -v ", pcmk__s(source, mount->source), ":", mount->target, NULL); if (mount->options != NULL) { pcmk__g_strcat(buffer, ":", mount->options, NULL); } break; case PE__CONTAINER_AGENT_RKT: g_string_append_printf(buffer, " --volume vol%d,kind=host," "source=%s%s%s " "--mount volume=vol%d,target=%s", volid, pcmk__s(source, mount->source), (mount->options != NULL)? "," : "", pcmk__s(mount->options, ""), volid, mount->target); volid++; break; default: break; } free(source); } for (GList *iter = data->ports; iter != NULL; iter = iter->next) { pe__bundle_port_t *port = (pe__bundle_port_t *) iter->data; switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: case PE__CONTAINER_AGENT_PODMAN: if (replica->ipaddr != NULL) { pcmk__g_strcat(buffer, " -p ", replica->ipaddr, ":", port->source, ":", port->target, NULL); } else if (!pcmk__str_eq(data->container_network, "host", pcmk__str_none)) { // No need to do port mapping if net == host pcmk__g_strcat(buffer, " -p ", port->source, ":", port->target, NULL); } break; case PE__CONTAINER_AGENT_RKT: if (replica->ipaddr != NULL) { pcmk__g_strcat(buffer, " --port=", port->target, ":", replica->ipaddr, ":", port->source, NULL); } else { pcmk__g_strcat(buffer, " --port=", port->target, ":", port->source, NULL); } break; default: break; } } /* @COMPAT: We should use pcmk__add_word() here, but we can't yet, because * it would cause restarts during rolling upgrades. * * In a previous version of the container resource creation logic, if * data->launcher_options is not NULL, we append * (" %s", data->launcher_options) even if data->launcher_options is an * empty string. Likewise for data->container_host_options. Using * * pcmk__add_word(buffer, 0, data->launcher_options) * * removes that extra trailing space, causing a resource definition change. */ if (data->launcher_options != NULL) { pcmk__g_strcat(buffer, " ", data->launcher_options, NULL); } if (data->container_host_options != NULL) { pcmk__g_strcat(buffer, " ", data->container_host_options, NULL); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", (const char *) buffer->str); g_string_free(buffer, TRUE); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", (dbuffer != NULL)? (const char *) dbuffer->str : ""); if (dbuffer != NULL) { g_string_free(dbuffer, TRUE); } if (replica->child != NULL) { if (data->container_command != NULL) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } else { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", SBIN_DIR "/pacemaker-remoted"); } /* TODO: Allow users to specify their own? * * We just want to know if the container is alive; we'll monitor the * child independently. */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); #if 0 /* @TODO Consider supporting the use case where we can start and stop * resources, but not proxy local commands (such as setting node * attributes), by running the local executor in stand-alone mode. * However, this would probably be better done via ACLs as with other * Pacemaker Remote nodes. */ } else if ((child != NULL) && data->untrusted) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", CRM_DAEMON_DIR "/pacemaker-execd"); crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", CRM_DAEMON_DIR "/pacemaker/cts-exec-helper -c poke"); #endif } else { if (data->container_command != NULL) { crm_create_nvpair_xml(xml_obj, NULL, "run_cmd", data->container_command); } /* TODO: Allow users to specify their own? * * We don't know what's in the container, so we just want to know if it * is alive. */ crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", "/bin/true"); } xml_obj = create_xml_node(xml_container, "operations"); crm_create_op_xml(xml_obj, ID(xml_container), "monitor", "60s", NULL); // TODO: Other ops? Timeouts and intervals from underlying resource? if (pe__unpack_resource(xml_container, &replica->container, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } pe__set_resource_flags(replica->container, pe_rsc_replica_container); parent->children = g_list_append(parent->children, replica->container); return pcmk_rc_ok; } /*! * \brief Ban a node from a resource's (and its children's) allowed nodes list * * \param[in,out] rsc Resource to modify * \param[in] uname Name of node to ban */ static void disallow_node(pe_resource_t *rsc, const char *uname) { gpointer match = g_hash_table_lookup(rsc->allowed_nodes, uname); if (match) { ((pe_node_t *) match)->weight = -INFINITY; ((pe_node_t *) match)->rsc_discover_mode = pe_discover_never; } if (rsc->children) { g_list_foreach(rsc->children, (GFunc) disallow_node, (gpointer) uname); } } static int create_remote_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica) { if (replica->child && valid_network(data)) { GHashTableIter gIter; pe_node_t *node = NULL; xmlNode *xml_remote = NULL; char *id = crm_strdup_printf("%s-%d", data->prefix, replica->offset); char *port_s = NULL; const char *uname = NULL; const char *connect_name = NULL; if (pe_find_resource(parent->cluster->resources, id) != NULL) { free(id); // The biggest hammer we have id = crm_strdup_printf("pcmk-internal-%s-remote-%d", replica->child->id, replica->offset); //@TODO return error instead of asserting? CRM_ASSERT(pe_find_resource(parent->cluster->resources, id) == NULL); } /* REMOTE_CONTAINER_HACK: Using "#uname" as the server name when the * connection does not have its own IP is a magic string that we use to * support nested remotes (i.e. a bundle running on a remote node). */ connect_name = (replica->ipaddr? replica->ipaddr : "#uname"); if (data->control_port == NULL) { port_s = pcmk__itoa(DEFAULT_REMOTE_PORT); } /* This sets replica->container as replica->remote's container, which is * similar to what happens with guest nodes. This is how the scheduler * knows that the bundle node is fenced by recovering the container, and * that remote should be ordered relative to the container. */ xml_remote = pe_create_remote_xml(NULL, id, replica->container->id, NULL, NULL, NULL, connect_name, (data->control_port? data->control_port : port_s)); free(port_s); /* Abandon our created ID, and pull the copy from the XML, because we * need something that will get freed during data set cleanup to use as * the node ID and uname. */ free(id); id = NULL; uname = ID(xml_remote); /* Ensure a node has been created for the guest (it may have already * been, if it has a permanent node attribute), and ensure its weight is * -INFINITY so no other resources can run on it. */ node = pe_find_node(parent->cluster->nodes, uname); if (node == NULL) { node = pe_create_node(uname, uname, "remote", "-INFINITY", parent->cluster); } else { node->weight = -INFINITY; } node->rsc_discover_mode = pe_discover_never; /* unpack_remote_nodes() ensures that each remote node and guest node * has a pe_node_t entry. Ideally, it would do the same for bundle nodes. * Unfortunately, a bundle has to be mostly unpacked before it's obvious * what nodes will be needed, so we do it just above. * * Worse, that means that the node may have been utilized while * unpacking other resources, without our weight correction. The most * likely place for this to happen is when pe__unpack_resource() calls * resource_location() to set a default score in symmetric clusters. * This adds a node *copy* to each resource's allowed nodes, and these * copies will have the wrong weight. * * As a hacky workaround, fix those copies here. * * @TODO Possible alternative: ensure bundles are unpacked before other * resources, so the weight is correct before any copies are made. */ g_list_foreach(parent->cluster->resources, (GFunc) disallow_node, (gpointer) uname); replica->node = pe__copy_node(node); replica->node->weight = 500; replica->node->rsc_discover_mode = pe_discover_exclusive; /* Ensure the node shows up as allowed and with the correct discovery set */ if (replica->child->allowed_nodes != NULL) { g_hash_table_destroy(replica->child->allowed_nodes); } replica->child->allowed_nodes = pcmk__strkey_table(NULL, free); g_hash_table_insert(replica->child->allowed_nodes, (gpointer) replica->node->details->id, pe__copy_node(replica->node)); { pe_node_t *copy = pe__copy_node(replica->node); copy->weight = -INFINITY; g_hash_table_insert(replica->child->parent->allowed_nodes, (gpointer) replica->node->details->id, copy); } if (pe__unpack_resource(xml_remote, &replica->remote, parent, parent->cluster) != pcmk_rc_ok) { return pcmk_rc_unpack_error; } g_hash_table_iter_init(&gIter, replica->remote->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&node)) { if (pe__is_guest_or_remote_node(node)) { /* Remote resources can only run on 'normal' cluster node */ node->weight = -INFINITY; } } replica->node->details->remote_rsc = replica->remote; // Ensure pe__is_guest_node() functions correctly immediately replica->remote->container = replica->container; /* A bundle's #kind is closer to "container" (guest node) than the * "remote" set by pe_create_node(). */ g_hash_table_insert(replica->node->details->attrs, strdup(CRM_ATTR_KIND), strdup("container")); /* One effect of this is that setup_container() will add * replica->remote to replica->container's fillers, which will make * pe__resource_contains_guest_node() true for replica->container. * * replica->child does NOT get added to replica->container's fillers. * The only noticeable effect if it did would be for its fail count to * be taken into account when checking replica->container's migration * threshold. */ parent->children = g_list_append(parent->children, replica->remote); } return pcmk_rc_ok; } static int create_replica_resources(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica) { int rc = pcmk_rc_ok; rc = create_container_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } rc = create_ip_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } rc = create_remote_resource(parent, data, replica); if (rc != pcmk_rc_ok) { return rc; } if ((replica->child != NULL) && (replica->ipaddr != NULL)) { add_hash_param(replica->child->meta, "external-ip", replica->ipaddr); } if (replica->remote != NULL) { /* * Allow the remote connection resource to be allocated to a * different node than the one on which the container is active. * * This makes it possible to have Pacemaker Remote nodes running * containers with pacemaker-remoted inside in order to start * services inside those containers. */ pe__set_resource_flags(replica->remote, pe_rsc_allow_remote_remotes); } return rc; } static void mount_add(pe__bundle_variant_data_t *bundle_data, const char *source, const char *target, const char *options, uint32_t flags) { pe__bundle_mount_t *mount = calloc(1, sizeof(pe__bundle_mount_t)); CRM_ASSERT(mount != NULL); mount->source = strdup(source); mount->target = strdup(target); pcmk__str_update(&mount->options, options); mount->flags = flags; bundle_data->mounts = g_list_append(bundle_data->mounts, mount); } static void mount_free(pe__bundle_mount_t *mount) { free(mount->source); free(mount->target); free(mount->options); free(mount); } static void port_free(pe__bundle_port_t *port) { free(port->source); free(port->target); free(port); } static pe__bundle_replica_t * replica_for_remote(pe_resource_t *remote) { pe_resource_t *top = remote; pe__bundle_variant_data_t *bundle_data = NULL; if (top == NULL) { return NULL; } while (top->parent != NULL) { top = top->parent; } get_bundle_variant_data(bundle_data, top); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (replica->remote == remote) { return replica; } } CRM_LOG_ASSERT(FALSE); return NULL; } bool pe__bundle_needs_remote_name(pe_resource_t *rsc) { const char *value; GHashTable *params = NULL; if (rsc == NULL) { return false; } // Use NULL node since pcmk__bundle_expand() uses that to set value params = pe_rsc_params(rsc, NULL, rsc->cluster); value = g_hash_table_lookup(params, XML_RSC_ATTR_REMOTE_RA_ADDR); return pcmk__str_eq(value, "#uname", pcmk__str_casei) && xml_contains_remote_node(rsc->xml); } const char * pe__add_bundle_remote_name(pe_resource_t *rsc, pe_working_set_t *data_set, xmlNode *xml, const char *field) { // REMOTE_CONTAINER_HACK: Allow remote nodes that start containers with pacemaker remote inside pe_node_t *node = NULL; pe__bundle_replica_t *replica = NULL; if (!pe__bundle_needs_remote_name(rsc)) { return NULL; } replica = replica_for_remote(rsc); if (replica == NULL) { return NULL; } node = replica->container->allocated_to; if (node == NULL) { /* If it won't be running anywhere after the * transition, go with where it's running now. */ node = pe__current_node(replica->container); } if(node == NULL) { crm_trace("Cannot determine address for bundle connection %s", rsc->id); return NULL; } crm_trace("Setting address for bundle connection %s to bundle host %s", rsc->id, pe__node_name(node)); if(xml != NULL && field != NULL) { crm_xml_add(xml, field, node->details->uname); } return node->details->uname; } #define pe__set_bundle_mount_flags(mount_xml, flags, flags_to_set) do { \ flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Bundle mount", ID(mount_xml), flags, \ (flags_to_set), #flags_to_set); \ } while (0) gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set) { const char *value = NULL; xmlNode *xml_obj = NULL; xmlNode *xml_resource = NULL; pe__bundle_variant_data_t *bundle_data = NULL; bool need_log_mount = TRUE; CRM_ASSERT(rsc != NULL); pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); bundle_data = calloc(1, sizeof(pe__bundle_variant_data_t)); rsc->variant_opaque = bundle_data; bundle_data->prefix = strdup(rsc->id); xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_DOCKER_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_DOCKER; } else { xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_RKT_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_RKT; } else { xml_obj = first_named_child(rsc->xml, PE__CONTAINER_AGENT_PODMAN_S); if (xml_obj != NULL) { bundle_data->agent_type = PE__CONTAINER_AGENT_PODMAN; } else { return FALSE; } } } // Use 0 for default, minimum, and invalid promoted-max value = crm_element_value(xml_obj, XML_RSC_ATTR_PROMOTED_MAX); if (value == NULL) { // @COMPAT deprecated since 2.0.0 value = crm_element_value(xml_obj, "masters"); } pcmk__scan_min_int(value, &bundle_data->promoted_max, 0); // Default replicas to promoted-max if it was specified and 1 otherwise value = crm_element_value(xml_obj, "replicas"); if ((value == NULL) && (bundle_data->promoted_max > 0)) { bundle_data->nreplicas = bundle_data->promoted_max; } else { pcmk__scan_min_int(value, &bundle_data->nreplicas, 1); } /* * Communication between containers on the same host via the * floating IPs only works if the container is started with: * --userland-proxy=false --ip-masq=false */ value = crm_element_value(xml_obj, "replicas-per-host"); pcmk__scan_min_int(value, &bundle_data->nreplicas_per_host, 1); if (bundle_data->nreplicas_per_host == 1) { pe__clear_resource_flags(rsc, pe_rsc_unique); } bundle_data->container_command = crm_element_value_copy(xml_obj, "run-command"); bundle_data->launcher_options = crm_element_value_copy(xml_obj, "options"); bundle_data->image = crm_element_value_copy(xml_obj, "image"); bundle_data->container_network = crm_element_value_copy(xml_obj, "network"); xml_obj = first_named_child(rsc->xml, "network"); if(xml_obj) { bundle_data->ip_range_start = crm_element_value_copy(xml_obj, "ip-range-start"); bundle_data->host_netmask = crm_element_value_copy(xml_obj, "host-netmask"); bundle_data->host_network = crm_element_value_copy(xml_obj, "host-interface"); bundle_data->control_port = crm_element_value_copy(xml_obj, "control-port"); value = crm_element_value(xml_obj, "add-host"); if (crm_str_to_boolean(value, &bundle_data->add_host) != 1) { bundle_data->add_host = TRUE; } for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL; xml_child = pcmk__xe_next(xml_child)) { pe__bundle_port_t *port = calloc(1, sizeof(pe__bundle_port_t)); port->source = crm_element_value_copy(xml_child, "port"); if(port->source == NULL) { port->source = crm_element_value_copy(xml_child, "range"); } else { port->target = crm_element_value_copy(xml_child, "internal-port"); } if(port->source != NULL && strlen(port->source) > 0) { if(port->target == NULL) { port->target = strdup(port->source); } bundle_data->ports = g_list_append(bundle_data->ports, port); } else { pe_err("Invalid port directive %s", ID(xml_child)); port_free(port); } } } xml_obj = first_named_child(rsc->xml, "storage"); for (xmlNode *xml_child = pcmk__xe_first_child(xml_obj); xml_child != NULL; xml_child = pcmk__xe_next(xml_child)) { const char *source = crm_element_value(xml_child, "source-dir"); const char *target = crm_element_value(xml_child, "target-dir"); const char *options = crm_element_value(xml_child, "options"); int flags = pe__bundle_mount_none; if (source == NULL) { source = crm_element_value(xml_child, "source-dir-root"); pe__set_bundle_mount_flags(xml_child, flags, pe__bundle_mount_subdir); } if (source && target) { mount_add(bundle_data, source, target, options, flags); if (strcmp(target, "/var/log") == 0) { need_log_mount = FALSE; } } else { pe_err("Invalid mount directive %s", ID(xml_child)); } } xml_obj = first_named_child(rsc->xml, "primitive"); if (xml_obj && valid_network(bundle_data)) { char *value = NULL; xmlNode *xml_set = NULL; xml_resource = create_xml_node(NULL, XML_CIB_TAG_INCARNATION); /* @COMPAT We no longer use the tag, but we need to keep it as * part of the resource name, so that bundles don't restart in a rolling * upgrade. (It also avoids needing to change regression tests.) */ crm_xml_set_id(xml_resource, "%s-%s", bundle_data->prefix, (bundle_data->promoted_max? "master" : (const char *)xml_resource->name)); xml_set = create_xml_node(xml_resource, XML_TAG_META_SETS); crm_xml_set_id(xml_set, "%s-%s-meta", bundle_data->prefix, xml_resource->name); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_ORDERED, XML_BOOLEAN_TRUE); value = pcmk__itoa(bundle_data->nreplicas); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_INCARNATION_MAX, value); free(value); value = pcmk__itoa(bundle_data->nreplicas_per_host); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_INCARNATION_NODEMAX, value); free(value); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_UNIQUE, pcmk__btoa(bundle_data->nreplicas_per_host > 1)); if (bundle_data->promoted_max) { crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_PROMOTABLE, XML_BOOLEAN_TRUE); value = pcmk__itoa(bundle_data->promoted_max); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_PROMOTED_MAX, value); free(value); } //crm_xml_add(xml_obj, XML_ATTR_ID, bundle_data->prefix); add_node_copy(xml_resource, xml_obj); } else if(xml_obj) { pe_err("Cannot control %s inside %s without either ip-range-start or control-port", rsc->id, ID(xml_obj)); return FALSE; } if(xml_resource) { int lpc = 0; GList *childIter = NULL; pe__bundle_port_t *port = NULL; GString *buffer = NULL; if (pe__unpack_resource(xml_resource, &(bundle_data->child), rsc, data_set) != pcmk_rc_ok) { return FALSE; } /* Currently, we always map the default authentication key location * into the same location inside the container. * * Ideally, we would respect the host's PCMK_authkey_location, but: * - it may be different on different nodes; * - the actual connection will do extra checking to make sure the key * file exists and is readable, that we can't do here on the DC * - tools such as crm_resource and crm_simulate may not have the same * environment variables as the cluster, causing operation digests to * differ * * Always using the default location inside the container is fine, * because we control the pacemaker_remote environment, and it avoids * having to pass another environment variable to the container. * * @TODO A better solution may be to have only pacemaker_remote use the * environment variable, and have the cluster nodes use a new * cluster option for key location. This would introduce the limitation * of the location being the same on all cluster nodes, but that's * reasonable. */ mount_add(bundle_data, DEFAULT_REMOTE_KEY_LOCATION, DEFAULT_REMOTE_KEY_LOCATION, NULL, pe__bundle_mount_none); if (need_log_mount) { mount_add(bundle_data, CRM_BUNDLE_DIR, "/var/log", NULL, pe__bundle_mount_subdir); } port = calloc(1, sizeof(pe__bundle_port_t)); if(bundle_data->control_port) { port->source = strdup(bundle_data->control_port); } else { /* If we wanted to respect PCMK_remote_port, we could use * crm_default_remote_port() here and elsewhere in this file instead * of DEFAULT_REMOTE_PORT. * * However, it gains nothing, since we control both the container * environment and the connection resource parameters, and the user * can use a different port if desired by setting control-port. */ port->source = pcmk__itoa(DEFAULT_REMOTE_PORT); } port->target = strdup(port->source); bundle_data->ports = g_list_append(bundle_data->ports, port); buffer = g_string_sized_new(1024); for (childIter = bundle_data->child->children; childIter != NULL; childIter = childIter->next) { pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); replica->child = childIter->data; replica->child->exclusive_discover = TRUE; replica->offset = lpc++; // Ensure the child's notify gets set based on the underlying primitive's value if (pcmk_is_set(replica->child->flags, pe_rsc_notify)) { pe__set_resource_flags(bundle_data->child, pe_rsc_notify); } allocate_ip(bundle_data, replica, buffer); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); bundle_data->attribute_target = g_hash_table_lookup(replica->child->meta, XML_RSC_ATTR_TARGET); } bundle_data->container_host_options = g_string_free(buffer, FALSE); if (bundle_data->attribute_target) { g_hash_table_replace(rsc->meta, strdup(XML_RSC_ATTR_TARGET), strdup(bundle_data->attribute_target)); g_hash_table_replace(bundle_data->child->meta, strdup(XML_RSC_ATTR_TARGET), strdup(bundle_data->attribute_target)); } } else { // Just a naked container, no pacemaker-remote GString *buffer = g_string_sized_new(1024); for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) { pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); replica->offset = lpc; allocate_ip(bundle_data, replica, buffer); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); } bundle_data->container_host_options = g_string_free(buffer, FALSE); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (create_replica_resources(rsc, bundle_data, replica) != pcmk_rc_ok) { pe_err("Failed unpacking resource %s", rsc->id); rsc->fns->free(rsc); return FALSE; } /* Utilization needs special handling for bundles. It makes no sense for * the inner primitive to have utilization, because it is tied * one-to-one to the guest node created by the container resource -- and * there's no way to set capacities for that guest node anyway. * * What the user really wants is to configure utilization for the * container. However, the schema only allows utilization for * primitives, and the container resource is implicit anyway, so the * user can *only* configure utilization for the inner primitive. If * they do, move the primitive's utilization values to the container. * * @TODO This means that bundles without an inner primitive can't have * utilization. An alternative might be to allow utilization values in * the top-level bundle XML in the schema, and copy those to each * container. */ if (replica->child != NULL) { GHashTable *empty = replica->container->utilization; replica->container->utilization = replica->child->utilization; replica->child->utilization = empty; } } if (bundle_data->child) { rsc->children = g_list_append(rsc->children, bundle_data->child); } return TRUE; } static int replica_resource_active(pe_resource_t *rsc, gboolean all) { if (rsc) { gboolean child_active = rsc->fns->active(rsc, all); if (child_active && !all) { return TRUE; } else if (!child_active && all) { return FALSE; } } return -1; } gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all) { pe__bundle_variant_data_t *bundle_data = NULL; GList *iter = NULL; get_bundle_variant_data(bundle_data, rsc); for (iter = bundle_data->replicas; iter != NULL; iter = iter->next) { pe__bundle_replica_t *replica = iter->data; int rsc_active; rsc_active = replica_resource_active(replica->ip, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->child, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->container, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } rsc_active = replica_resource_active(replica->remote, all); if (rsc_active >= 0) { return (gboolean) rsc_active; } } /* If "all" is TRUE, we've already checked that no resources were inactive, * so return TRUE; if "all" is FALSE, we didn't find any active resources, * so return FALSE. */ return all; } /*! * \internal * \brief Find the bundle replica corresponding to a given node * * \param[in] bundle Top-level bundle resource * \param[in] node Node to search for * * \return Bundle replica if found, NULL otherwise */ pe_resource_t * pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_ASSERT(bundle && node); get_bundle_variant_data(bundle_data, bundle); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica && replica->node); if (replica->node->details == node->details) { return replica->child; } } return NULL; } /*! * \internal * \deprecated This function will be removed in a future release */ static void print_rsc_in_list(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { if (rsc != NULL) { if (options & pe_print_html) { status_print("
  • "); } rsc->fns->print(rsc, pre_text, options, print_data); if (options & pe_print_html) { status_print("
  • \n"); } } } /*! * \internal * \deprecated This function will be removed in a future release */ static void bundle_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (pre_text == NULL) { pre_text = ""; } child_text = crm_strdup_printf("%s ", pre_text); get_bundle_variant_data(bundle_data, rsc); status_print("%sid); status_print("type=\"%s\" ", container_agent_str(bundle_data->agent_type)); status_print("image=\"%s\" ", bundle_data->image); status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique)); status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed)); status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed)); status_print(">\n"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); status_print("%s \n", pre_text, replica->offset); print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); status_print("%s \n", pre_text); } status_print("%s\n", pre_text); free(child_text); } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__bundle_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean printed_header = FALSE; gboolean print_everything = TRUE; const char *desc = NULL; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; char *id = NULL; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (!print_everything && !print_ip && !print_child && !print_ctnr && !print_remote) { continue; } if (!printed_header) { printed_header = TRUE; desc = pe__resource_description(rsc, show_opts); rc = pe__name_and_nvpairs_xml(out, true, "bundle", 8, "id", rsc->id, "type", container_agent_str(bundle_data->agent_type), "image", bundle_data->image, "unique", pe__rsc_bool_str(rsc, pe_rsc_unique), "maintenance", pe__rsc_bool_str(rsc, pe_rsc_maintenance), "managed", pe__rsc_bool_str(rsc, pe_rsc_managed), "failed", pe__rsc_bool_str(rsc, pe_rsc_failed), "description", desc); CRM_ASSERT(rc == pcmk_rc_ok); } id = pcmk__itoa(replica->offset); rc = pe__name_and_nvpairs_xml(out, true, "replica", 1, "id", id); free(id); CRM_ASSERT(rc == pcmk_rc_ok); if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), show_opts, replica->remote, only_node, only_rsc); } pcmk__output_xml_pop_parent(out); // replica } if (printed_header) { pcmk__output_xml_pop_parent(out); // bundle } return rc; } static void pe__bundle_replica_output_html(pcmk__output_t *out, pe__bundle_replica_t *replica, pe_node_t *node, uint32_t show_opts) { pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } pe__common_output_html(out, rsc, buffer, node, show_opts); } /*! * \internal * \brief Get a string describing a resource's unmanaged state or lack thereof * * \param[in] rsc Resource to describe * * \return A string indicating that a resource is in maintenance mode or * otherwise unmanaged, or an empty string otherwise */ static const char * get_unmanaged_str(const pe_resource_t *rsc) { if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { return " (maintenance)"; } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { return " (unmanaged)"; } return ""; } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__bundle_html(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); desc = pe__resource_description(rsc, show_opts); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { /* The text output messages used below require pe_print_implicit to * be set to do anything. */ uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); if (pcmk__list_of_multiple(bundle_data->replicas)) { out->begin_list(out, NULL, NULL, "Replica[%d]", replica->offset); } if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), new_show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), new_show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), new_show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), new_show_opts, replica->remote, only_node, only_rsc); } if (pcmk__list_of_multiple(bundle_data->replicas)) { out->end_list(out); } } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { continue; } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); pe__bundle_replica_output_html(out, replica, pe__current_node(replica->container), show_opts); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } static void pe__bundle_replica_output_text(pcmk__output_t *out, pe__bundle_replica_t *replica, pe_node_t *node, uint32_t show_opts) { const pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } pe__common_output_text(out, rsc, buffer, node, show_opts); } PCMK__OUTPUT_ARGS("bundle", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__bundle_text(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; pe__bundle_variant_data_t *bundle_data = NULL; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; desc = pe__resource_description(rsc, show_opts); get_bundle_variant_data(bundle_data, rsc); CRM_ASSERT(rsc != NULL); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; gboolean print_ip, print_child, print_ctnr, print_remote; CRM_ASSERT(replica); if (pcmk__rsc_filtered_by_node(replica->container, only_node)) { continue; } print_ip = replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, print_everything); print_child = replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, print_everything); print_ctnr = !replica->container->fns->is_filtered(replica->container, only_rsc, print_everything); print_remote = replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, print_everything); if (pcmk_is_set(show_opts, pcmk_show_implicit_rscs) || (print_everything == FALSE && (print_ip || print_child || print_ctnr || print_remote))) { /* The text output messages used below require pe_print_implicit to * be set to do anything. */ uint32_t new_show_opts = show_opts | pcmk_show_implicit_rscs; PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); if (pcmk__list_of_multiple(bundle_data->replicas)) { out->list_item(out, NULL, "Replica[%d]", replica->offset); } out->begin_list(out, NULL, NULL, NULL); if (print_ip) { out->message(out, crm_map_element_name(replica->ip->xml), new_show_opts, replica->ip, only_node, only_rsc); } if (print_child) { out->message(out, crm_map_element_name(replica->child->xml), new_show_opts, replica->child, only_node, only_rsc); } if (print_ctnr) { out->message(out, crm_map_element_name(replica->container->xml), new_show_opts, replica->container, only_node, only_rsc); } if (print_remote) { out->message(out, crm_map_element_name(replica->remote->xml), new_show_opts, replica->remote, only_node, only_rsc); } out->end_list(out); } else if (print_everything == FALSE && !(print_ip || print_child || print_ctnr || print_remote)) { continue; } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, rc, "Container bundle%s: %s [%s]%s%s%s%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", desc ? " (" : "", desc ? desc : "", desc ? ")" : "", get_unmanaged_str(rsc)); pe__bundle_replica_output_text(out, replica, pe__current_node(replica->container), show_opts); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } /*! * \internal * \deprecated This function will be removed in a future release */ static void print_bundle_replica(pe__bundle_replica_t *replica, const char *pre_text, long options, void *print_data) { pe_node_t *node = NULL; pe_resource_t *rsc = replica->child; int offset = 0; char buffer[LINE_MAX]; if(rsc == NULL) { rsc = replica->container; } if (replica->remote) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->remote)); } else { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_printable_id(replica->container)); } if (replica->ipaddr) { offset += snprintf(buffer + offset, LINE_MAX - offset, " (%s)", replica->ipaddr); } node = pe__current_node(replica->container); common_print(rsc, pre_text, buffer, node, options, print_data); } /*! * \internal * \deprecated This function will be removed in a future release */ void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { pe__bundle_variant_data_t *bundle_data = NULL; char *child_text = NULL; CRM_CHECK(rsc != NULL, return); if (options & pe_print_xml) { bundle_print_xml(rsc, pre_text, options, print_data); return; } get_bundle_variant_data(bundle_data, rsc); if (pre_text == NULL) { pre_text = " "; } status_print("%sContainer bundle%s: %s [%s]%s%s\n", pre_text, ((bundle_data->nreplicas > 1)? " set" : ""), rsc->id, bundle_data->image, pcmk_is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", pcmk_is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); if (options & pe_print_html) { status_print("
    \n
      \n"); } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (options & pe_print_html) { status_print("
    • "); } if (pcmk_is_set(options, pe_print_implicit)) { child_text = crm_strdup_printf(" %s", pre_text); if (pcmk__list_of_multiple(bundle_data->replicas)) { status_print(" %sReplica[%d]\n", pre_text, replica->offset); } if (options & pe_print_html) { status_print("
      \n
        \n"); } print_rsc_in_list(replica->ip, child_text, options, print_data); print_rsc_in_list(replica->container, child_text, options, print_data); print_rsc_in_list(replica->remote, child_text, options, print_data); print_rsc_in_list(replica->child, child_text, options, print_data); if (options & pe_print_html) { status_print("
      \n"); } } else { child_text = crm_strdup_printf("%s ", pre_text); print_bundle_replica(replica, child_text, options, print_data); } free(child_text); if (options & pe_print_html) { status_print("
    • \n"); } } if (options & pe_print_html) { status_print("
    \n"); } } static void free_bundle_replica(pe__bundle_replica_t *replica) { if (replica == NULL) { return; } if (replica->node) { free(replica->node); replica->node = NULL; } if (replica->ip) { free_xml(replica->ip->xml); replica->ip->xml = NULL; replica->ip->fns->free(replica->ip); replica->ip = NULL; } if (replica->container) { free_xml(replica->container->xml); replica->container->xml = NULL; replica->container->fns->free(replica->container); replica->container = NULL; } if (replica->remote) { free_xml(replica->remote->xml); replica->remote->xml = NULL; replica->remote->fns->free(replica->remote); replica->remote = NULL; } free(replica->ipaddr); free(replica); } void pe__free_bundle(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; CRM_CHECK(rsc != NULL, return); get_bundle_variant_data(bundle_data, rsc); pe_rsc_trace(rsc, "Freeing %s", rsc->id); free(bundle_data->prefix); free(bundle_data->image); free(bundle_data->control_port); free(bundle_data->host_network); free(bundle_data->host_netmask); free(bundle_data->ip_range_start); free(bundle_data->container_network); free(bundle_data->launcher_options); free(bundle_data->container_command); g_free(bundle_data->container_host_options); g_list_free_full(bundle_data->replicas, (GDestroyNotify) free_bundle_replica); g_list_free_full(bundle_data->mounts, (GDestroyNotify)mount_free); g_list_free_full(bundle_data->ports, (GDestroyNotify)port_free); g_list_free(rsc->children); if(bundle_data->child) { free_xml(bundle_data->child->xml); bundle_data->child->xml = NULL; bundle_data->child->fns->free(bundle_data->child); } common_free(rsc); } enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current) { enum rsc_role_e container_role = RSC_ROLE_UNKNOWN; return container_role; } /*! * \brief Get the number of configured replicas in a bundle * * \param[in] rsc Bundle resource * * \return Number of configured replicas, or 0 on error */ int pe_bundle_replicas(const pe_resource_t *rsc) { if ((rsc == NULL) || (rsc->variant != pe_container)) { return 0; } else { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); return bundle_data->nreplicas; } } void pe__count_bundle(pe_resource_t *rsc) { pe__bundle_variant_data_t *bundle_data = NULL; get_bundle_variant_data(bundle_data, rsc); for (GList *item = bundle_data->replicas; item != NULL; item = item->next) { pe__bundle_replica_t *replica = item->data; if (replica->ip) { replica->ip->fns->count(replica->ip); } if (replica->child) { replica->child->fns->count(replica->child); } if (replica->container) { replica->container->fns->count(replica->container); } if (replica->remote) { replica->remote->fns->count(replica->remote); } } } gboolean pe__bundle_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent) { gboolean passes = FALSE; pe__bundle_variant_data_t *bundle_data = NULL; if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else { get_bundle_variant_data(bundle_data, rsc); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (replica->ip != NULL && !replica->ip->fns->is_filtered(replica->ip, only_rsc, FALSE)) { passes = TRUE; break; } else if (replica->child != NULL && !replica->child->fns->is_filtered(replica->child, only_rsc, FALSE)) { passes = TRUE; break; } else if (!replica->container->fns->is_filtered(replica->container, only_rsc, FALSE)) { passes = TRUE; break; } else if (replica->remote != NULL && !replica->remote->fns->is_filtered(replica->remote, only_rsc, FALSE)) { passes = TRUE; break; } } } return !passes; } /*! * \internal * \brief Get a list of a bundle's containers * * \param[in] bundle Bundle resource * * \return Newly created list of \p bundle's containers * \note It is the caller's responsibility to free the result with * g_list_free(). */ GList * pe__bundle_containers(const pe_resource_t *bundle) { GList *containers = NULL; const pe__bundle_variant_data_t *data = NULL; get_bundle_variant_data(data, bundle); for (GList *iter = data->replicas; iter != NULL; iter = iter->next) { pe__bundle_replica_t *replica = iter->data; containers = g_list_append(containers, replica->container); } return containers; } // Bundle implementation of resource_object_functions_t:active_node() pe_node_t * pe__bundle_active_node(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean) { pe_node_t *active = NULL; pe_node_t *node = NULL; pe_resource_t *container = NULL; GList *containers = NULL; GList *iter = NULL; GHashTable *nodes = NULL; const pe__bundle_variant_data_t *data = NULL; if (count_all != NULL) { *count_all = 0; } if (count_clean != NULL) { *count_clean = 0; } if (rsc == NULL) { return NULL; } /* For the purposes of this method, we only care about where the bundle's * containers are active, so build a list of active containers. */ get_bundle_variant_data(data, rsc); for (iter = data->replicas; iter != NULL; iter = iter->next) { pe__bundle_replica_t *replica = iter->data; if (replica->container->running_on != NULL) { containers = g_list_append(containers, replica->container); } } if (containers == NULL) { return NULL; } /* If the bundle has only a single active container, just use that * container's method. If live migration is ever supported for bundle * containers, this will allow us to prefer the migration source when there * is only one container and it is migrating. For now, this just lets us * avoid creating the nodes table. */ if (pcmk__list_of_1(containers)) { container = containers->data; node = container->fns->active_node(container, count_all, count_clean); g_list_free(containers); return node; } // Add all containers' active nodes to a hash table (for uniqueness) nodes = g_hash_table_new(NULL, NULL); for (iter = containers; iter != NULL; iter = iter->next) { container = iter->data; for (GList *node_iter = container->running_on; node_iter != NULL; node_iter = node_iter->next) { node = node_iter->data; // If insert returns true, we haven't counted this node yet if (g_hash_table_insert(nodes, (gpointer) node->details, (gpointer) node) && !pe__count_active_node(rsc, node, &active, count_all, count_clean)) { goto done; } } } done: g_list_free(containers); g_hash_table_destroy(nodes); return active; } + +/*! + * \brief Get maximum bundle resource instances per node + * + * \param[in] rsc Bundle resource to check + * + * \return Maximum number of \p rsc instances that can be active on one node + */ +unsigned int +pe__bundle_max_per_node(const pe_resource_t *rsc) +{ + pe__bundle_variant_data_t *bundle_data = NULL; + + get_bundle_variant_data(bundle_data, rsc); + CRM_ASSERT(bundle_data->nreplicas_per_host >= 0); + return (unsigned int) bundle_data->nreplicas_per_host; +} diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index e411f98724..52bf71f2e9 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -1,1470 +1,1492 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #ifdef PCMK__COMPAT_2_0 #define PROMOTED_INSTANCES RSC_ROLE_PROMOTED_LEGACY_S "s" #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_LEGACY_S "s" #else #define PROMOTED_INSTANCES RSC_ROLE_PROMOTED_S #define UNPROMOTED_INSTANCES RSC_ROLE_UNPROMOTED_S #endif typedef struct clone_variant_data_s { int clone_max; int clone_node_max; int promoted_max; int promoted_node_max; int total_clones; uint32_t flags; // Group of enum pe__clone_flags notify_data_t *stop_notify; notify_data_t *start_notify; notify_data_t *demote_notify; notify_data_t *promote_notify; xmlNode *xml_obj_child; } clone_variant_data_t; #define get_clone_variant_data(data, rsc) \ CRM_ASSERT((rsc != NULL) && (rsc->variant == pe_clone)); \ data = (clone_variant_data_t *) rsc->variant_opaque; /*! * \internal * \brief Return the maximum number of clone instances allowed to be run * * \param[in] clone Clone or clone instance to check * * \return Maximum instances for \p clone */ int pe__clone_max(const pe_resource_t *clone) { const clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->clone_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed per node * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum allowed instances per node for \p clone */ int pe__clone_node_max(const pe_resource_t *clone) { const clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->clone_node_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed to be promoted * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum promoted instances for \p clone */ int pe__clone_promoted_max(const pe_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->promoted_max; } /*! * \internal * \brief Return the maximum number of clone instances allowed to be promoted * * \param[in] clone Promotable clone or clone instance to check * * \return Maximum promoted instances for \p clone */ int pe__clone_promoted_node_max(const pe_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, pe__const_top_resource(clone, false)); return clone_data->promoted_node_max; } static GList * sorted_hash_table_values(GHashTable *table) { GList *retval = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!g_list_find_custom(retval, value, (GCompareFunc) strcmp)) { retval = g_list_prepend(retval, (char *) value); } } retval = g_list_sort(retval, (GCompareFunc) strcmp); return retval; } static GList * nodes_with_status(GHashTable *table, const char *status) { GList *retval = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!strcmp((char *) value, status)) { retval = g_list_prepend(retval, key); } } retval = g_list_sort(retval, (GCompareFunc) pcmk__numeric_strcasecmp); return retval; } static GString * node_list_to_str(const GList *list) { GString *retval = NULL; for (const GList *iter = list; iter != NULL; iter = iter->next) { pcmk__add_word(&retval, 1024, (const char *) iter->data); } return retval; } static void clone_header(pcmk__output_t *out, int *rc, const pe_resource_t *rsc, clone_variant_data_t *clone_data, const char *desc) { GString *attrs = NULL; if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { pcmk__add_separated_word(&attrs, 64, "promotable", ", "); } if (pcmk_is_set(rsc->flags, pe_rsc_unique)) { pcmk__add_separated_word(&attrs, 64, "unique", ", "); } if (pe__resource_is_disabled(rsc)) { pcmk__add_separated_word(&attrs, 64, "disabled", ", "); } if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { pcmk__add_separated_word(&attrs, 64, "maintenance", ", "); } else if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pcmk__add_separated_word(&attrs, 64, "unmanaged", ", "); } if (attrs != NULL) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s] (%s)%s%s%s", rsc->id, ID(clone_data->xml_obj_child), (const char *) attrs->str, desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); g_string_free(attrs, TRUE); } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Clone Set: %s [%s]%s%s%s", rsc->id, ID(clone_data->xml_obj_child), desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); } } void pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid, pe_working_set_t *data_set) { if (pe_rsc_is_clone(rsc)) { clone_variant_data_t *clone_data = rsc->variant_opaque; pe_warn("Ignoring " XML_RSC_ATTR_UNIQUE " for %s because %s resources " "such as %s can be used only as anonymous clones", rsc->id, standard, rid); clone_data->clone_node_max = 1; clone_data->clone_max = QB_MIN(clone_data->clone_max, g_list_length(data_set->nodes)); } } pe_resource_t * find_clone_instance(const pe_resource_t *rsc, const char *sub_id) { char *child_id = NULL; pe_resource_t *child = NULL; const char *child_base = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); child_base = ID(clone_data->xml_obj_child); child_id = crm_strdup_printf("%s:%s", child_base, sub_id); child = pe_find_resource(rsc->children, child_id); free(child_id); return child; } pe_resource_t * pe__create_clone_child(pe_resource_t *rsc, pe_working_set_t *data_set) { gboolean as_orphan = FALSE; char *inc_num = NULL; char *inc_max = NULL; pe_resource_t *child_rsc = NULL; xmlNode *child_copy = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); CRM_CHECK(clone_data->xml_obj_child != NULL, return FALSE); if (clone_data->total_clones >= clone_data->clone_max) { // If we've already used all available instances, this is an orphan as_orphan = TRUE; } // Allocate instance numbers in numerical order (starting at 0) inc_num = pcmk__itoa(clone_data->total_clones); inc_max = pcmk__itoa(clone_data->clone_max); child_copy = copy_xml(clone_data->xml_obj_child); crm_xml_add(child_copy, XML_RSC_ATTR_INCARNATION, inc_num); if (pe__unpack_resource(child_copy, &child_rsc, rsc, data_set) != pcmk_rc_ok) { goto bail; } /* child_rsc->globally_unique = rsc->globally_unique; */ CRM_ASSERT(child_rsc); clone_data->total_clones += 1; pe_rsc_trace(child_rsc, "Setting clone attributes for: %s", child_rsc->id); rsc->children = g_list_append(rsc->children, child_rsc); if (as_orphan) { pe__set_resource_flags_recursive(child_rsc, pe_rsc_orphan); } add_hash_param(child_rsc->meta, XML_RSC_ATTR_INCARNATION_MAX, inc_max); pe_rsc_trace(rsc, "Added %s instance %s", rsc->id, child_rsc->id); bail: free(inc_num); free(inc_max); return child_rsc; } gboolean clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set) { int lpc = 0; xmlNode *a_child = NULL; xmlNode *xml_obj = rsc->xml; clone_variant_data_t *clone_data = NULL; const char *max_clones = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_MAX); const char *max_clones_node = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INCARNATION_NODEMAX); pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); clone_data = calloc(1, sizeof(clone_variant_data_t)); rsc->variant_opaque = clone_data; if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { const char *promoted_max = NULL; const char *promoted_node_max = NULL; promoted_max = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROMOTED_MAX); if (promoted_max == NULL) { // @COMPAT deprecated since 2.0.0 promoted_max = g_hash_table_lookup(rsc->meta, PCMK_XA_PROMOTED_MAX_LEGACY); } promoted_node_max = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROMOTED_NODEMAX); if (promoted_node_max == NULL) { // @COMPAT deprecated since 2.0.0 promoted_node_max = g_hash_table_lookup(rsc->meta, PCMK_XA_PROMOTED_NODE_MAX_LEGACY); } // Use 1 as default but 0 for minimum and invalid if (promoted_max == NULL) { clone_data->promoted_max = 1; } else { pcmk__scan_min_int(promoted_max, &(clone_data->promoted_max), 0); } // Use 1 as default but 0 for minimum and invalid if (promoted_node_max == NULL) { clone_data->promoted_node_max = 1; } else { pcmk__scan_min_int(promoted_node_max, &(clone_data->promoted_node_max), 0); } } // Implied by calloc() /* clone_data->xml_obj_child = NULL; */ // Use 1 as default but 0 for minimum and invalid if (max_clones_node == NULL) { clone_data->clone_node_max = 1; } else { pcmk__scan_min_int(max_clones_node, &(clone_data->clone_node_max), 0); } /* Use number of nodes (but always at least 1, which is handy for crm_verify * for a CIB without nodes) as default, but 0 for minimum and invalid */ if (max_clones == NULL) { clone_data->clone_max = QB_MAX(1, g_list_length(data_set->nodes)); } else { pcmk__scan_min_int(max_clones, &(clone_data->clone_max), 0); } if (crm_is_true(g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_ORDERED))) { clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Clone", rsc->id, clone_data->flags, pe__clone_ordered, "pe__clone_ordered"); } if ((rsc->flags & pe_rsc_unique) == 0 && clone_data->clone_node_max > 1) { pcmk__config_err("Ignoring " XML_RSC_ATTR_PROMOTED_MAX " for %s " "because anonymous clones support only one instance " "per node", rsc->id); clone_data->clone_node_max = 1; } pe_rsc_trace(rsc, "Options for %s", rsc->id); pe_rsc_trace(rsc, "\tClone max: %d", clone_data->clone_max); pe_rsc_trace(rsc, "\tClone node max: %d", clone_data->clone_node_max); pe_rsc_trace(rsc, "\tClone is unique: %s", pe__rsc_bool_str(rsc, pe_rsc_unique)); pe_rsc_trace(rsc, "\tClone is promotable: %s", pe__rsc_bool_str(rsc, pe_rsc_promotable)); // Clones may contain a single group or primitive for (a_child = pcmk__xe_first_child(xml_obj); a_child != NULL; a_child = pcmk__xe_next(a_child)) { if (pcmk__str_any_of((const char *)a_child->name, XML_CIB_TAG_RESOURCE, XML_CIB_TAG_GROUP, NULL)) { clone_data->xml_obj_child = a_child; break; } } if (clone_data->xml_obj_child == NULL) { pcmk__config_err("%s has nothing to clone", rsc->id); return FALSE; } /* * Make clones ever so slightly sticky by default * * This helps ensure clone instances are not shuffled around the cluster * for no benefit in situations when pre-allocation is not appropriate */ if (g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_STICKINESS) == NULL) { add_hash_param(rsc->meta, XML_RSC_ATTR_STICKINESS, "1"); } /* This ensures that the globally-unique value always exists for children to * inherit when being unpacked, as well as in resource agents' environment. */ add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE, pe__rsc_bool_str(rsc, pe_rsc_unique)); if (clone_data->clone_max <= 0) { /* Create one child instance so that unpack_find_resource() will hook up * any orphans up to the parent correctly. */ if (pe__create_clone_child(rsc, data_set) == NULL) { return FALSE; } } else { // Create a child instance for each available instance number for (lpc = 0; lpc < clone_data->clone_max; lpc++) { if (pe__create_clone_child(rsc, data_set) == NULL) { return FALSE; } } } pe_rsc_trace(rsc, "Added %d children to resource %s...", clone_data->clone_max, rsc->id); return TRUE; } gboolean clone_active(pe_resource_t * rsc, gboolean all) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; gboolean child_active = child_rsc->fns->active(child_rsc, all); if (all == FALSE && child_active) { return TRUE; } else if (all && child_active == FALSE) { return FALSE; } } if (all) { return TRUE; } else { return FALSE; } } /*! * \internal * \deprecated This function will be removed in a future release */ static void short_print(const char *list, const char *prefix, const char *type, const char *suffix, long options, void *print_data) { if(suffix == NULL) { suffix = ""; } if (!pcmk__str_empty(list)) { if (options & pe_print_html) { status_print("
  • "); } status_print("%s%s: [ %s ]%s", prefix, type, list, suffix); if (options & pe_print_html) { status_print("
  • \n"); } else if (options & pe_print_suppres_nl) { /* nothing */ } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("\n"); } } } static const char * configured_role_str(pe_resource_t * rsc) { const char *target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); if ((target_role == NULL) && rsc->children && rsc->children->data) { target_role = g_hash_table_lookup(((pe_resource_t*)rsc->children->data)->meta, XML_RSC_ATTR_TARGET_ROLE); } return target_role; } static enum rsc_role_e configured_role(pe_resource_t * rsc) { const char *target_role = configured_role_str(rsc); if (target_role) { return text2role(target_role); } return RSC_ROLE_UNKNOWN; } /*! * \internal * \deprecated This function will be removed in a future release */ static void clone_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { char *child_text = crm_strdup_printf("%s ", pre_text); const char *target_role = configured_role_str(rsc); GList *gIter = rsc->children; status_print("%sid); status_print("multi_state=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_promotable)); status_print("unique=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_unique)); status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed)); status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed)); status_print("failure_ignored=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored)); if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; child_rsc->fns->print(child_rsc, child_text, options, print_data); } status_print("%s\n", pre_text); free(child_text); } bool is_set_recursive(const pe_resource_t *rsc, long long flag, bool any) { GList *gIter; bool all = !any; if (pcmk_is_set(rsc->flags, flag)) { if(any) { return TRUE; } } else if(all) { return FALSE; } for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) { if(is_set_recursive(gIter->data, flag, any)) { if(any) { return TRUE; } } else if(all) { return FALSE; } } if(all) { return TRUE; } return FALSE; } /*! * \internal * \deprecated This function will be removed in a future release */ void clone_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { GString *list_text = NULL; char *child_text = NULL; GString *stopped_list = NULL; GList *promoted_list = NULL; GList *started_list = NULL; GList *gIter = rsc->children; clone_variant_data_t *clone_data = NULL; int active_instances = 0; if (pre_text == NULL) { pre_text = " "; } if (options & pe_print_xml) { clone_print_xml(rsc, pre_text, options, print_data); return; } get_clone_variant_data(clone_data, rsc); child_text = crm_strdup_printf("%s ", pre_text); status_print("%sClone Set: %s [%s]%s%s%s", pre_text ? pre_text : "", rsc->id, ID(clone_data->xml_obj_child), pcmk_is_set(rsc->flags, pe_rsc_promotable)? " (promotable)" : "", pcmk_is_set(rsc->flags, pe_rsc_unique)? " (unique)" : "", pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : " (unmanaged)"); if (options & pe_print_html) { status_print("\n
      \n"); } else if ((options & pe_print_log) == 0) { status_print("\n"); } for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (options & pe_print_clone_details) { print_full = TRUE; } if (pcmk_is_set(rsc->flags, pe_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (pcmk_is_set(options, pe_print_pending) && (child_rsc->pending_task != NULL) && strcmp(child_rsc->pending_task, "probe")) { // Print individual instance when non-probe action is pending print_full = TRUE; } else if (partially_active == FALSE) { // List stopped instances when requested (except orphans) if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan) && !pcmk_is_set(options, pe_print_clone_active)) { pcmk__add_word(&stopped_list, 1024, child_rsc->id); } } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE) || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) { // Print individual instance when active orphaned/unmanaged/failed print_full = TRUE; } else if (child_rsc->fns->active(child_rsc, TRUE)) { // Instance of fully active anonymous clone pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE); if (location) { // Instance is active on a single node enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE); if (location->details->online == FALSE && location->details->unclean) { print_full = TRUE; } else if (a_role > RSC_ROLE_UNPROMOTED) { promoted_list = g_list_append(promoted_list, location); } else { started_list = g_list_append(started_list, location); } } else { /* uncolocated group - bleh */ print_full = TRUE; } } else { // Instance of partially active anonymous clone print_full = TRUE; } if (print_full) { if (options & pe_print_html) { status_print("
    • \n"); } child_rsc->fns->print(child_rsc, child_text, options, print_data); if (options & pe_print_html) { status_print("
    • \n"); } } } /* Promoted */ promoted_list = g_list_sort(promoted_list, pe__cmp_node_name); for (gIter = promoted_list; gIter; gIter = gIter->next) { pe_node_t *host = gIter->data; pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } if (list_text != NULL) { short_print((const char *) list_text->str, child_text, PROMOTED_INSTANCES, NULL, options, print_data); g_string_truncate(list_text, 0); } g_list_free(promoted_list); /* Started/Unpromoted */ started_list = g_list_sort(started_list, pe__cmp_node_name); for (gIter = started_list; gIter; gIter = gIter->next) { pe_node_t *host = gIter->data; pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } if (list_text != NULL) { if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if (role == RSC_ROLE_UNPROMOTED) { short_print((const char *) list_text->str, child_text, UNPROMOTED_INSTANCES " (target-role)", NULL, options, print_data); } else { short_print((const char *) list_text->str, child_text, UNPROMOTED_INSTANCES, NULL, options, print_data); } } else { short_print((const char *) list_text->str, child_text, "Started", NULL, options, print_data); } } g_list_free(started_list); if (!pcmk_is_set(options, pe_print_clone_active)) { const char *state = "Stopped"; enum rsc_role_e role = configured_role(rsc); if (role == RSC_ROLE_STOPPED) { state = "Stopped (disabled)"; } if (!pcmk_is_set(rsc->flags, pe_rsc_unique) && (clone_data->clone_max > active_instances)) { GList *nIter; GList *list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped list for non-unique clones */ if (stopped_list != NULL) { g_string_truncate(stopped_list, 0); } if (list == NULL) { /* Clusters with symmetrical=false haven't calculated allowed_nodes yet * If we've not probed for them yet, the Stopped list will be empty */ list = g_hash_table_get_values(rsc->known_on); } list = g_list_sort(list, pe__cmp_node_name); for (nIter = list; nIter != NULL; nIter = nIter->next) { pe_node_t *node = (pe_node_t *)nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL) { pcmk__add_word(&stopped_list, 1024, node->details->uname); } } g_list_free(list); } if (stopped_list != NULL) { short_print((const char *) stopped_list->str, child_text, state, NULL, options, print_data); } } if (options & pe_print_html) { status_print("
    \n"); } if (list_text != NULL) { g_string_free(list_text, TRUE); } if (stopped_list != NULL) { g_string_free(stopped_list, TRUE); } free(child_text); } PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__clone_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; GList *gIter = rsc->children; GList *all = NULL; int rc = pcmk_rc_no_output; gboolean printed_header = FALSE; gboolean print_everything = TRUE; if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); all = g_list_prepend(all, (gpointer) "*"); for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) { continue; } if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) { continue; } if (!printed_header) { printed_header = TRUE; desc = pe__resource_description(rsc, show_opts); rc = pe__name_and_nvpairs_xml(out, true, "clone", 10, "id", rsc->id, "multi_state", pe__rsc_bool_str(rsc, pe_rsc_promotable), "unique", pe__rsc_bool_str(rsc, pe_rsc_unique), "maintenance", pe__rsc_bool_str(rsc, pe_rsc_maintenance), "managed", pe__rsc_bool_str(rsc, pe_rsc_managed), "disabled", pcmk__btoa(pe__resource_is_disabled(rsc)), "failed", pe__rsc_bool_str(rsc, pe_rsc_failed), "failure_ignored", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored), "target_role", configured_role_str(rsc), "description", desc); CRM_ASSERT(rc == pcmk_rc_ok); } out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, all); } if (printed_header) { pcmk__output_xml_pop_parent(out); } g_list_free(all); return rc; } PCMK__OUTPUT_ARGS("clone", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__clone_default(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); GHashTable *stopped = NULL; GString *list_text = NULL; GList *promoted_list = NULL; GList *started_list = NULL; GList *gIter = rsc->children; const char *desc = NULL; clone_variant_data_t *clone_data = NULL; int active_instances = 0; int rc = pcmk_rc_no_output; gboolean print_everything = TRUE; desc = pe__resource_description(rsc, show_opts); get_clone_variant_data(clone_data, rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } print_everything = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (pcmk__rsc_filtered_by_node(child_rsc, only_node)) { continue; } if (child_rsc->fns->is_filtered(child_rsc, only_rsc, print_everything)) { continue; } if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) { print_full = TRUE; } if (pcmk_is_set(rsc->flags, pe_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || !pcmk_is_set(rsc->flags, pe_rsc_orphan)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (pcmk_is_set(show_opts, pcmk_show_pending) && (child_rsc->pending_task != NULL) && strcmp(child_rsc->pending_task, "probe")) { // Print individual instance when non-probe action is pending print_full = TRUE; } else if (partially_active == FALSE) { // List stopped instances when requested (except orphans) if (!pcmk_is_set(child_rsc->flags, pe_rsc_orphan) && !pcmk_is_set(show_opts, pcmk_show_clone_detail) && pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { if (stopped == NULL) { stopped = pcmk__strkey_table(free, free); } g_hash_table_insert(stopped, strdup(child_rsc->id), strdup("Stopped")); } } else if (is_set_recursive(child_rsc, pe_rsc_orphan, TRUE) || is_set_recursive(child_rsc, pe_rsc_managed, FALSE) == FALSE || is_set_recursive(child_rsc, pe_rsc_failed, TRUE)) { // Print individual instance when active orphaned/unmanaged/failed print_full = TRUE; } else if (child_rsc->fns->active(child_rsc, TRUE)) { // Instance of fully active anonymous clone pe_node_t *location = child_rsc->fns->location(child_rsc, NULL, TRUE); if (location) { // Instance is active on a single node enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, TRUE); if (location->details->online == FALSE && location->details->unclean) { print_full = TRUE; } else if (a_role > RSC_ROLE_UNPROMOTED) { promoted_list = g_list_append(promoted_list, location); } else { started_list = g_list_append(started_list, location); } } else { /* uncolocated group - bleh */ print_full = TRUE; } } else { // Instance of partially active anonymous clone print_full = TRUE; } if (print_full) { GList *all = NULL; clone_header(out, &rc, rsc, clone_data, desc); /* Print every resource that's a child of this clone. */ all = g_list_prepend(all, (gpointer) "*"); out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, all); g_list_free(all); } } if (pcmk_is_set(show_opts, pcmk_show_clone_detail)) { PCMK__OUTPUT_LIST_FOOTER(out, rc); return pcmk_rc_ok; } /* Promoted */ promoted_list = g_list_sort(promoted_list, pe__cmp_node_name); for (gIter = promoted_list; gIter; gIter = gIter->next) { pe_node_t *host = gIter->data; if (!pcmk__str_in_list(host->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } g_list_free(promoted_list); if ((list_text != NULL) && (list_text->len > 0)) { clone_header(out, &rc, rsc, clone_data, desc); out->list_item(out, NULL, PROMOTED_INSTANCES ": [ %s ]", (const char *) list_text->str); g_string_truncate(list_text, 0); } /* Started/Unpromoted */ started_list = g_list_sort(started_list, pe__cmp_node_name); for (gIter = started_list; gIter; gIter = gIter->next) { pe_node_t *host = gIter->data; if (!pcmk__str_in_list(host->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { continue; } pcmk__add_word(&list_text, 1024, host->details->uname); active_instances++; } g_list_free(started_list); if ((list_text != NULL) && (list_text->len > 0)) { clone_header(out, &rc, rsc, clone_data, desc); if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if (role == RSC_ROLE_UNPROMOTED) { out->list_item(out, NULL, UNPROMOTED_INSTANCES " (target-role): [ %s ]", (const char *) list_text->str); } else { out->list_item(out, NULL, UNPROMOTED_INSTANCES ": [ %s ]", (const char *) list_text->str); } } else { out->list_item(out, NULL, "Started: [ %s ]", (const char *) list_text->str); } } if (list_text != NULL) { g_string_free(list_text, TRUE); } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { if (!pcmk_is_set(rsc->flags, pe_rsc_unique) && (clone_data->clone_max > active_instances)) { GList *nIter; GList *list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped table for non-unique clones */ if (stopped != NULL) { g_hash_table_destroy(stopped); stopped = NULL; } if (list == NULL) { /* Clusters with symmetrical=false haven't calculated allowed_nodes yet * If we've not probed for them yet, the Stopped list will be empty */ list = g_hash_table_get_values(rsc->known_on); } list = g_list_sort(list, pe__cmp_node_name); for (nIter = list; nIter != NULL; nIter = nIter->next) { pe_node_t *node = (pe_node_t *)nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL && pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node->details->uname); const char *state = "Stopped"; if (configured_role(rsc) == RSC_ROLE_STOPPED) { state = "Stopped (disabled)"; } if (stopped == NULL) { stopped = pcmk__strkey_table(free, free); } if (probe_op != NULL) { int rc; pcmk__scan_min_int(crm_element_value(probe_op, XML_LRM_ATTR_RC), &rc, 0); g_hash_table_insert(stopped, strdup(node->details->uname), crm_strdup_printf("Stopped (%s)", services_ocf_exitcode_str(rc))); } else { g_hash_table_insert(stopped, strdup(node->details->uname), strdup(state)); } } } g_list_free(list); } if (stopped != NULL) { GList *list = sorted_hash_table_values(stopped); clone_header(out, &rc, rsc, clone_data, desc); for (GList *status_iter = list; status_iter != NULL; status_iter = status_iter->next) { const char *status = status_iter->data; GList *nodes = nodes_with_status(stopped, status); GString *nodes_str = node_list_to_str(nodes); if (nodes_str != NULL) { if (nodes_str->len > 0) { out->list_item(out, NULL, "%s: [ %s ]", status, (const char *) nodes_str->str); } g_string_free(nodes_str, TRUE); } g_list_free(nodes); } g_list_free(list); g_hash_table_destroy(stopped); /* If there are no instances of this clone (perhaps because there are no * nodes configured), simply output the clone header by itself. This can * come up in PCS testing. */ } else if (active_instances == 0) { clone_header(out, &rc, rsc, clone_data, desc); PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } void clone_free(pe_resource_t * rsc) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); pe_rsc_trace(rsc, "Freeing %s", rsc->id); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; CRM_ASSERT(child_rsc); pe_rsc_trace(child_rsc, "Freeing child %s", child_rsc->id); free_xml(child_rsc->xml); child_rsc->xml = NULL; /* There could be a saved unexpanded xml */ free_xml(child_rsc->orig_xml); child_rsc->orig_xml = NULL; child_rsc->fns->free(child_rsc); } g_list_free(rsc->children); if (clone_data) { CRM_ASSERT(clone_data->demote_notify == NULL); CRM_ASSERT(clone_data->stop_notify == NULL); CRM_ASSERT(clone_data->start_notify == NULL); CRM_ASSERT(clone_data->promote_notify == NULL); } common_free(rsc); } enum rsc_role_e clone_resource_state(const pe_resource_t * rsc, gboolean current) { enum rsc_role_e clone_role = RSC_ROLE_UNKNOWN; GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; enum rsc_role_e a_role = child_rsc->fns->state(child_rsc, current); if (a_role > clone_role) { clone_role = a_role; } } pe_rsc_trace(rsc, "%s role: %s", rsc->id, role2text(clone_role)); return clone_role; } /*! * \internal * \brief Check whether a clone has an instance for every node * * \param[in] rsc Clone to check * \param[in] data_set Cluster state */ bool pe__is_universal_clone(const pe_resource_t *rsc, const pe_working_set_t *data_set) { if (pe_rsc_is_clone(rsc)) { clone_variant_data_t *clone_data = rsc->variant_opaque; if (clone_data->clone_max == g_list_length(data_set->nodes)) { return TRUE; } } return FALSE; } gboolean pe__clone_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent) { gboolean passes = FALSE; clone_variant_data_t *clone_data = NULL; if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else { get_clone_variant_data(clone_data, rsc); passes = pcmk__str_in_list(ID(clone_data->xml_obj_child), only_rsc, pcmk__str_star_matches); if (!passes) { for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) { const pe_resource_t *child_rsc = NULL; child_rsc = (const pe_resource_t *) iter->data; if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) { passes = TRUE; break; } } } } return !passes; } const char * pe__clone_child_id(const pe_resource_t *rsc) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); return ID(clone_data->xml_obj_child); } /*! * \internal * \brief Check whether a clone is ordered * * \param[in] clone Clone resource to check * * \return true if clone is ordered, otherwise false */ bool pe__clone_is_ordered(const pe_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); return pcmk_is_set(clone_data->flags, pe__clone_ordered); } /*! * \internal * \brief Set a clone flag * * \param[in,out] clone Clone resource to set flag for * \param[in] flag Clone flag to set * * \return Standard Pacemaker return code (either pcmk_rc_ok if flag was not * already set or pcmk_rc_already if it was) */ int pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); if (pcmk_is_set(clone_data->flags, flag)) { return pcmk_rc_already; } clone_data->flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, "Clone", clone->id, clone_data->flags, flag, "flag"); return pcmk_rc_ok; } /*! * \internal * \brief Create pseudo-actions needed for promotable clones * * \param[in,out] clone Promotable clone to create actions for * \param[in] any_promoting Whether any instances will be promoted * \param[in] any_demoting Whether any instance will be demoted */ void pe__create_promotable_pseudo_ops(pe_resource_t *clone, bool any_promoting, bool any_demoting) { pe_action_t *action = NULL; pe_action_t *action_complete = NULL; clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); // Create a "promote" action for the clone itself action = pe__new_rsc_pseudo_action(clone, RSC_PROMOTE, !any_promoting, true); // Create a "promoted" action for when all promotions are done action_complete = pe__new_rsc_pseudo_action(clone, RSC_PROMOTED, !any_promoting, true); action_complete->priority = INFINITY; // Create notification pseudo-actions for promotion if (clone_data->promote_notify == NULL) { clone_data->promote_notify = pe__action_notif_pseudo_ops(clone, RSC_PROMOTE, action, action_complete); } // Create a "demote" action for the clone itself action = pe__new_rsc_pseudo_action(clone, RSC_DEMOTE, !any_demoting, true); // Create a "demoted" action for when all demotions are done action_complete = pe__new_rsc_pseudo_action(clone, RSC_DEMOTED, !any_demoting, true); action_complete->priority = INFINITY; // Create notification pseudo-actions for demotion if (clone_data->demote_notify == NULL) { clone_data->demote_notify = pe__action_notif_pseudo_ops(clone, RSC_DEMOTE, action, action_complete); if (clone_data->promote_notify != NULL) { order_actions(clone_data->stop_notify->post_done, clone_data->promote_notify->pre, pe_order_optional); order_actions(clone_data->start_notify->post_done, clone_data->promote_notify->pre, pe_order_optional); order_actions(clone_data->demote_notify->post_done, clone_data->promote_notify->pre, pe_order_optional); order_actions(clone_data->demote_notify->post_done, clone_data->start_notify->pre, pe_order_optional); order_actions(clone_data->demote_notify->post_done, clone_data->stop_notify->pre, pe_order_optional); } } } /*! * \internal * \brief Create all notification data and actions for a clone * * \param[in,out] clone Clone to create notifications for */ void pe__create_clone_notifications(pe_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); pe__create_action_notifications(clone, clone_data->start_notify); pe__create_action_notifications(clone, clone_data->stop_notify); pe__create_action_notifications(clone, clone_data->promote_notify); pe__create_action_notifications(clone, clone_data->demote_notify); } /*! * \internal * \brief Free all notification data for a clone * * \param[in,out] clone Clone to free notification data for */ void pe__free_clone_notification_data(pe_resource_t *clone) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); pe__free_action_notification_data(clone_data->demote_notify); clone_data->demote_notify = NULL; pe__free_action_notification_data(clone_data->stop_notify); clone_data->stop_notify = NULL; pe__free_action_notification_data(clone_data->start_notify); clone_data->start_notify = NULL; pe__free_action_notification_data(clone_data->promote_notify); clone_data->promote_notify = NULL; } /*! * \internal * \brief Create pseudo-actions for clone start/stop notifications * * \param[in,out] clone Clone to create pseudo-actions for * \param[in,out] start Start action for \p clone * \param[in,out] stop Stop action for \p clone * \param[in,out] started Started action for \p clone * \param[in,out] stopped Stopped action for \p clone */ void pe__create_clone_notif_pseudo_ops(pe_resource_t *clone, pe_action_t *start, pe_action_t *started, pe_action_t *stop, pe_action_t *stopped) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, clone); if (clone_data->start_notify == NULL) { clone_data->start_notify = pe__action_notif_pseudo_ops(clone, RSC_START, start, started); } if (clone_data->stop_notify == NULL) { clone_data->stop_notify = pe__action_notif_pseudo_ops(clone, RSC_STOP, stop, stopped); if ((clone_data->start_notify != NULL) && (clone_data->stop_notify != NULL)) { order_actions(clone_data->stop_notify->post_done, clone_data->start_notify->pre, pe_order_optional); } } } + +/*! + * \brief Get maximum clone resource instances per node + * + * \param[in] rsc Clone resource to check + * + * \return Maximum number of \p rsc instances that can be active on one node + */ +unsigned int +pe__clone_max_per_node(const pe_resource_t *rsc) +{ + const char *max_clones_node = NULL; + int max_instances = 1; + + CRM_ASSERT((rsc != NULL) && (rsc->variant == pe_clone)); + max_clones_node = g_hash_table_lookup(rsc->meta, + XML_RSC_ATTR_INCARNATION_NODEMAX); + if (max_clones_node != NULL) { + pcmk__scan_min_int(max_clones_node, &max_instances, 0); + } + return (unsigned int) max_instances; +} diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index f1681247a4..465a479d16 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -1,1174 +1,1178 @@ /* * Copyright 2004-2023 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); static pe_node_t *active_node(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); 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, active_node, + pe__primitive_max_per_node, }, { group_unpack, native_find_rsc, native_parameter, group_print, group_active, group_resource_state, native_location, group_free, pe__count_common, pe__group_is_filtered, active_node, + pe__group_max_per_node, }, { clone_unpack, native_find_rsc, native_parameter, clone_print, clone_active, clone_resource_state, native_location, clone_free, pe__count_common, pe__clone_is_filtered, active_node, + pe__clone_max_per_node, }, { pe__unpack_bundle, native_find_rsc, native_parameter, pe__print_bundle, pe__bundle_active, pe__bundle_resource_state, native_location, pe__free_bundle, pe__count_bundle, pe__bundle_is_filtered, pe__bundle_active_node, + pe__bundle_max_per_node, } }; static enum pe_obj_types get_resource_type(const char *name) { if (pcmk__str_eq(name, 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, 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); } } 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,out] rsc Resource to query * \param[in] node Node for evaluating rules (NULL for defaults) * \param[in,out] 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, 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,out] 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)" : "")); } #ifndef PCMK__COMPAT_2_0 static void warn_about_deprecated_classes(pe_resource_t *rsc) { const char *std = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_UPSTART, pcmk__str_none)) { pe_warn_once(pe_wo_upstart, "Support for Upstart resources (such as %s) is deprecated " "and will be removed in a future release of Pacemaker", rsc->id); } else if (pcmk__str_eq(std, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_none)) { pe_warn_once(pe_wo_nagios, "Support for Nagios resources (such as %s) is deprecated " "and will be removed in a future release of Pacemaker", rsc->id); } } #endif /*! * \internal * \brief Unpack configuration XML for a given resource * * Unpack the XML object containing a resource's configuration into a new * \c 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 * \note If pcmk_rc_ok is returned, \p *rsc is guaranteed to be non-NULL, and * the caller is responsible for freeing it using its variant-specific * free() method. Otherwise, \p *rsc is guaranteed to be NULL. */ int pe__unpack_resource(xmlNode *xml_obj, 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 *id = NULL; bool guest_node = false; bool remote_node = false; pe_rule_eval_data_t rule_data = { .node_hash = NULL, .role = RSC_ROLE_UNKNOWN, .now = NULL, .match_data = NULL, .rsc_data = NULL, .op_data = NULL }; CRM_CHECK(rsc != NULL, return EINVAL); CRM_CHECK((xml_obj != NULL) && (data_set != NULL), *rsc = NULL; return EINVAL); rule_data.now = data_set->now; crm_log_xml_trace(xml_obj, "[raw XML]"); id = crm_element_value(xml_obj, XML_ATTR_ID); if (id == NULL) { pe_err("Ignoring <%s> configuration without " XML_ATTR_ID, crm_element_name(xml_obj)); 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)); if (*rsc == NULL) { crm_crit("Unable to allocate memory for resource '%s'", id); return ENOMEM; } (*rsc)->cluster = data_set; if (expanded_xml) { crm_log_xml_trace(expanded_xml, "[expanded XML]"); (*rsc)->xml = expanded_xml; (*rsc)->orig_xml = xml_obj; } else { (*rsc)->xml = xml_obj; (*rsc)->orig_xml = NULL; } /* Do not use xml_obj from here on, use (*rsc)->xml in case templates are involved */ (*rsc)->parent = parent; ops = find_xml_node((*rsc)->xml, "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("Ignoring resource '%s' of unknown type '%s'", id, crm_element_name((*rsc)->xml)); common_free(*rsc); *rsc = NULL; return pcmk_rc_unpack_error; } #ifndef PCMK__COMPAT_2_0 warn_about_deprecated_classes(*rsc); #endif (*rsc)->meta = pcmk__strkey_table(free, free); (*rsc)->allowed_nodes = pcmk__strkey_table(NULL, free); (*rsc)->known_on = pcmk__strkey_table(NULL, free); value = crm_element_value((*rsc)->xml, 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]; get_meta_attributes((*rsc)->meta, *rsc, NULL, data_set); (*rsc)->parameters = pe_rsc_params(*rsc, NULL, data_set); // \deprecated (*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 (crm_is_true(value)) { pe__set_resource_flags(*rsc, pe_rsc_allow_migrate); } else if ((value == NULL) && remote_node) { /* By default, we want remote nodes to be able * to float around the cluster without having to stop all the * resources within the remote-node before moving. Allowing * migration support enables this feature. If this ever causes * problems, migration support can be explicitly turned off with * allow-migrate=false. */ 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(pe__const_top_resource(*rsc, false))) { 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); } 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), "%s dependency restart handling: restart", (*rsc)->id); 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), "%s dependency restart handling: ignore", (*rsc)->id); } 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), "%s multiple running resource recovery: stop only", (*rsc)->id); } else if (pcmk__str_eq(value, "block", pcmk__str_casei)) { (*rsc)->recovery_type = recovery_block; pe_rsc_trace((*rsc), "%s multiple running resource recovery: block", (*rsc)->id); } else if (pcmk__str_eq(value, "stop_unexpected", pcmk__str_casei)) { (*rsc)->recovery_type = recovery_stop_unexpected; pe_rsc_trace((*rsc), "%s multiple running resource recovery: " "stop unexpected instances", (*rsc)->id); } else { // "stop_start" if (!pcmk__str_eq(value, "stop_start", pcmk__str_casei|pcmk__str_null_matches)) { 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), "%s multiple running resource recovery: " "stop/start", (*rsc)->id); } 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(crm_element_value((*rsc)->xml, XML_AGENT_ATTR_CLASS), 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), "%s desired next state: %s", (*rsc)->id, (*rsc)->next_role != RSC_ROLE_UNKNOWN ? role2text((*rsc)->next_role) : "default"); if ((*rsc)->fns->unpack(*rsc, data_set) == FALSE) { (*rsc)->fns->free(*rsc); *rsc = NULL; 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), "%s action notification: %s", (*rsc)->id, 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); if (expanded_xml) { if (add_template_rsc(xml_obj, data_set) == FALSE) { (*rsc)->fns->free(*rsc); *rsc = NULL; return pcmk_rc_unpack_error; } } return pcmk_rc_ok; } 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; } /*! * \internal * \brief Get the topmost parent of a resource as a const pointer * * \param[in] rsc Resource to check * \param[in] include_bundle If true, go all the way to bundle * * \return \p NULL if \p rsc is NULL, \p rsc if \p rsc has no parent, * the bundle if \p rsc is bundled and \p include_bundle is true, * otherwise the topmost parent of \p rsc up to a clone */ const pe_resource_t * pe__const_top_resource(const pe_resource_t *rsc, bool include_bundle) { const pe_resource_t *parent = rsc; if (parent == NULL) { return NULL; } while (parent->parent != NULL) { if (!include_bundle && (parent->parent->variant == pe_container)) { break; } 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 (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); } /*! * \internal * \brief Count a node and update most preferred to it as appropriate * * \param[in] rsc An active resource * \param[in] node A node that \p rsc is active on * \param[in,out] active This will be set to \p node if \p node is more * preferred than the current value * \param[in,out] count_all If not NULL, this will be incremented * \param[in,out] count_clean If not NULL, this will be incremented if \p node * is online and clean * * \return true if the count should continue, or false if sufficiently known */ bool pe__count_active_node(const pe_resource_t *rsc, pe_node_t *node, pe_node_t **active, unsigned int *count_all, unsigned int *count_clean) { bool keep_looking = false; bool is_happy = false; CRM_CHECK((rsc != NULL) && (node != NULL) && (active != NULL), return false); is_happy = node->details->online && !node->details->unclean; if (count_all != NULL) { ++*count_all; } if ((count_clean != NULL) && is_happy) { ++*count_clean; } if ((count_all != NULL) || (count_clean != NULL)) { keep_looking = true; // We're counting, so go through entire list } if (rsc->partial_migration_source != NULL) { if (node->details == rsc->partial_migration_source->details) { *active = node; // This is the migration source } else { keep_looking = true; } } else if (!pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) { if (is_happy && ((*active == NULL) || !(*active)->details->online || (*active)->details->unclean)) { *active = node; // This is the first clean node } else { keep_looking = true; } } if (*active == NULL) { *active = node; // This is the first node checked } return keep_looking; } // Shared implementation of resource_object_functions_t:active_node() static pe_node_t * active_node(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean) { pe_node_t *active = NULL; if (count_all != NULL) { *count_all = 0; } if (count_clean != NULL) { *count_clean = 0; } if (rsc == NULL) { return NULL; } for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { if (!pe__count_active_node(rsc, (pe_node_t *) iter->data, &active, count_all, count_clean)) { break; // Don't waste time iterating if we don't have to } } return active; } /*! * \brief * \internal Find and count active nodes according to "requires" * * \param[in] rsc Resource to check * \param[out] count If not NULL, will be set to count of active nodes * * \return An active node (or NULL if resource is not active anywhere) * * \note This is a convenience wrapper for active_node() where the count of all * active nodes or only clean active nodes is desired according to the * "requires" meta-attribute. */ pe_node_t * pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count) { if (rsc == NULL) { if (count != NULL) { *count = 0; } return NULL; } else if (pcmk_is_set(rsc->flags, pe_rsc_needs_fencing)) { return rsc->fns->active_node(rsc, count, NULL); } else { return rsc->fns->active_node(rsc, NULL, count); } } 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/group.c b/lib/pengine/group.c index d54b01ab21..44a7a31f95 100644 --- a/lib/pengine/group.c +++ b/lib/pengine/group.c @@ -1,521 +1,535 @@ /* * Copyright 2004-2023 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 typedef struct group_variant_data_s { pe_resource_t *last_child; // Last group member uint32_t flags; // Group of enum pe__group_flags } group_variant_data_t; /*! * \internal * \brief Get a group's last member * * \param[in] group Group resource to check * * \return Last member of \p group if any, otherwise NULL */ pe_resource_t * pe__last_group_member(const pe_resource_t *group) { if (group != NULL) { CRM_CHECK((group->variant == pe_group) && (group->variant_opaque != NULL), return NULL); return ((group_variant_data_t *) group->variant_opaque)->last_child; } return NULL; } /*! * \internal * \brief Check whether a group flag is set * * \param[in] group Group resource to check * \param[in] flags Flag or flags to check * * \return true if all \p flags are set for \p group, otherwise false */ bool pe__group_flag_is_set(const pe_resource_t *group, uint32_t flags) { group_variant_data_t *group_data = NULL; CRM_CHECK((group != NULL) && (group->variant == pe_group) && (group->variant_opaque != NULL), return false); group_data = (group_variant_data_t *) group->variant_opaque; return pcmk_all_flags_set(group_data->flags, flags); } /*! * \internal * \brief Set a (deprecated) group flag * * \param[in,out] group Group resource to check * \param[in] option Name of boolean configuration option * \param[in] flag Flag to set if \p option is true (which is default) * \param[in] wo_bit "Warn once" flag to use for deprecation warning */ static void set_group_flag(pe_resource_t *group, const char *option, uint32_t flag, uint32_t wo_bit) { const char *value_s = NULL; int value = 0; value_s = g_hash_table_lookup(group->meta, option); // We don't actually need the null check but it speeds up the common case if ((value_s == NULL) || (crm_str_to_boolean(value_s, &value) < 0) || (value != 0)) { ((group_variant_data_t *) group->variant_opaque)->flags |= flag; } else { pe_warn_once(wo_bit, "Support for the '%s' group meta-attribute is deprecated " "and will be removed in a future release " "(use a resource set instead)", option); } } static int inactive_resources(pe_resource_t *rsc) { int retval = 0; for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (!child_rsc->fns->active(child_rsc, TRUE)) { retval++; } } return retval; } static void group_header(pcmk__output_t *out, int *rc, const pe_resource_t *rsc, int n_inactive, bool show_inactive, const char *desc) { GString *attrs = NULL; if (n_inactive > 0 && !show_inactive) { attrs = g_string_sized_new(64); g_string_append_printf(attrs, "%d member%s inactive", n_inactive, pcmk__plural_s(n_inactive)); } if (pe__resource_is_disabled(rsc)) { pcmk__add_separated_word(&attrs, 64, "disabled", ", "); } if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { pcmk__add_separated_word(&attrs, 64, "maintenance", ", "); } else if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pcmk__add_separated_word(&attrs, 64, "unmanaged", ", "); } if (attrs != NULL) { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s (%s)%s%s%s", rsc->id, (const char *) attrs->str, desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); g_string_free(attrs, TRUE); } else { PCMK__OUTPUT_LIST_HEADER(out, FALSE, *rc, "Resource Group: %s%s%s%s", rsc->id, desc ? " (" : "", desc ? desc : "", desc ? ")" : ""); } } static bool skip_child_rsc(pe_resource_t *rsc, pe_resource_t *child, gboolean parent_passes, GList *only_rsc, uint32_t show_opts) { bool star_list = pcmk__list_of_1(only_rsc) && pcmk__str_eq("*", g_list_first(only_rsc)->data, pcmk__str_none); bool child_filtered = child->fns->is_filtered(child, only_rsc, FALSE); bool child_active = child->fns->active(child, FALSE); bool show_inactive = pcmk_is_set(show_opts, pcmk_show_inactive_rscs); /* If the resource is in only_rsc by name (so, ignoring "*") then allow * it regardless of if it's active or not. */ if (!star_list && !child_filtered) { return false; } else if (!child_filtered && (child_active || show_inactive)) { return false; } else if (parent_passes && (child_active || show_inactive)) { return false; } return true; } gboolean group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set) { xmlNode *xml_obj = rsc->xml; xmlNode *xml_native_rsc = NULL; group_variant_data_t *group_data = NULL; const char *clone_id = NULL; pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); group_data = calloc(1, sizeof(group_variant_data_t)); group_data->last_child = NULL; rsc->variant_opaque = group_data; // @COMPAT These are deprecated since 2.1.5 set_group_flag(rsc, XML_RSC_ATTR_ORDERED, pe__group_ordered, pe_wo_group_order); set_group_flag(rsc, "collocated", pe__group_colocated, pe_wo_group_coloc); clone_id = crm_element_value(rsc->xml, XML_RSC_ATTR_INCARNATION); for (xml_native_rsc = pcmk__xe_first_child(xml_obj); xml_native_rsc != NULL; xml_native_rsc = pcmk__xe_next(xml_native_rsc)) { if (pcmk__str_eq((const char *)xml_native_rsc->name, XML_CIB_TAG_RESOURCE, pcmk__str_none)) { pe_resource_t *new_rsc = NULL; crm_xml_add(xml_native_rsc, XML_RSC_ATTR_INCARNATION, clone_id); if (pe__unpack_resource(xml_native_rsc, &new_rsc, rsc, data_set) != pcmk_rc_ok) { continue; } rsc->children = g_list_append(rsc->children, new_rsc); group_data->last_child = new_rsc; pe_rsc_trace(rsc, "Added %s member %s", rsc->id, new_rsc->id); } } if (rsc->children == NULL) { /* The schema does not allow empty groups, but if validation is * disabled, we allow them (members can be added later). * * @COMPAT At a major release bump, we should consider this a failure so * that group methods can assume children is not NULL, and there * are no strange effects from phantom groups due to their * presence or meta-attributes. */ pcmk__config_warn("Group %s will be ignored because it does not have " "any members", rsc->id); } return TRUE; } gboolean group_active(pe_resource_t * rsc, gboolean all) { gboolean c_all = TRUE; gboolean c_any = FALSE; GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (child_rsc->fns->active(child_rsc, all)) { c_any = TRUE; } else { c_all = FALSE; } } if (c_any == FALSE) { return FALSE; } else if (all && c_all == FALSE) { return FALSE; } return TRUE; } /*! * \internal * \deprecated This function will be removed in a future release */ static void group_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { GList *gIter = rsc->children; char *child_text = crm_strdup_printf("%s ", pre_text); status_print("%sid); status_print("number_resources=\"%d\" ", g_list_length(rsc->children)); status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; child_rsc->fns->print(child_rsc, child_text, options, print_data); } status_print("%s\n", pre_text); free(child_text); } /*! * \internal * \deprecated This function will be removed in a future release */ void group_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { char *child_text = NULL; GList *gIter = rsc->children; if (pre_text == NULL) { pre_text = " "; } if (options & pe_print_xml) { group_print_xml(rsc, pre_text, options, print_data); return; } child_text = crm_strdup_printf("%s ", pre_text); status_print("%sResource Group: %s", pre_text ? pre_text : "", rsc->id); if (options & pe_print_html) { status_print("\n
      \n"); } else if ((options & pe_print_log) == 0) { status_print("\n"); } if (options & pe_print_brief) { print_rscs_brief(rsc->children, child_text, options, print_data, TRUE); } else { for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (options & pe_print_html) { status_print("
    • \n"); } child_rsc->fns->print(child_rsc, child_text, options, print_data); if (options & pe_print_html) { status_print("
    • \n"); } } } if (options & pe_print_html) { status_print("
    \n"); } free(child_text); } PCMK__OUTPUT_ARGS("group", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__group_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; GList *gIter = rsc->children; int rc = pcmk_rc_no_output; gboolean parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); desc = pe__resource_description(rsc, show_opts); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) { continue; } if (rc == pcmk_rc_no_output) { char *count = pcmk__itoa(g_list_length(gIter)); const char *maint_s = pe__rsc_bool_str(rsc, pe_rsc_maintenance); const char *managed_s = pe__rsc_bool_str(rsc, pe_rsc_managed); const char *disabled_s = pcmk__btoa(pe__resource_is_disabled(rsc)); rc = pe__name_and_nvpairs_xml(out, true, "group", 5, XML_ATTR_ID, rsc->id, "number_resources", count, "maintenance", maint_s, "managed", managed_s, "disabled", disabled_s, "description", desc); free(count); CRM_ASSERT(rc == pcmk_rc_ok); } out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, only_rsc); } if (rc == pcmk_rc_ok) { pcmk__output_xml_pop_parent(out); } return rc; } PCMK__OUTPUT_ARGS("group", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__group_default(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const char *desc = NULL; int rc = pcmk_rc_no_output; gboolean parent_passes = pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)); gboolean active = rsc->fns->active(rsc, TRUE); gboolean partially_active = rsc->fns->active(rsc, FALSE); desc = pe__resource_description(rsc, show_opts); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return rc; } if (pcmk_is_set(show_opts, pcmk_show_brief)) { GList *rscs = pe__filter_rsc_list(rsc->children, only_rsc); if (rscs != NULL) { group_header(out, &rc, rsc, !active && partially_active ? inactive_resources(rsc) : 0, pcmk_is_set(show_opts, pcmk_show_inactive_rscs), desc); pe__rscs_brief_output(out, rscs, show_opts | pcmk_show_inactive_rscs); rc = pcmk_rc_ok; g_list_free(rscs); } } else { for (GList *gIter = rsc->children; gIter; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; if (skip_child_rsc(rsc, child_rsc, parent_passes, only_rsc, show_opts)) { continue; } group_header(out, &rc, rsc, !active && partially_active ? inactive_resources(rsc) : 0, pcmk_is_set(show_opts, pcmk_show_inactive_rscs), desc); out->message(out, crm_map_element_name(child_rsc->xml), show_opts, child_rsc, only_node, only_rsc); } } PCMK__OUTPUT_LIST_FOOTER(out, rc); return rc; } void group_free(pe_resource_t * rsc) { CRM_CHECK(rsc != NULL, return); pe_rsc_trace(rsc, "Freeing %s", rsc->id); for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; CRM_ASSERT(child_rsc); pe_rsc_trace(child_rsc, "Freeing child %s", child_rsc->id); child_rsc->fns->free(child_rsc); } pe_rsc_trace(rsc, "Freeing child list"); g_list_free(rsc->children); common_free(rsc); } enum rsc_role_e group_resource_state(const pe_resource_t * rsc, gboolean current) { enum rsc_role_e group_role = RSC_ROLE_UNKNOWN; GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child_rsc = (pe_resource_t *) gIter->data; enum rsc_role_e role = child_rsc->fns->state(child_rsc, current); if (role > group_role) { group_role = role; } } pe_rsc_trace(rsc, "%s role: %s", rsc->id, role2text(group_role)); return group_role; } gboolean pe__group_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent) { gboolean passes = FALSE; if (check_parent && pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else if (strstr(rsc->id, ":") != NULL && pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) { passes = TRUE; } else { for (const GList *iter = rsc->children; iter != NULL; iter = iter->next) { const pe_resource_t *child_rsc = (const pe_resource_t *) iter->data; if (!child_rsc->fns->is_filtered(child_rsc, only_rsc, FALSE)) { passes = TRUE; break; } } } return !passes; } + +/*! + * \brief Get maximum group resource instances per node + * + * \param[in] rsc Group resource to check + * + * \return Maximum number of \p rsc instances that can be active on one node + */ +unsigned int +pe__group_max_per_node(const pe_resource_t *rsc) +{ + CRM_ASSERT((rsc != NULL) && (rsc->variant == pe_group)); + return 1U; +} diff --git a/lib/pengine/native.c b/lib/pengine/native.c index 5e92ddcefd..6715e572c3 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -1,1414 +1,1428 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #ifdef PCMK__COMPAT_2_0 #define PROVIDER_SEP "::" #else #define PROVIDER_SEP ":" #endif /*! * \internal * \brief Check whether a resource is active on multiple nodes */ static bool is_multiply_active(const pe_resource_t *rsc) { unsigned int count = 0; if (rsc->variant == pe_native) { pe__find_active_requires(rsc, &count); } return count > 1; } static void native_priority_to_node(pe_resource_t * rsc, pe_node_t * node, gboolean failed) { int priority = 0; if ((rsc->priority == 0) || (failed == TRUE)) { return; } if (rsc->role == RSC_ROLE_PROMOTED) { // Promoted instance takes base priority + 1 priority = rsc->priority + 1; } else { priority = rsc->priority; } node->details->priority += priority; pe_rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s)", pe__node_name(node), node->details->priority, (rsc->role == RSC_ROLE_PROMOTED)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == RSC_ROLE_PROMOTED)? " + 1" : ""); /* Priority of a resource running on a guest node is added to the cluster * node as well. */ if (node->details->remote_rsc && node->details->remote_rsc->container) { GList *gIter = node->details->remote_rsc->container->running_on; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = gIter->data; a_node->details->priority += priority; pe_rsc_trace(rsc, "%s now has priority %d with %s'%s' (priority: %d%s) " "from guest node %s", pe__node_name(a_node), a_node->details->priority, (rsc->role == RSC_ROLE_PROMOTED)? "promoted " : "", rsc->id, rsc->priority, (rsc->role == RSC_ROLE_PROMOTED)? " + 1" : "", pe__node_name(node)); } } } void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set, gboolean failed) { GList *gIter = rsc->running_on; CRM_CHECK(node != NULL, return); for (; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = (pe_node_t *) gIter->data; CRM_CHECK(a_node != NULL, return); if (pcmk__str_eq(a_node->details->id, node->details->id, pcmk__str_casei)) { return; } } pe_rsc_trace(rsc, "Adding %s to %s %s", rsc->id, pe__node_name(node), pcmk_is_set(rsc->flags, pe_rsc_managed)? "" : "(unmanaged)"); rsc->running_on = g_list_append(rsc->running_on, node); if (rsc->variant == pe_native) { node->details->running_rsc = g_list_append(node->details->running_rsc, rsc); native_priority_to_node(rsc, node, failed); } if (rsc->variant == pe_native && node->details->maintenance) { pe__clear_resource_flags(rsc, pe_rsc_managed); pe__set_resource_flags(rsc, pe_rsc_maintenance); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_resource_t *p = rsc->parent; pe_rsc_info(rsc, "resource %s isn't managed", rsc->id); resource_location(rsc, node, INFINITY, "not_managed_default", data_set); while(p && node->details->online) { /* add without the additional location constraint */ p->running_on = g_list_append(p->running_on, node); p = p->parent; } return; } if (is_multiply_active(rsc)) { switch (rsc->recovery_type) { case recovery_stop_only: { GHashTableIter gIter; pe_node_t *local_node = NULL; /* make sure it doesn't come up again */ if (rsc->allowed_nodes != NULL) { g_hash_table_destroy(rsc->allowed_nodes); } rsc->allowed_nodes = pe__node_list2table(data_set->nodes); g_hash_table_iter_init(&gIter, rsc->allowed_nodes); while (g_hash_table_iter_next(&gIter, NULL, (void **)&local_node)) { local_node->weight = -INFINITY; } } break; case recovery_block: pe__clear_resource_flags(rsc, pe_rsc_managed); pe__set_resource_flags(rsc, pe_rsc_block); /* If the resource belongs to a group or bundle configured with * multiple-active=block, block the entire entity. */ if (rsc->parent && (rsc->parent->variant == pe_group || rsc->parent->variant == pe_container) && rsc->parent->recovery_type == recovery_block) { GList *gIter = rsc->parent->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; pe__clear_resource_flags(child, pe_rsc_managed); pe__set_resource_flags(child, pe_rsc_block); } } break; default: // recovery_stop_start, recovery_stop_unexpected /* The scheduler will do the right thing because the relevant * variables and flags are set when unpacking the history. */ break; } crm_debug("%s is active on multiple nodes including %s: %s", rsc->id, pe__node_name(node), recovery2text(rsc->recovery_type)); } else { pe_rsc_trace(rsc, "Resource %s is active on %s", rsc->id, pe__node_name(node)); } if (rsc->parent != NULL) { native_add_running(rsc->parent, node, data_set, FALSE); } } static void recursive_clear_unique(pe_resource_t *rsc, gpointer user_data) { pe__clear_resource_flags(rsc, pe_rsc_unique); add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE, XML_BOOLEAN_FALSE); g_list_foreach(rsc->children, (GFunc) recursive_clear_unique, NULL); } gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set) { pe_resource_t *parent = uber_parent(rsc); const char *standard = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); uint32_t ra_caps = pcmk_get_ra_caps(standard); pe_rsc_trace(rsc, "Processing resource %s...", rsc->id); // Only some agent standards support unique and promotable clones if (!pcmk_is_set(ra_caps, pcmk_ra_cap_unique) && pcmk_is_set(rsc->flags, pe_rsc_unique) && pe_rsc_is_clone(parent)) { /* @COMPAT We should probably reject this situation as an error (as we * do for promotable below) rather than warn and convert, but that would * be a backward-incompatible change that we should probably do with a * transform at a schema major version bump. */ pe__force_anon(standard, parent, rsc->id, data_set); /* Clear globally-unique on the parent and all its descendants unpacked * so far (clearing the parent should make any future children unpacking * correct). We have to clear this resource explicitly because it isn't * hooked into the parent's children yet. */ recursive_clear_unique(parent, NULL); recursive_clear_unique(rsc, NULL); } if (!pcmk_is_set(ra_caps, pcmk_ra_cap_promotable) && pcmk_is_set(parent->flags, pe_rsc_promotable)) { pe_err("Resource %s is of type %s and therefore " "cannot be used as a promotable clone resource", rsc->id, standard); return FALSE; } return TRUE; } static bool rsc_is_on_node(pe_resource_t *rsc, const pe_node_t *node, int flags) { pe_rsc_trace(rsc, "Checking whether %s is on %s", rsc->id, pe__node_name(node)); if (pcmk_is_set(flags, pe_find_current) && rsc->running_on) { for (GList *iter = rsc->running_on; iter; iter = iter->next) { pe_node_t *loc = (pe_node_t *) iter->data; if (loc->details == node->details) { return true; } } } else if (pcmk_is_set(flags, pe_find_inactive) && (rsc->running_on == NULL)) { return true; } else if (!pcmk_is_set(flags, pe_find_current) && rsc->allocated_to && (rsc->allocated_to->details == node->details)) { return true; } return false; } pe_resource_t * native_find_rsc(pe_resource_t * rsc, const char *id, const pe_node_t *on_node, int flags) { bool match = false; pe_resource_t *result = NULL; CRM_CHECK(id && rsc && rsc->id, return NULL); if (flags & pe_find_clone) { const char *rid = ID(rsc->xml); if (!pe_rsc_is_clone(pe__const_top_resource(rsc, false))) { match = false; } else if (!strcmp(id, rsc->id) || pcmk__str_eq(id, rid, pcmk__str_none)) { match = true; } } else if (!strcmp(id, rsc->id)) { match = true; } else if (pcmk_is_set(flags, pe_find_renamed) && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) { match = true; } else if (pcmk_is_set(flags, pe_find_any) || (pcmk_is_set(flags, pe_find_anon) && !pcmk_is_set(rsc->flags, pe_rsc_unique))) { match = pe_base_name_eq(rsc, id); } if (match && on_node) { if (!rsc_is_on_node(rsc, on_node, flags)) { match = false; } } if (match) { return rsc; } for (GList *gIter = rsc->children; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; result = rsc->fns->find_rsc(child, id, on_node, flags); if (result) { return result; } } return NULL; } // create is ignored char * native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name, pe_working_set_t * data_set) { char *value_copy = NULL; const char *value = NULL; GHashTable *params = NULL; CRM_CHECK(rsc != NULL, return NULL); CRM_CHECK(name != NULL && strlen(name) != 0, return NULL); pe_rsc_trace(rsc, "Looking up %s in %s", name, rsc->id); params = pe_rsc_params(rsc, node, data_set); value = g_hash_table_lookup(params, name); if (value == NULL) { /* try meta attributes instead */ value = g_hash_table_lookup(rsc->meta, name); } pcmk__str_update(&value_copy, value); return value_copy; } gboolean native_active(pe_resource_t * rsc, gboolean all) { for (GList *gIter = rsc->running_on; gIter != NULL; gIter = gIter->next) { pe_node_t *a_node = (pe_node_t *) gIter->data; if (a_node->details->unclean) { pe_rsc_trace(rsc, "Resource %s: %s is unclean", rsc->id, pe__node_name(a_node)); return TRUE; } else if (a_node->details->online == FALSE && pcmk_is_set(rsc->flags, pe_rsc_managed)) { pe_rsc_trace(rsc, "Resource %s: %s is offline", rsc->id, pe__node_name(a_node)); } else { pe_rsc_trace(rsc, "Resource %s active on %s", rsc->id, pe__node_name(a_node)); return TRUE; } } return FALSE; } struct print_data_s { long options; void *print_data; }; static const char * native_pending_state(const pe_resource_t *rsc) { const char *pending_state = NULL; if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_START, pcmk__str_casei)) { pending_state = "Starting"; } else if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_STOP, pcmk__str_casei)) { pending_state = "Stopping"; } else if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_MIGRATE, pcmk__str_casei)) { pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_MIGRATED, pcmk__str_casei)) { /* Work might be done in here. */ pending_state = "Migrating"; } else if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_PROMOTE, pcmk__str_casei)) { pending_state = "Promoting"; } else if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_DEMOTE, pcmk__str_casei)) { pending_state = "Demoting"; } return pending_state; } static const char * native_pending_task(const pe_resource_t *rsc) { const char *pending_task = NULL; if (pcmk__str_eq(rsc->pending_task, CRMD_ACTION_STATUS, pcmk__str_casei)) { pending_task = "Monitoring"; /* Pending probes are not printed, even if pending * operations are requested. If someone ever requests that * behavior, uncomment this and the corresponding part of * unpack.c:unpack_rsc_op(). */ /* } else if (pcmk__str_eq(rsc->pending_task, "probe", pcmk__str_casei)) { pending_task = "Checking"; */ } return pending_task; } static enum rsc_role_e native_displayable_role(const pe_resource_t *rsc) { enum rsc_role_e role = rsc->role; if ((role == RSC_ROLE_STARTED) && pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable)) { role = RSC_ROLE_UNPROMOTED; } return role; } static const char * native_displayable_state(const pe_resource_t *rsc, bool print_pending) { const char *rsc_state = NULL; if (print_pending) { rsc_state = native_pending_state(rsc); } if (rsc_state == NULL) { rsc_state = role2text(native_displayable_role(rsc)); } return rsc_state; } /*! * \internal * \deprecated This function will be removed in a future release */ static void native_print_xml(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rsc_state = native_displayable_state(rsc, pcmk_is_set(options, pe_print_pending)); const char *target_role = NULL; /* resource information. */ status_print("%sxml, XML_ATTR_TYPE)); status_print("role=\"%s\" ", rsc_state); if (rsc->meta) { target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print("active=\"%s\" ", pcmk__btoa(rsc->fns->active(rsc, TRUE))); status_print("orphaned=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_orphan)); status_print("blocked=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_block)); status_print("managed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_managed)); status_print("failed=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failed)); status_print("failure_ignored=\"%s\" ", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored)); status_print("nodes_running_on=\"%d\" ", g_list_length(rsc->running_on)); if (options & pe_print_pending) { const char *pending_task = native_pending_task(rsc); if (pending_task) { status_print("pending=\"%s\" ", pending_task); } } /* print out the nodes this resource is running on */ if (options & pe_print_rsconly) { status_print("/>\n"); /* do nothing */ } else if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; status_print("%s \n", pre_text, pcmk__s(node->details->uname, ""), node->details->id, pcmk__btoa(!node->details->online)); } status_print("%s\n", pre_text); } else { status_print("/>\n"); } } // Append a flag to resource description string's flags list static bool add_output_flag(GString *s, const char *flag_desc, bool have_flags) { g_string_append(s, (have_flags? ", " : " (")); g_string_append(s, flag_desc); return true; } // Append a node name to resource description string's node list static bool add_output_node(GString *s, const char *node, bool have_nodes) { g_string_append(s, (have_nodes? " " : " [ ")); g_string_append(s, node); return true; } /*! * \internal * \brief Create a string description of a resource * * \param[in] rsc Resource to describe * \param[in] name Desired identifier for the resource * \param[in] node If not NULL, node that resource is "on" * \param[in] show_opts Bitmask of pcmk_show_opt_e. * \param[in] target_role Resource's target role * \param[in] show_nodes Whether to display nodes when multiply active * * \return Newly allocated string description of resource * \note Caller must free the result with g_free(). */ gchar * pcmk__native_output_string(const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts, const char *target_role, bool show_nodes) { const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *provider = NULL; const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); GString *outstr = NULL; bool have_flags = false; if (rsc->variant != pe_native) { return NULL; } CRM_CHECK(name != NULL, name = "unknown"); CRM_CHECK(kind != NULL, kind = "unknown"); CRM_CHECK(class != NULL, class = "unknown"); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); } if ((node == NULL) && (rsc->lock_node != NULL)) { node = rsc->lock_node; } if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only) || pcmk__list_of_multiple(rsc->running_on)) { node = NULL; } outstr = g_string_sized_new(128); // Resource name and agent pcmk__g_strcat(outstr, name, "\t(", class, ((provider == NULL)? "" : PROVIDER_SEP), pcmk__s(provider, ""), ":", kind, "):\t", NULL); // State on node if (pcmk_is_set(rsc->flags, pe_rsc_orphan)) { g_string_append(outstr, " ORPHANED"); } if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { enum rsc_role_e role = native_displayable_role(rsc); g_string_append(outstr, " FAILED"); if (role > RSC_ROLE_UNPROMOTED) { pcmk__add_word(&outstr, 0, role2text(role)); } } else { bool show_pending = pcmk_is_set(show_opts, pcmk_show_pending); pcmk__add_word(&outstr, 0, native_displayable_state(rsc, show_pending)); } if (node) { pcmk__add_word(&outstr, 0, pe__node_name(node)); } // Failed probe operation if (native_displayable_role(rsc) == RSC_ROLE_STOPPED) { xmlNode *probe_op = pe__failed_probe_for_rsc(rsc, node ? node->details->uname : NULL); if (probe_op != NULL) { int rc; pcmk__scan_min_int(crm_element_value(probe_op, XML_LRM_ATTR_RC), &rc, 0); pcmk__g_strcat(outstr, " (", services_ocf_exitcode_str(rc), ") ", NULL); } } // Flags, as: ( [...]) if (node && !(node->details->online) && node->details->unclean) { have_flags = add_output_flag(outstr, "UNCLEAN", have_flags); } if (node && (node == rsc->lock_node)) { have_flags = add_output_flag(outstr, "LOCKED", have_flags); } if (pcmk_is_set(show_opts, pcmk_show_pending)) { const char *pending_task = native_pending_task(rsc); if (pending_task) { have_flags = add_output_flag(outstr, pending_task, have_flags); } } if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); /* Only show target role if it limits our abilities (i.e. ignore * Started, as it is the default anyways, and doesn't prevent the * resource from becoming promoted). */ if (target_role_e == RSC_ROLE_STOPPED) { have_flags = add_output_flag(outstr, "disabled", have_flags); } else if (pcmk_is_set(pe__const_top_resource(rsc, false)->flags, pe_rsc_promotable) && target_role_e == RSC_ROLE_UNPROMOTED) { have_flags = add_output_flag(outstr, "target-role:", have_flags); g_string_append(outstr, target_role); } } // Blocked or maintenance implies unmanaged if (pcmk_any_flags_set(rsc->flags, pe_rsc_block|pe_rsc_maintenance)) { if (pcmk_is_set(rsc->flags, pe_rsc_block)) { have_flags = add_output_flag(outstr, "blocked", have_flags); } else if (pcmk_is_set(rsc->flags, pe_rsc_maintenance)) { have_flags = add_output_flag(outstr, "maintenance", have_flags); } } else if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { have_flags = add_output_flag(outstr, "unmanaged", have_flags); } if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { have_flags = add_output_flag(outstr, "failure ignored", have_flags); } if (have_flags) { g_string_append_c(outstr, ')'); } // User-supplied description if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description) || pcmk__list_of_multiple(rsc->running_on)) { const char *desc = crm_element_value(rsc->xml, XML_ATTR_DESC); if (desc) { g_string_append(outstr, " ("); g_string_append(outstr, desc); g_string_append(outstr, ")"); } } if (show_nodes && !pcmk_is_set(show_opts, pcmk_show_rsc_only) && pcmk__list_of_multiple(rsc->running_on)) { bool have_nodes = false; for (GList *iter = rsc->running_on; iter != NULL; iter = iter->next) { pe_node_t *n = (pe_node_t *) iter->data; have_nodes = add_output_node(outstr, n->details->uname, have_nodes); } if (have_nodes) { g_string_append(outstr, " ]"); } } return g_string_free(outstr, FALSE); } int pe__common_output_html(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts) { const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *target_role = NULL; xmlNodePtr list_node = NULL; const char *cl = NULL; CRM_ASSERT(rsc->variant == pe_native); CRM_ASSERT(kind != NULL); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { cl = "rsc-managed"; } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { cl = "rsc-failed"; } else if (rsc->variant == pe_native && (rsc->running_on == NULL)) { cl = "rsc-failed"; } else if (pcmk__list_of_multiple(rsc->running_on)) { cl = "rsc-multiple"; } else if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { cl = "rsc-failure-ignored"; } else { cl = "rsc-ok"; } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); list_node = pcmk__output_create_html_node(out, "li", NULL, NULL, NULL); pcmk_create_html_node(list_node, "span", NULL, cl, s); g_free(s); } return pcmk_rc_ok; } int pe__common_output_text(pcmk__output_t *out, const pe_resource_t *rsc, const char *name, const pe_node_t *node, uint32_t show_opts) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pe_native); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(show_opts, pcmk_show_implicit_rscs)) { crm_trace("skipping print of internal resource %s", rsc->id); return pcmk_rc_no_output; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } { gchar *s = pcmk__native_output_string(rsc, name, node, show_opts, target_role, true); out->list_item(out, NULL, "%s", s); g_free(s); } return pcmk_rc_ok; } /*! * \internal * \deprecated This function will be removed in a future release */ void common_print(pe_resource_t *rsc, const char *pre_text, const char *name, const pe_node_t *node, long options, void *print_data) { const char *target_role = NULL; CRM_ASSERT(rsc->variant == pe_native); if (rsc->meta) { const char *is_internal = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_INTERNAL_RSC); if (crm_is_true(is_internal) && !pcmk_is_set(options, pe_print_implicit)) { crm_trace("skipping print of internal resource %s", rsc->id); return; } target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } if ((pre_text == NULL) && (options & pe_print_printf)) { pre_text = " "; } if (options & pe_print_html) { if (!pcmk_is_set(rsc->flags, pe_rsc_managed)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pe_rsc_failed)) { status_print(""); } else if (rsc->running_on == NULL) { status_print(""); } else if (pcmk__list_of_multiple(rsc->running_on)) { status_print(""); } else if (pcmk_is_set(rsc->flags, pe_rsc_failure_ignored)) { status_print(""); } else { status_print(""); } } { gchar *resource_s = pcmk__native_output_string(rsc, name, node, options, target_role, false); status_print("%s%s", (pre_text? pre_text : ""), resource_s); g_free(resource_s); } if (pcmk_is_set(options, pe_print_html)) { status_print(" "); } if (!pcmk_is_set(options, pe_print_rsconly) && pcmk__list_of_multiple(rsc->running_on)) { GList *gIter = rsc->running_on; int counter = 0; if (options & pe_print_html) { status_print("
      \n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("["); } for (; gIter != NULL; gIter = gIter->next) { pe_node_t *n = (pe_node_t *) gIter->data; counter++; if (options & pe_print_html) { status_print("
    • \n%s", pe__node_name(n)); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" %s", pe__node_name(n)); } else if ((options & pe_print_log)) { status_print("\t%d : %s", counter, pe__node_name(n)); } else { status_print("%s", pe__node_name(n)); } if (options & pe_print_html) { status_print("
    • \n"); } } if (options & pe_print_html) { status_print("
    \n"); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" ]"); } } if (options & pe_print_html) { status_print("
    \n"); } else if (options & pe_print_suppres_nl) { /* nothing */ } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print("\n"); } } /*! * \internal * \deprecated This function will be removed in a future release */ void native_print(pe_resource_t *rsc, const char *pre_text, long options, void *print_data) { const pe_node_t *node = NULL; CRM_ASSERT(rsc->variant == pe_native); if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } node = pe__current_node(rsc); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } common_print(rsc, pre_text, rsc_printable_id(rsc), node, options, print_data); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_xml(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); bool print_pending = pcmk_is_set(show_opts, pcmk_show_pending); const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); const char *rsc_state = native_displayable_state(rsc, print_pending); const char *desc = NULL; char ra_name[LINE_MAX]; char *nodes_running_on = NULL; const char *lock_node_name = NULL; int rc = pcmk_rc_no_output; const char *target_role = NULL; desc = pe__resource_description(rsc, show_opts); if (rsc->meta != NULL) { target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); } CRM_ASSERT(rsc->variant == pe_native); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } /* resource information. */ snprintf(ra_name, LINE_MAX, "%s%s%s:%s", class, ((prov == NULL)? "" : PROVIDER_SEP), ((prov == NULL)? "" : prov), crm_element_value(rsc->xml, XML_ATTR_TYPE)); nodes_running_on = pcmk__itoa(g_list_length(rsc->running_on)); if (rsc->lock_node != NULL) { lock_node_name = rsc->lock_node->details->uname; } rc = pe__name_and_nvpairs_xml(out, true, "resource", 15, "id", rsc_printable_id(rsc), "resource_agent", ra_name, "role", rsc_state, "target_role", target_role, "active", pcmk__btoa(rsc->fns->active(rsc, TRUE)), "orphaned", pe__rsc_bool_str(rsc, pe_rsc_orphan), "blocked", pe__rsc_bool_str(rsc, pe_rsc_block), "maintenance", pe__rsc_bool_str(rsc, pe_rsc_maintenance), "managed", pe__rsc_bool_str(rsc, pe_rsc_managed), "failed", pe__rsc_bool_str(rsc, pe_rsc_failed), "failure_ignored", pe__rsc_bool_str(rsc, pe_rsc_failure_ignored), "nodes_running_on", nodes_running_on, "pending", (print_pending? native_pending_task(rsc) : NULL), "locked_to", lock_node_name, "description", desc); free(nodes_running_on); CRM_ASSERT(rc == pcmk_rc_ok); if (rsc->running_on != NULL) { GList *gIter = rsc->running_on; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; rc = pe__name_and_nvpairs_xml(out, false, "node", 3, "name", node->details->uname, "id", node->details->id, "cached", pcmk__btoa(node->details->online)); CRM_ASSERT(rc == pcmk_rc_ok); } } pcmk__output_xml_pop_parent(out); return rc; } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_html(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pe_node_t *node = pe__current_node(rsc); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } CRM_ASSERT(rsc->variant == pe_native); if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_html(out, rsc, rsc_printable_id(rsc), node, show_opts); } PCMK__OUTPUT_ARGS("primitive", "uint32_t", "pe_resource_t *", "GList *", "GList *") int pe__resource_text(pcmk__output_t *out, va_list args) { uint32_t show_opts = va_arg(args, uint32_t); pe_resource_t *rsc = va_arg(args, pe_resource_t *); GList *only_node G_GNUC_UNUSED = va_arg(args, GList *); GList *only_rsc = va_arg(args, GList *); const pe_node_t *node = pe__current_node(rsc); CRM_ASSERT(rsc->variant == pe_native); if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { return pcmk_rc_no_output; } if (node == NULL) { // This is set only if a non-probe action is pending on this node node = rsc->pending_node; } return pe__common_output_text(out, rsc, rsc_printable_id(rsc), node, show_opts); } void native_free(pe_resource_t * rsc) { pe_rsc_trace(rsc, "Freeing resource action list (not the data)"); common_free(rsc); } enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current) { enum rsc_role_e role = rsc->next_role; if (current) { role = rsc->role; } pe_rsc_trace(rsc, "%s state: %s", rsc->id, role2text(role)); return role; } /*! * \internal * \brief List nodes where a resource (or any of its children) is * * \param[in] rsc Resource to check * \param[out] list List to add result to * \param[in] current 0 = where allocated, 1 = where running, * 2 = where running or pending * * \return If list contains only one node, that node, or NULL otherwise */ pe_node_t * native_location(const pe_resource_t *rsc, GList **list, int current) { pe_node_t *one = NULL; GList *result = NULL; if (rsc->children) { GList *gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *child = (pe_resource_t *) gIter->data; child->fns->location(child, &result, current); } } else if (current) { if (rsc->running_on) { result = g_list_copy(rsc->running_on); } if ((current == 2) && rsc->pending_node && !pe_find_node_id(result, rsc->pending_node->details->id)) { result = g_list_append(result, rsc->pending_node); } } else if (current == FALSE && rsc->allocated_to) { result = g_list_append(NULL, rsc->allocated_to); } if (result && (result->next == NULL)) { one = result->data; } if (list) { GList *gIter = result; for (; gIter != NULL; gIter = gIter->next) { pe_node_t *node = (pe_node_t *) gIter->data; if (*list == NULL || pe_find_node_id(*list, node->details->id) == NULL) { *list = g_list_append(*list, node); } } } g_list_free(result); return one; } static void get_rscs_brief(GList *rsc_list, GHashTable * rsc_table, GHashTable * active_table) { GList *gIter = rsc_list; for (; gIter != NULL; gIter = gIter->next) { pe_resource_t *rsc = (pe_resource_t *) gIter->data; const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); int offset = 0; char buffer[LINE_MAX]; int *rsc_counter = NULL; int *active_counter = NULL; if (rsc->variant != pe_native) { continue; } offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", class); if (pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); if (prov != NULL) { offset += snprintf(buffer + offset, LINE_MAX - offset, PROVIDER_SEP "%s", prov); } } offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s", kind); CRM_LOG_ASSERT(offset > 0); if (rsc_table) { rsc_counter = g_hash_table_lookup(rsc_table, buffer); if (rsc_counter == NULL) { rsc_counter = calloc(1, sizeof(int)); *rsc_counter = 0; g_hash_table_insert(rsc_table, strdup(buffer), rsc_counter); } (*rsc_counter)++; } if (active_table) { GList *gIter2 = rsc->running_on; for (; gIter2 != NULL; gIter2 = gIter2->next) { pe_node_t *node = (pe_node_t *) gIter2->data; GHashTable *node_table = NULL; if (node->details->unclean == FALSE && node->details->online == FALSE && pcmk_is_set(rsc->flags, pe_rsc_managed)) { continue; } node_table = g_hash_table_lookup(active_table, node->details->uname); if (node_table == NULL) { node_table = pcmk__strkey_table(free, free); g_hash_table_insert(active_table, strdup(node->details->uname), node_table); } active_counter = g_hash_table_lookup(node_table, buffer); if (active_counter == NULL) { active_counter = calloc(1, sizeof(int)); *active_counter = 0; g_hash_table_insert(node_table, strdup(buffer), active_counter); } (*active_counter)++; } } } } static void destroy_node_table(gpointer data) { GHashTable *node_table = data; if (node_table) { g_hash_table_destroy(node_table); } } /*! * \internal * \deprecated This function will be removed in a future release */ void print_rscs_brief(GList *rsc_list, const char *pre_text, long options, void *print_data, gboolean print_all) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GHashTableIter hash_iter; char *type = NULL; int *rsc_counter = NULL; get_rscs_brief(rsc_list, rsc_table, active_table); g_hash_table_iter_init(&hash_iter, rsc_table); while (g_hash_table_iter_next(&hash_iter, (gpointer *)&type, (gpointer *)&rsc_counter)) { GHashTableIter hash_iter2; char *node_name = NULL; GHashTable *node_table = NULL; int active_counter_all = 0; g_hash_table_iter_init(&hash_iter2, active_table); while (g_hash_table_iter_next(&hash_iter2, (gpointer *)&node_name, (gpointer *)&node_table)) { int *active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (options & pe_print_rsconly) { node_name = NULL; } if (options & pe_print_html) { status_print("
  • \n"); } if (print_all) { status_print("%s%d/%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, rsc_counter ? *rsc_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } else { status_print("%s%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", active_counter ? *active_counter : 0, type, active_counter && (*active_counter > 0) && node_name ? node_name : ""); } if (options & pe_print_html) { status_print("
  • \n"); } } if (print_all && active_counter_all == 0) { if (options & pe_print_html) { status_print("
  • \n"); } status_print("%s%d/%d\t(%s):\tActive\n", pre_text ? pre_text : "", active_counter_all, rsc_counter ? *rsc_counter : 0, type); if (options & pe_print_html) { status_print("
  • \n"); } } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } } int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, uint32_t show_opts) { GHashTable *rsc_table = pcmk__strkey_table(free, free); GHashTable *active_table = pcmk__strkey_table(free, destroy_node_table); GList *sorted_rscs; int rc = pcmk_rc_no_output; get_rscs_brief(rsc_list, rsc_table, active_table); /* Make a list of the rsc_table keys so that it can be sorted. This is to make sure * output order stays consistent between systems. */ sorted_rscs = g_hash_table_get_keys(rsc_table); sorted_rscs = g_list_sort(sorted_rscs, (GCompareFunc) strcmp); for (GList *gIter = sorted_rscs; gIter; gIter = gIter->next) { char *type = (char *) gIter->data; int *rsc_counter = g_hash_table_lookup(rsc_table, type); GList *sorted_nodes = NULL; int active_counter_all = 0; /* Also make a list of the active_table keys so it can be sorted. If there's * more than one instance of a type of resource running, we need the nodes to * be sorted to make sure output order stays consistent between systems. */ sorted_nodes = g_hash_table_get_keys(active_table); sorted_nodes = g_list_sort(sorted_nodes, (GCompareFunc) pcmk__numeric_strcasecmp); for (GList *gIter2 = sorted_nodes; gIter2; gIter2 = gIter2->next) { char *node_name = (char *) gIter2->data; GHashTable *node_table = g_hash_table_lookup(active_table, node_name); int *active_counter = NULL; if (node_table == NULL) { continue; } active_counter = g_hash_table_lookup(node_table, type); if (active_counter == NULL || *active_counter == 0) { continue; } else { active_counter_all += *active_counter; } if (pcmk_is_set(show_opts, pcmk_show_rsc_only)) { node_name = NULL; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive %s", *active_counter, rsc_counter ? *rsc_counter : 0, type, (*active_counter > 0) && node_name ? node_name : ""); } else { out->list_item(out, NULL, "%d\t(%s):\tActive %s", *active_counter, type, (*active_counter > 0) && node_name ? node_name : ""); } rc = pcmk_rc_ok; } if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs) && active_counter_all == 0) { out->list_item(out, NULL, "%d/%d\t(%s):\tActive", active_counter_all, rsc_counter ? *rsc_counter : 0, type); rc = pcmk_rc_ok; } if (sorted_nodes) { g_list_free(sorted_nodes); } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } if (sorted_rscs) { g_list_free(sorted_rscs); } return rc; } gboolean pe__native_is_filtered(const pe_resource_t *rsc, GList *only_rsc, gboolean check_parent) { if (pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, pcmk__str_star_matches) || pcmk__str_in_list(rsc->id, only_rsc, pcmk__str_star_matches)) { return FALSE; } else if (check_parent && rsc->parent) { const pe_resource_t *up = pe__const_top_resource(rsc, true); return up->fns->is_filtered(up, only_rsc, FALSE); } return TRUE; } + +/*! + * \brief Get maximum primitive resource instances per node + * + * \param[in] rsc Primitive resource to check + * + * \return Maximum number of \p rsc instances that can be active on one node + */ +unsigned int +pe__primitive_max_per_node(const pe_resource_t *rsc) +{ + CRM_ASSERT((rsc != NULL) && (rsc->variant == pe_native)); + return 1U; +} diff --git a/lib/pengine/pe_status_private.h b/lib/pengine/pe_status_private.h index ae8d131f6f..e0acc8f74e 100644 --- a/lib/pengine/pe_status_private.h +++ b/lib/pengine/pe_status_private.h @@ -1,121 +1,141 @@ /* * Copyright 2018-2023 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_STATUS_PRIVATE__H # define PE_STATUS_PRIVATE__H /* This header is for the sole use of libpe_status, so that functions can be * declared with G_GNUC_INTERNAL for efficiency. */ #if defined(PCMK__UNIT_TESTING) #undef G_GNUC_INTERNAL #define G_GNUC_INTERNAL #endif /*! * \internal * \deprecated This macro will be removed in a future release */ # define status_print(fmt, args...) \ if(options & pe_print_html) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_printf || options & pe_print_ncurses) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_xml) { \ FILE *stream = print_data; \ fprintf(stream, fmt, ##args); \ } else if(options & pe_print_log) { \ int log_level = *(int*)print_data; \ do_crm_log(log_level, fmt, ##args); \ } 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; G_GNUC_INTERNAL pe_resource_t *pe__create_clone_child(pe_resource_t *rsc, pe_working_set_t *data_set); G_GNUC_INTERNAL void pe__create_action_notifications(pe_resource_t *rsc, notify_data_t *n_data); G_GNUC_INTERNAL void pe__free_action_notification_data(notify_data_t *n_data); G_GNUC_INTERNAL notify_data_t *pe__action_notif_pseudo_ops(pe_resource_t *rsc, const char *task, pe_action_t *action, pe_action_t *complete); G_GNUC_INTERNAL void pe__force_anon(const char *standard, pe_resource_t *rsc, const char *rid, pe_working_set_t *data_set); G_GNUC_INTERNAL gint pe__cmp_rsc_priority(gconstpointer a, gconstpointer b); G_GNUC_INTERNAL gboolean pe__unpack_resource(xmlNode *xml_obj, pe_resource_t **rsc, pe_resource_t *parent, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_remote_nodes(xmlNode *xml_resources, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_resources(const xmlNode *xml_resources, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_config(xmlNode *config, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_nodes(xmlNode *xml_nodes, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_tags(xmlNode *xml_tags, pe_working_set_t *data_set); G_GNUC_INTERNAL gboolean unpack_status(xmlNode *status, pe_working_set_t *data_set); G_GNUC_INTERNAL op_digest_cache_t *pe__compare_fencing_digest(pe_resource_t *rsc, const char *agent, pe_node_t *node, pe_working_set_t *data_set); G_GNUC_INTERNAL void pe__unpack_node_health_scores(pe_working_set_t *data_set); +// Primitive resource methods + +G_GNUC_INTERNAL +unsigned int pe__primitive_max_per_node(const pe_resource_t *rsc); + +// Group resource methods + +G_GNUC_INTERNAL +unsigned int pe__group_max_per_node(const pe_resource_t *rsc); + +// Clone resource methods + +G_GNUC_INTERNAL +unsigned int pe__clone_max_per_node(const pe_resource_t *rsc); + +// Bundle resource methods + G_GNUC_INTERNAL pe_node_t *pe__bundle_active_node(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); +G_GNUC_INTERNAL +unsigned int pe__bundle_max_per_node(const pe_resource_t *rsc); + #endif // PE_STATUS_PRIVATE__H