diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index 919de20bc8..75768a9e40 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -1,401 +1,398 @@ /* * Copyright 2004-2019 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 # 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...) { was_processing_error = TRUE; crm_config_error = TRUE; crm_err(fmt); } # define pe_warn(fmt...) { was_processing_warning = TRUE; crm_config_warning = TRUE; crm_warn(fmt); } # 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_action_bit(action, bit) action->flags = crm_set_bit(__FUNCTION__, __LINE__, action->uuid, action->flags, bit) # define pe_clear_action_bit(action, bit) action->flags = crm_clear_bit(__FUNCTION__, __LINE__, action->uuid, action->flags, bit) 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 GListPtr node_list_rh; // List of pe_node_t* } pe__location_t; typedef struct pe__order_constraint_s { int id; enum pe_ordering type; void *lh_opaque; resource_t *lh_rsc; action_t *lh_action; char *lh_action_task; void *rh_opaque; resource_t *rh_rsc; action_t *rh_action; char *rh_action_task; } pe__ordering_t; typedef struct notify_data_s { GSList *keys; // Environment variable name/value pairs const char *action; action_t *pre; action_t *post; action_t *pre_done; action_t *post_done; GListPtr active; /* notify_entry_t* */ GListPtr inactive; /* notify_entry_t* */ GListPtr start; /* notify_entry_t* */ GListPtr stop; /* notify_entry_t* */ GListPtr demote; /* notify_entry_t* */ GListPtr promote; /* notify_entry_t* */ GListPtr master; /* notify_entry_t* */ GListPtr slave; /* notify_entry_t* */ GHashTable *allowed_nodes; } notify_data_t; bool pe_can_fence(pe_working_set_t *data_set, node_t *node); int merge_weights(int w1, int w2); void add_hash_param(GHashTable * hash, const char *name, const char *value); char *native_parameter(resource_t * rsc, 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(void); void verify_pe_options(GHashTable * options); void common_update_score(resource_t * rsc, const char *id, int score); void native_add_running(resource_t * rsc, node_t * node, pe_working_set_t * data_set); gboolean native_unpack(resource_t * rsc, pe_working_set_t * data_set); gboolean group_unpack(resource_t * rsc, pe_working_set_t * data_set); gboolean clone_unpack(resource_t * rsc, pe_working_set_t * data_set); gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set); resource_t *native_find_rsc(resource_t *rsc, const char *id, const node_t *node, int flags); gboolean native_active(resource_t * rsc, gboolean all); gboolean group_active(resource_t * rsc, gboolean all); gboolean clone_active(resource_t * rsc, gboolean all); gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all); void native_print(resource_t * rsc, const char *pre_text, long options, void *print_data); void group_print(resource_t * rsc, const char *pre_text, long options, void *print_data); void clone_print(resource_t * rsc, const char *pre_text, long options, void *print_data); void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options, void *print_data); int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name , size_t pairs_count, ...); int pe__clone_xml(pcmk__output_t *out, va_list args); int pe__clone_html(pcmk__output_t *out, va_list args); int pe__clone_text(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__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(resource_t * rsc); void group_free(resource_t * rsc); void clone_free(resource_t * rsc); void pe__free_bundle(pe_resource_t *rsc); enum rsc_role_e native_resource_state(const resource_t * rsc, gboolean current); enum rsc_role_e group_resource_state(const resource_t * rsc, gboolean current); enum rsc_role_e clone_resource_state(const resource_t * rsc, gboolean current); enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc, gboolean current); gboolean common_unpack(xmlNode * xml_obj, resource_t ** rsc, resource_t * parent, pe_working_set_t * data_set); void common_free(resource_t * rsc); extern node_t *node_copy(const 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 = 0x00, pe_fc_effective = 0x01, // don't count expired failures pe_fc_fillers = 0x02, // if container, include filler failures in count }; int pe_get_failcount(node_t *node, resource_t *rsc, time_t *last_failure, uint32_t flags, xmlNode *xml_op, pe_working_set_t *data_set); pe_action_t *pe__clear_failcount(pe_resource_t *rsc, pe_node_t *node, const char *reason, pe_working_set_t *data_set); /* Functions for finding/counting a resource's active nodes */ pe_node_t *pe__find_active_on(const pe_resource_t *rsc, unsigned int *count_all, unsigned int *count_clean); pe_node_t *pe__find_active_requires(const pe_resource_t *rsc, unsigned int *count); static inline pe_node_t * pe__current_node(const pe_resource_t *rsc) { return pe__find_active_on(rsc, NULL, NULL); } /* Binary like operators for lists of nodes */ extern void node_list_exclude(GHashTable * list, GListPtr list2, gboolean merge_scores); extern GListPtr node_list_dup(GListPtr list, gboolean reset, gboolean filter); extern GHashTable *node_hash_from_list(GListPtr list); static inline gpointer pe_hash_table_lookup(GHashTable * hash, gconstpointer key) { if (hash) { return g_hash_table_lookup(hash, key); } return NULL; } extern action_t *get_pseudo_op(const char *name, pe_working_set_t * data_set); extern gboolean order_actions(action_t * lh_action, action_t * rh_action, enum pe_ordering order); GHashTable *node_hash_dup(GHashTable * hash); /* Printing functions for debug */ extern void print_node(const char *pre_text, node_t * node, gboolean details); extern void print_str_str(gpointer key, gpointer value, gpointer user_data); extern void pe__output_node(node_t * node, gboolean details, pcmk__output_t *out); extern void print_resource(int log_level, const char *pre_text, resource_t * rsc, gboolean details); extern void pe__output_resource(int log_level, resource_t * rsc, gboolean details, pcmk__output_t *out); extern void dump_node_scores_worker(int level, const char *file, const char *function, int line, resource_t * rsc, const char *comment, GHashTable * nodes); extern void dump_node_capacity(int level, const char *comment, node_t * node); extern void dump_rsc_utilization(int level, const char *comment, resource_t * rsc, node_t * node); # define dump_node_scores(level, rsc, text, nodes) do { \ dump_node_scores_worker(level, __FILE__, __FUNCTION__, __LINE__, rsc, text, nodes); \ } while(0) /* Sorting functions */ extern gint sort_rsc_priority(gconstpointer a, gconstpointer b); extern gint sort_rsc_index(gconstpointer a, gconstpointer b); extern xmlNode *find_rsc_op_entry(resource_t * rsc, const char *key); extern action_t *custom_action(resource_t * rsc, char *key, const char *task, node_t * on_node, gboolean optional, gboolean foo, pe_working_set_t * data_set); # define delete_key(rsc) generate_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, data_set); # define stopped_key(rsc) generate_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, data_set); # define stop_key(rsc) generate_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, data_set); # define reload_key(rsc) generate_op_key(rsc->id, CRMD_ACTION_RELOAD, 0) # define start_key(rsc) generate_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, data_set) # define started_key(rsc) generate_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, data_set) # define promote_key(rsc) generate_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, data_set) # define promoted_key(rsc) generate_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, data_set) # define demote_key(rsc) generate_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, data_set) # define demoted_key(rsc) generate_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, data_set) extern int pe_get_configured_timeout(resource_t *rsc, const char *action, pe_working_set_t *data_set); extern action_t *find_first_action(GListPtr input, const char *uuid, const char *task, node_t * on_node); extern enum action_tasks get_complex_task(resource_t * rsc, const char *name, gboolean allow_non_atomic); extern GListPtr find_actions(GListPtr input, const char *key, const node_t *on_node); GList *find_actions_exact(GList *input, const char *key, const pe_node_t *on_node); extern GListPtr find_recurring_actions(GListPtr input, node_t * not_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(action_t * action); extern void resource_location(resource_t * rsc, node_t * node, int score, const char *tag, pe_working_set_t * data_set); extern gint sort_op_by_callid(gconstpointer a, gconstpointer b); extern gboolean get_target_role(resource_t * rsc, enum rsc_role_e *role); extern resource_t *find_clone_instance(resource_t * rsc, const char *sub_id, pe_working_set_t * data_set); extern void destroy_ticket(gpointer data); extern 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(resource_t *rsc, const char *id) { if (id && rsc && rsc->id) { // Number of characters in rsc->id before any clone suffix size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1; return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len); } return FALSE; } int pe__target_rc_from_xml(xmlNode *xml_op); gint sort_node_uname(gconstpointer a, gconstpointer b); bool is_set_recursive(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 *rsc_action_digest_cmp(resource_t * rsc, xmlNode * xml_op, node_t * node, pe_working_set_t * data_set); action_t *pe_fence_op(node_t * node, const char *op, bool optional, const char *reason, pe_working_set_t * data_set); void trigger_unfencing( resource_t * rsc, node_t *node, const char *reason, action_t *dependency, pe_working_set_t * data_set); void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite); void pe_action_set_flag_reason(const char *function, long line, pe_action_t *action, pe_action_t *reason, const char *text, enum pe_action_flags flags, bool overwrite); #define pe_action_required(action, reason, text) pe_action_set_flag_reason(__FUNCTION__, __LINE__, action, reason, text, pe_action_optional, FALSE) #define pe_action_implies(action, reason, flag) pe_action_set_flag_reason(__FUNCTION__, __LINE__, action, reason, NULL, flag, FALSE) void set_bit_recursive(resource_t * rsc, unsigned long long flag); void clear_bit_recursive(resource_t * rsc, unsigned long long flag); gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref); void print_rscs_brief(GListPtr rsc_list, const char * pre_text, long options, void * print_data, gboolean print_all); -void pe__rscs_brief_output_text(pcmk__output_t *out, GListPtr rsc_list, const char *pre_text, - long options, gboolean print_all); -void pe__rscs_brief_output_html(pcmk__output_t *out, GListPtr rsc_list, - long options, gboolean print_all); +void pe__rscs_brief_output(pcmk__output_t *out, GListPtr rsc_list, long options, gboolean print_all); void pe_fence_node(pe_working_set_t * data_set, node_t * node, const char *reason); node_t *pe_create_node(const char *id, const char *uname, const char *type, const char *score, pe_working_set_t * data_set); bool remote_id_conflict(const char *remote_name, pe_working_set_t *data); void common_print(resource_t * rsc, const char *pre_text, const char *name, node_t *node, long options, void *print_data); -void pe__common_output_text(pcmk__output_t *out, resource_t * rsc, const char *pre_text, const char *name, node_t *node, long options); +void pe__common_output_text(pcmk__output_t *out, resource_t * rsc, const char *name, node_t *node, long options); void pe__common_output_html(pcmk__output_t *out, resource_t * rsc, const char *name, node_t *node, long options); pe_resource_t *pe__find_bundle_replica(const pe_resource_t *bundle, const pe_node_t *node); bool pe__bundle_needs_remote_name(pe_resource_t *rsc); const char *pe__add_bundle_remote_name(pe_resource_t *rsc, xmlNode *xml, const char *field); const char *pe_node_attribute_calculated(const pe_node_t *node, const char *name, const resource_t *rsc); const char *pe_node_attribute_raw(pe_node_t *node, const char *name); bool pe__is_universal_clone(pe_resource_t *rsc, pe_working_set_t *data_set); void pe__add_param_check(xmlNode *rsc_op, pe_resource_t *rsc, pe_node_t *node, enum pe_check_parameters, pe_working_set_t *data_set); void pe__foreach_param_check(pe_working_set_t *data_set, void (*cb)(pe_resource_t*, pe_node_t*, xmlNode*, enum pe_check_parameters, pe_working_set_t*)); void pe__free_param_checks(pe_working_set_t *data_set); bool pe__shutdown_requested(pe_node_t *node); void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set); #define BOOL2STR(x) ((x) ? "true" : "false") /*! * \internal * \brief Register xml formatting message functions. */ void pe__register_messages(pcmk__output_t *out); void pe__unpack_dataset_nvpairs(xmlNode *xml_obj, const char *set_name, GHashTable *node_hash, GHashTable *hash, const char *always_first, gboolean overwrite, pe_working_set_t *data_set); #endif diff --git a/lib/common/output_html.c b/lib/common/output_html.c index 870080ac18..ee72aea054 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -1,366 +1,392 @@ /* * Copyright 2019 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 _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include static const char *stylesheet_default = ".bold { font-weight: bold }\n" + ".maint { color: blue }\n" + ".offline { color: red }\n" + ".online { color: green }\n" + ".rsc-failed { color: red }\n" + ".rsc-failure-ignored { color: yellow }\n" + ".rsc-managed { color: yellow }\n" + ".rsc-multiple { color: orange }\n" + ".rsc-ok { color: green }\n" + ".standby { color: orange }\n" ".warning { color: red, font-weight: bold }"; static gboolean cgi_output = FALSE; static int meta_refresh = 0; static char *stylesheet_link = NULL; static char *title = NULL; GOptionEntry pcmk__html_output_entries[] = { { "output-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output, "Add text needed to use output in a CGI program", NULL }, { "output-meta-refresh", 0, 0, G_OPTION_ARG_INT, &meta_refresh, "How often to refresh", "SECONDS" }, { "output-stylesheet-link", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link, "Link to an external CSS stylesheet", "URI" }, { "output-title", 0, 0, G_OPTION_ARG_STRING, &title, - "Page title (defaults to command line)", + "Page title", "TITLE" }, { NULL } }; typedef struct private_data_s { xmlNode *root; GQueue *parent_q; GSList *errors; } private_data_t; static void html_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } xmlFreeNode(priv->root); g_queue_free(priv->parent_q); g_slist_free(priv->errors); free(priv); } static bool html_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If html_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); priv->root = create_xml_node(NULL, "html"); xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL); xmlSetProp(priv->root, (pcmkXmlStr) "lang", (pcmkXmlStr) "en"); g_queue_push_tail(priv->parent_q, priv->root); priv->errors = NULL; pcmk__output_xml_create_parent(out, "body"); return true; } static void add_error_node(gpointer data, gpointer user_data) { char *str = (char *) data; pcmk__output_t *out = (pcmk__output_t *) user_data; out->list_item(out, NULL, "%s", str); } static void html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { private_data_t *priv = out->priv; htmlNodePtr head_node = NULL; htmlNodePtr charset_node = NULL; /* If root is NULL, html_init failed and we are being called from pcmk__output_free * in the pcmk__output_new path. */ if (priv == NULL || priv->root == NULL) { return; } if (cgi_output && print) { fprintf(out->dest, "Content-Type: text/html\n\n"); } /* Add the head node last - it's not needed earlier because it doesn't contain * anything else that the user could add, and we want it done last to pick up * any options that may have been given. */ head_node = xmlNewNode(NULL, (pcmkXmlStr) "head"); if (title != NULL ) { pcmk_create_xml_text_node(head_node, "title", title); } else if (out->request != NULL) { pcmk_create_xml_text_node(head_node, "title", out->request); } charset_node = create_xml_node(head_node, "meta"); xmlSetProp(charset_node, (pcmkXmlStr) "charset", (pcmkXmlStr) "utf-8"); if (meta_refresh != 0) { htmlNodePtr refresh_node = create_xml_node(head_node, "meta"); xmlSetProp(refresh_node, (pcmkXmlStr) "http-equiv", (pcmkXmlStr) "refresh"); xmlSetProp(refresh_node, (pcmkXmlStr) "content", (pcmkXmlStr) crm_itoa(meta_refresh)); } /* Stylesheets are included two different ways. The first is via a built-in * default (see the stylesheet_default const above). The second is via the * "stylesheet-link" option, and this should obviously be a link to a * stylesheet. The second can override the first. At least one should be * given. */ pcmk_create_xml_text_node(head_node, "style", stylesheet_default); if (stylesheet_link != NULL) { htmlNodePtr link_node = create_xml_node(head_node, "link"); xmlSetProp(link_node, (pcmkXmlStr) "rel", (pcmkXmlStr) "stylesheet"); xmlSetProp(link_node, (pcmkXmlStr) "href", (pcmkXmlStr) stylesheet_link); } xmlAddPrevSibling(priv->root->children, head_node); if (g_slist_length(priv->errors) > 0) { out->begin_list(out, "Errors", NULL, NULL); g_slist_foreach(priv->errors, add_error_node, (gpointer) out); out->end_list(out); } if (print) { htmlDocDump(out->dest, priv->root->doc); } if (copy_dest != NULL) { *copy_dest = copy_xml(priv->root); } } static void html_reset(pcmk__output_t *out) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); htmlDocDump(out->dest, priv->root->doc); html_free_priv(out); html_init(out); } static void html_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { char *rc_buf = NULL; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); rc_buf = crm_strdup_printf("Return code: %d", exit_status); pcmk__output_create_xml_text_node(out, "h2", "Command Output"); pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf); if (proc_stdout != NULL) { pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout"); pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout); } if (proc_stderr != NULL) { pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr"); pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr); } free(rc_buf); } static void html_version(pcmk__output_t *out, bool extended) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); pcmk__output_create_xml_text_node(out, "h2", "Version Information"); pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker"); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION)); pcmk__output_create_html_node(out, "div", NULL, NULL, "Author: Andrew Beekhof"); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION)); pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES)); } G_GNUC_PRINTF(2, 3) static void html_err(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = out->priv; int len = 0; char *buf = NULL; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); priv->errors = g_slist_append(priv->errors, buf); } G_GNUC_PRINTF(2, 3) static void html_info(pcmk__output_t *out, const char *format, ...) { /* This function intentially left blank */ } static void html_output_xml(pcmk__output_t *out, const char *name, const char *buf) { htmlNodePtr node = NULL; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf); xmlSetProp(node, (pcmkXmlStr) "lang", (pcmkXmlStr) "xml"); } G_GNUC_PRINTF(4, 5) static void html_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = out->priv; + xmlNodePtr node = NULL; CRM_ASSERT(priv != NULL); + /* If we are already in a list (the queue depth is always at least + * one because of the element), first create a
  • element + * to hold the

    and the new list. + */ + if (g_queue_get_length(priv->parent_q) > 2) { + pcmk__output_xml_create_parent(out, "li"); + } + if (format != NULL) { va_list ap; char *buf = NULL; int len; va_start(ap, format); len = vasprintf(&buf, format, ap); va_end(ap); CRM_ASSERT(len >= 0); pcmk__output_create_xml_text_node(out, "h2", buf); free(buf); } - pcmk__output_xml_create_parent(out, "ul"); + node = pcmk__output_xml_create_parent(out, "ul"); + g_queue_push_tail(priv->parent_q, node); } G_GNUC_PRINTF(3, 4) static void html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { private_data_t *priv = out->priv; htmlNodePtr item_node = NULL; va_list ap; char *buf = NULL; int len; CRM_ASSERT(priv != NULL); va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len >= 0); va_end(ap); item_node = pcmk__output_create_xml_text_node(out, "li", buf); free(buf); if (name != NULL) { xmlSetProp(item_node, (pcmkXmlStr) "class", (pcmkXmlStr) name); } } static void html_end_list(pcmk__output_t *out) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); + /* Remove the
      tag. */ g_queue_pop_tail(priv->parent_q); + pcmk__output_xml_pop_parent(out); + + /* Remove the
    • created for nested lists. */ + if (g_queue_get_length(priv->parent_q) > 2) { + pcmk__output_xml_pop_parent(out); + } } pcmk__output_t * pcmk__mk_html_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "html"; retval->request = g_strjoinv(" ", argv); retval->supports_quiet = false; retval->init = html_init; retval->free_priv = html_free_priv; retval->finish = html_finish; retval->reset = html_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = html_subprocess_output; retval->version = html_version; retval->info = html_info; retval->err = html_err; retval->output_xml = html_output_xml; retval->begin_list = html_begin_list; retval->list_item = html_list_item; retval->end_list = html_end_list; return retval; } xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text) { htmlNodePtr node = pcmk__output_create_xml_text_node(out, element_name, text); if (class_name != NULL) { xmlSetProp(node, (pcmkXmlStr) "class", (pcmkXmlStr) class_name); } if (id != NULL) { xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) id); } return node; } diff --git a/lib/common/output_text.c b/lib/common/output_text.c index 58c5fcc732..c91540b73d 100644 --- a/lib/common/output_text.c +++ b/lib/common/output_text.c @@ -1,293 +1,293 @@ /* * Copyright 2019 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 static gboolean fancy = FALSE; GOptionEntry pcmk__text_output_entries[] = { { "output-fancy", 0, 0, G_OPTION_ARG_NONE, &fancy, "Use more highly formatted output", NULL }, { NULL } }; typedef struct text_list_data_s { unsigned int len; char *singular_noun; char *plural_noun; } text_list_data_t; typedef struct private_data_s { GQueue *parent_q; } private_data_t; static void text_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->parent_q); free(priv); } static bool text_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If text_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); return true; } static void text_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { /* This function intentionally left blank */ } static void text_reset(pcmk__output_t *out) { CRM_ASSERT(out->priv != NULL); text_free_priv(out); text_init(out); } static void text_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { if (proc_stdout != NULL) { fprintf(out->dest, "%s\n", proc_stdout); } if (proc_stderr != NULL) { fprintf(out->dest, "%s\n", proc_stderr); } } static void text_version(pcmk__output_t *out, bool extended) { if (extended) { fprintf(out->dest, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { fprintf(out->dest, "Pacemaker %s\n", PACEMAKER_VERSION); fprintf(out->dest, "Written by Andrew Beekhof\n"); } } G_GNUC_PRINTF(2, 3) static void text_err(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(stderr, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(stderr, "\n"); } G_GNUC_PRINTF(2, 3) static void text_info(pcmk__output_t *out, const char *format, ...) { va_list ap; int len = 0; va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vfprintf(out->dest, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Add a newline. */ fprintf(out->dest, "\n"); } static void text_output_xml(pcmk__output_t *out, const char *name, const char *buf) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); pcmk__indented_printf(out, "%s", buf); } G_GNUC_PRINTF(4, 5) static void text_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = out->priv; text_list_data_t *new_list = NULL; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); - if (fancy) { + if (fancy && format) { pcmk__indented_vprintf(out, format, ap); fprintf(out->dest, ":\n"); } va_end(ap); new_list = calloc(1, sizeof(text_list_data_t)); new_list->len = 0; new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun); new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun); g_queue_push_tail(priv->parent_q, new_list); } G_GNUC_PRINTF(3, 4) static void text_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { private_data_t *priv = out->priv; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); if (fancy) { if (id != NULL) { /* Not really a good way to do this all in one call, so make it two. * The first handles the indentation and list styling. The second * just prints right after that one. */ pcmk__indented_printf(out, "%s: ", id); vfprintf(out->dest, format, ap); } else { pcmk__indented_vprintf(out, format, ap); } } else { pcmk__indented_vprintf(out, format, ap); } fputc('\n', out->dest); va_end(ap); ((text_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++; } static void text_end_list(pcmk__output_t *out) { private_data_t *priv = out->priv; text_list_data_t *node = NULL; CRM_ASSERT(priv != NULL); node = g_queue_pop_tail(priv->parent_q); if (node->singular_noun != NULL && node->plural_noun != NULL) { if (node->len == 1) { pcmk__indented_printf(out, "%d %s found\n", node->len, node->singular_noun); } else { pcmk__indented_printf(out, "%d %s found\n", node->len, node->plural_noun); } } free(node); } pcmk__output_t * pcmk__mk_text_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "text"; retval->request = g_strjoinv(" ", argv); retval->supports_quiet = true; retval->init = text_init; retval->free_priv = text_free_priv; retval->finish = text_finish; retval->reset = text_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = text_subprocess_output; retval->version = text_version; retval->info = text_info; retval->err = text_err; retval->output_xml = text_output_xml; retval->begin_list = text_begin_list; retval->list_item = text_list_item; retval->end_list = text_end_list; return retval; } G_GNUC_PRINTF(2, 0) void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { int len = 0; if (fancy) { int level = 0; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); level = g_queue_get_length(priv->parent_q); for (int i = 0; i < level; i++) { - putc('\t', out->dest); + fprintf(out->dest, " "); } if (level > 0) { fprintf(out->dest, "* "); } } len = vfprintf(out->dest, format, args); CRM_ASSERT(len >= 0); } G_GNUC_PRINTF(2, 3) void pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; va_start(ap, format); pcmk__indented_vprintf(out, format, ap); va_end(ap); } diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c index 4eb9c1b2cd..239ba06897 100644 --- a/lib/fencing/st_output.c +++ b/lib/fencing/st_output.c @@ -1,286 +1,294 @@ /* * Copyright 2019 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 static char * time_t_string(time_t when) { crm_time_t *crm_when = crm_time_new(NULL); char *buf = NULL; crm_time_set_timet(crm_when, &when); buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_free(crm_when); return buf; } static int last_fenced_html(pcmk__output_t *out, va_list args) { const char *target = va_arg(args, const char *); time_t when = va_arg(args, time_t); if (when) { char *buf = crm_strdup_printf("Node %s last fenced at: %s", target, ctime(&when)); pcmk__output_create_html_node(out, "div", NULL, NULL, buf); free(buf); } return 0; } static int last_fenced_text(pcmk__output_t *out, va_list args) { const char *target = va_arg(args, const char *); time_t when = va_arg(args, time_t); if (when) { pcmk__indented_printf(out, "Node %s last fenced at: %s", target, ctime(&when)); } else { pcmk__indented_printf(out, "Node %s has never been fenced\n", target); } return 0; } static int last_fenced_xml(pcmk__output_t *out, va_list args) { const char *target = va_arg(args, const char *); time_t when = va_arg(args, time_t); if (when) { xmlNodePtr node = pcmk__output_create_xml_node(out, "last-fenced"); char *buf = time_t_string(when); xmlSetProp(node, (pcmkXmlStr) "target", (pcmkXmlStr) target); xmlSetProp(node, (pcmkXmlStr) "when", (pcmkXmlStr) buf); free(buf); } return 0; } static int stonith_event_html(pcmk__output_t *out, va_list args) { stonith_history_t *event = va_arg(args, stonith_history_t *); int full_history = va_arg(args, int); gboolean later_succeeded = va_arg(args, gboolean); switch(event->state) { - case st_done: + case st_done: { + char *completed_s = time_t_string(event->completed); + out->list_item(out, "successful-stonith-event", "%s of %s successful: delegate=%s, client=%s, origin=%s, %s='%s'", stonith_action_str(event->action), event->target, event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-successful", - time_t_string(event->completed)); + completed_s); + free(completed_s); break; + } + + case st_failed: { + char *failed_s = time_t_string(event->completed); - case st_failed: out->list_item(out, "failed-stonith-event", "%s of %s failed : delegate=%s, client=%s, origin=%s, %s='%s' %s", stonith_action_str(event->action), event->target, event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-failed", - time_t_string(event->completed), + failed_s, later_succeeded ? "(a later attempt succeeded)" : ""); + free(failed_s); break; + } default: out->list_item(out, "pending-stonith-event", "%s of %s pending: client=%s, origin=%s", stonith_action_str(event->action), event->target, event->client, event->origin); break; } return 0; } static int stonith_event_text(pcmk__output_t *out, va_list args) { stonith_history_t *event = va_arg(args, stonith_history_t *); int full_history = va_arg(args, int); gboolean later_succeeded = va_arg(args, gboolean); char *buf = time_t_string(event->completed); switch (event->state) { case st_failed: pcmk__indented_printf(out, "%s of %s failed: delegate=%s, client=%s, origin=%s, %s='%s' %s\n", stonith_action_str(event->action), event->target, event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-failed", buf, later_succeeded ? "(a later attempt succeeded)" : ""); break; case st_done: pcmk__indented_printf(out, "%s of %s successful: delegate=%s, client=%s, origin=%s, %s='%s'\n", stonith_action_str(event->action), event->target, event->delegate ? event->delegate : "", event->client, event->origin, full_history ? "completed" : "last-successful", buf); break; default: pcmk__indented_printf(out, "%s of %s pending: client=%s, origin=%s\n", stonith_action_str(event->action), event->target, event->client, event->origin); break; } free(buf); return 0; } static int stonith_event_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "fence_event"); stonith_history_t *event = va_arg(args, stonith_history_t *); char *buf = NULL; switch (event->state) { case st_failed: xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) "failed"); break; case st_done: xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) "success"); break; default: { char *state = crm_itoa(event->state); xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) "pending"); xmlSetProp(node, (pcmkXmlStr) "extended-status", (pcmkXmlStr) state); free(state); break; } } if (event->delegate != NULL) { xmlSetProp(node, (pcmkXmlStr) "delegate", (pcmkXmlStr) event->delegate); } xmlSetProp(node, (pcmkXmlStr) "action", (pcmkXmlStr) event->action); xmlSetProp(node, (pcmkXmlStr) "target", (pcmkXmlStr) event->target); xmlSetProp(node, (pcmkXmlStr) "client", (pcmkXmlStr) event->client); xmlSetProp(node, (pcmkXmlStr) "origin", (pcmkXmlStr) event->origin); if (event->state == st_failed || event->state == st_done) { buf = time_t_string(event->completed); xmlSetProp(node, (pcmkXmlStr) "completed", (pcmkXmlStr) buf); free(buf); } return 0; } static int validate_agent_html(pcmk__output_t *out, va_list args) { const char *agent = va_arg(args, const char *); const char *device = va_arg(args, const char *); const char *output = va_arg(args, const char *); const char *error_output = va_arg(args, const char *); int rc = va_arg(args, int); if (device) { char *buf = crm_strdup_printf("Validation of %s on %s %s", agent, device, rc ? "failed" : "succeeded"); pcmk__output_create_html_node(out, "div", NULL, NULL, buf); free(buf); } else { char *buf = crm_strdup_printf("Validation of %s %s", agent, rc ? "failed" : "succeeded"); pcmk__output_create_html_node(out, "div", NULL, NULL, buf); free(buf); } out->subprocess_output(out, rc, output, error_output); return rc; } static int validate_agent_text(pcmk__output_t *out, va_list args) { const char *agent = va_arg(args, const char *); const char *device = va_arg(args, const char *); const char *output = va_arg(args, const char *); const char *error_output = va_arg(args, const char *); int rc = va_arg(args, int); if (device) { pcmk__indented_printf(out, "Validation of %s on %s %s\n", agent, device, rc ? "failed" : "succeeded"); } else { pcmk__indented_printf(out, "Validation of %s %s\n", agent, rc ? "failed" : "succeeded"); } if (output) { puts(output); } if (error_output) { puts(error_output); } return rc; } static int validate_agent_xml(pcmk__output_t *out, va_list args) { xmlNodePtr node = pcmk__output_create_xml_node(out, "validate"); const char *agent = va_arg(args, const char *); const char *device = va_arg(args, const char *); const char *output = va_arg(args, const char *); const char *error_output = va_arg(args, const char *); int rc = va_arg(args, int); xmlSetProp(node, (pcmkXmlStr) "agent", (pcmkXmlStr) agent); if (device != NULL) { xmlSetProp(node, (pcmkXmlStr) "device", (pcmkXmlStr) device); } xmlSetProp(node, (pcmkXmlStr) "valid", (pcmkXmlStr) (rc ? "false" : "true")); pcmk__output_xml_push_parent(out, node); out->subprocess_output(out, rc, output, error_output); pcmk__output_xml_pop_parent(out); return rc; } static pcmk__message_entry_t fmt_functions[] = { { "last-fenced", "html", last_fenced_html }, { "last-fenced", "text", last_fenced_text }, { "last-fenced", "xml", last_fenced_xml }, { "stonith-event", "html", stonith_event_html }, { "stonith-event", "text", stonith_event_text }, { "stonith-event", "xml", stonith_event_xml }, { "validate", "html", validate_agent_html }, { "validate", "text", validate_agent_text }, { "validate", "xml", validate_agent_xml }, { NULL, NULL, NULL } }; void stonith__register_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } diff --git a/lib/pengine/bundle.c b/lib/pengine/bundle.c index 290911cf5b..6c505f5716 100644 --- a/lib/pengine/bundle.c +++ b/lib/pengine/bundle.c @@ -1,1926 +1,1915 @@ /* * Copyright 2004-2019 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 #define PE__VARIANT_BUNDLE 1 #include "./variant.h" 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 int allocate_ip(pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, char *buffer, int max) { if(data->ip_range_start == NULL) { return 0; } 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) { return snprintf(buffer, max, " --add-host=%s-%d:%s", data->prefix, replica->offset, replica->ipaddr); } case PE__CONTAINER_AGENT_RKT: return snprintf(buffer, max, " --hosts-entry=%s=%s-%d", replica->ipaddr, data->prefix, replica->offset); default: // PE__CONTAINER_AGENT_UNKNOWN break; } return 0; } 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] 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: clear_bit(rsc->flags, pe_rsc_unique); */ } return TRUE; } return FALSE; } static bool create_ip_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { 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 (!common_unpack(xml_ip, &replica->ip, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->ip); } return TRUE; } static bool create_docker_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-docker-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_DOCKER_S); 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); offset += snprintf(buffer+offset, max-offset, " --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) { offset += snprintf(buffer+offset, max-offset, " -h %s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1"); if (data->container_network) { #if 0 offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target); } if(mount->options) { offset += snprintf(buffer+offset, max-offset, ":%s", mount->options); } } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s", replica->ipaddr, port->source, port->target); } else if(safe_str_neq(data->container_network, "host")) { // No need to do port mapping if net=host offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { 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"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { 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 (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } static bool create_podman_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; id = crm_strdup_printf("%s-podman-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_PODMAN_S); 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); // FIXME: (bandini 2018-08) podman has no restart policies //offset += snprintf(buffer+offset, max-offset, " --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) { offset += snprintf(buffer+offset, max-offset, " -h %s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " -e PCMK_stderr=1"); if (data->container_network) { #if 0 // podman has no support for --link-local-ip offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " -e PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " -v %s:%s", source, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " -v %s:%s", mount->source, mount->target); } if(mount->options) { offset += snprintf(buffer+offset, max-offset, ":%s", mount->options); } } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " -p %s:%s:%s", replica->ipaddr, port->source, port->target); } else if(safe_str_neq(data->container_network, "host")) { // No need to do port mapping if net=host offset += snprintf(buffer+offset, max-offset, " -p %s:%s", port->source, port->target); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { 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"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { 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 (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } static bool create_rkt_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { int offset = 0, max = 4096; char *buffer = calloc(1, max+1); int doffset = 0, dmax = 1024; char *dbuffer = calloc(1, dmax+1); char *id = NULL; xmlNode *xml_container = NULL; xmlNode *xml_obj = NULL; int volid = 0; id = crm_strdup_printf("%s-rkt-%d", data->prefix, replica->offset); crm_xml_sanitize_id(id); xml_container = create_resource(id, "heartbeat", PE__CONTAINER_AGENT_RKT_S); 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", "true"); crm_create_nvpair_xml(xml_obj, NULL, "force_kill", "false"); crm_create_nvpair_xml(xml_obj, NULL, "reuse", "false"); /* 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) { offset += snprintf(buffer+offset, max-offset, " --hostname=%s-%d", data->prefix, replica->offset); } offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_stderr=1"); if (data->container_network) { #if 0 offset += snprintf(buffer+offset, max-offset, " --link-local-ip=%s", replica->ipaddr); #endif offset += snprintf(buffer+offset, max-offset, " --net=%s", data->container_network); } if(data->control_port) { offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%s", data->control_port); } else { offset += snprintf(buffer+offset, max-offset, " --environment=PCMK_remote_port=%d", DEFAULT_REMOTE_PORT); } for(GListPtr pIter = data->mounts; pIter != NULL; pIter = pIter->next) { pe__bundle_mount_t *mount = pIter->data; if (is_set(mount->flags, pe__bundle_mount_subdir)) { char *source = crm_strdup_printf( "%s/%s-%d", mount->source, data->prefix, replica->offset); if(doffset > 0) { doffset += snprintf(dbuffer+doffset, dmax-doffset, ","); } doffset += snprintf(dbuffer+doffset, dmax-doffset, "%s", source); offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, source); if(mount->options) { offset += snprintf(buffer+offset, max-offset, ",%s", mount->options); } offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target); free(source); } else { offset += snprintf(buffer+offset, max-offset, " --volume vol%d,kind=host,source=%s", volid, mount->source); if(mount->options) { offset += snprintf(buffer+offset, max-offset, ",%s", mount->options); } offset += snprintf(buffer+offset, max-offset, " --mount volume=vol%d,target=%s", volid, mount->target); } volid++; } for(GListPtr pIter = data->ports; pIter != NULL; pIter = pIter->next) { pe__bundle_port_t *port = pIter->data; if (replica->ipaddr) { offset += snprintf(buffer+offset, max-offset, " --port=%s:%s:%s", port->target, replica->ipaddr, port->source); } else { offset += snprintf(buffer+offset, max-offset, " --port=%s:%s", port->target, port->source); } } if (data->launcher_options) { offset += snprintf(buffer+offset, max-offset, " %s", data->launcher_options); } if (data->container_host_options) { offset += snprintf(buffer + offset, max - offset, " %s", data->container_host_options); } crm_create_nvpair_xml(xml_obj, NULL, "run_opts", buffer); free(buffer); crm_create_nvpair_xml(xml_obj, NULL, "mount_points", dbuffer); free(dbuffer); if (replica->child) { if (data->container_command) { 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"); /* } else if(child && data->untrusted) { * Support this use-case? * * The ability to have resources started/stopped by us, but * unable to set attributes, etc. * * Arguably better to control API access this with ACLs like * "normal" remote nodes * * crm_create_nvpair_xml(xml_obj, NULL, * "run_cmd", * "/usr/libexec/pacemaker/pacemaker-execd"); * crm_create_nvpair_xml(xml_obj, NULL, "monitor_cmd", * "/usr/libexec/pacemaker/lrmd_internal_ctl -c poke"); */ } else { if (data->container_command) { 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 (!common_unpack(xml_container, &replica->container, parent, data_set)) { return FALSE; } parent->children = g_list_append(parent->children, replica->container); return TRUE; } /*! * \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(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) { GListPtr child; for (child = rsc->children; child != NULL; child = child->next) { disallow_node((resource_t *) (child->data), uname); } } } static bool create_remote_resource(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { if (replica->child && valid_network(data)) { GHashTableIter gIter; GListPtr rsc_iter = NULL; 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 (remote_id_conflict(id, data_set)) { free(id); // The biggest hammer we have id = crm_strdup_printf("pcmk-internal-%s-remote-%d", replica->child->id, replica->offset); CRM_ASSERT(remote_id_conflict(id, data_set) == FALSE); } /* 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 = crm_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 PE 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(data_set->nodes, uname); if (node == NULL) { node = pe_create_node(uname, uname, "remote", "-INFINITY", data_set); } 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 common_unpack() 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. */ for (rsc_iter = data_set->resources; rsc_iter; rsc_iter = rsc_iter->next) { disallow_node((resource_t *) (rsc_iter->data), uname); } replica->node = node_copy(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 = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free); g_hash_table_insert(replica->child->allowed_nodes, (gpointer) replica->node->details->id, node_copy(replica->node)); { node_t *copy = node_copy(replica->node); copy->weight = -INFINITY; g_hash_table_insert(replica->child->parent->allowed_nodes, (gpointer) replica->node->details->id, copy); } if (!common_unpack(xml_remote, &replica->remote, parent, data_set)) { return FALSE; } 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 TRUE; } static bool create_container(pe_resource_t *parent, pe__bundle_variant_data_t *data, pe__bundle_replica_t *replica, pe_working_set_t *data_set) { switch (data->agent_type) { case PE__CONTAINER_AGENT_DOCKER: if (!create_docker_resource(parent, data, replica, data_set)) { return FALSE; } break; case PE__CONTAINER_AGENT_PODMAN: if (!create_podman_resource(parent, data, replica, data_set)) { return FALSE; } break; case PE__CONTAINER_AGENT_RKT: if (!create_rkt_resource(parent, data, replica, data_set)) { return FALSE; } break; default: // PE__CONTAINER_AGENT_UNKNOWN return FALSE; } if (create_ip_resource(parent, data, replica, data_set) == FALSE) { return FALSE; } if(create_remote_resource(parent, data, replica, data_set) == FALSE) { return FALSE; } if (replica->child && replica->ipaddr) { add_hash_param(replica->child->meta, "external-ip", replica->ipaddr); } if (replica->remote) { /* * 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. */ set_bit(replica->remote->flags, pe_rsc_allow_remote_remotes); } return TRUE; } 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)); mount->source = strdup(source); mount->target = strdup(target); if (options) { mount->options = strdup(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) { 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; if (rsc == NULL) { return FALSE; } value = g_hash_table_lookup(rsc->parameters, XML_RSC_ATTR_REMOTE_RA_ADDR); if (safe_str_eq(value, "#uname") == FALSE) { return FALSE; } else { const char *match[3][2] = { { XML_ATTR_TYPE, "remote" }, { XML_AGENT_ATTR_CLASS, PCMK_RESOURCE_CLASS_OCF }, { XML_AGENT_ATTR_PROVIDER, "pacemaker" }, }; for (int m = 0; m < 3; m++) { value = crm_element_value(rsc->xml, match[m][0]); if (safe_str_neq(value, match[m][1])) { return FALSE; } } } return TRUE; } const char * pe__add_bundle_remote_name(pe_resource_t *rsc, 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, node->details->uname); if(xml != NULL && field != NULL) { crm_xml_add(xml, field, node->details->uname); } return node->details->uname; } 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; } } } 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"); } bundle_data->promoted_max = crm_parse_int(value, "0"); if (bundle_data->promoted_max < 0) { pe_err("%s for %s must be nonnegative integer, using 0", XML_RSC_ATTR_PROMOTED_MAX, rsc->id); bundle_data->promoted_max = 0; } value = crm_element_value(xml_obj, "replicas"); if ((value == NULL) && bundle_data->promoted_max) { bundle_data->nreplicas = bundle_data->promoted_max; } else { bundle_data->nreplicas = crm_parse_int(value, "1"); } if (bundle_data->nreplicas < 1) { pe_err("'replicas' for %s must be positive integer, using 1", rsc->id); 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"); bundle_data->nreplicas_per_host = crm_parse_int(value, "1"); if (bundle_data->nreplicas_per_host < 1) { pe_err("'replicas-per-host' for %s must be positive integer, using 1", rsc->id); bundle_data->nreplicas_per_host = 1; } if (bundle_data->nreplicas_per_host == 1) { clear_bit(rsc->flags, 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 (check_boolean(value) == FALSE) { bundle_data->add_host = TRUE; } else { crm_str_to_boolean(value, &bundle_data->add_host); } for (xmlNode *xml_child = __xml_first_child_element(xml_obj); xml_child != NULL; xml_child = __xml_next_element(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 = __xml_first_child_element(xml_obj); xml_child != NULL; xml_child = __xml_next_element(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"); set_bit(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 = crm_itoa(bundle_data->nreplicas); crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_INCARNATION_MAX, value); free(value); value = crm_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, (bundle_data->nreplicas_per_host > 1)? XML_BOOLEAN_TRUE : XML_BOOLEAN_FALSE); if (bundle_data->promoted_max) { crm_create_nvpair_xml(xml_set, NULL, XML_RSC_ATTR_PROMOTABLE, XML_BOOLEAN_TRUE); value = crm_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; GListPtr childIter = NULL; resource_t *new_rsc = NULL; pe__bundle_port_t *port = NULL; int offset = 0, max = 1024; char *buffer = NULL; if (common_unpack(xml_resource, &new_rsc, rsc, data_set) == FALSE) { pe_err("Failed unpacking resource %s", ID(rsc->xml)); if (new_rsc != NULL && new_rsc->fns != NULL) { new_rsc->fns->free(new_rsc); } return FALSE; } bundle_data->child = new_rsc; /* 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 = crm_itoa(DEFAULT_REMOTE_PORT); } port->target = strdup(port->source); bundle_data->ports = g_list_append(bundle_data->ports, port); buffer = calloc(1, max+1); 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 (is_set(replica->child->flags, pe_rsc_notify)) { set_bit(bundle_data->child->flags, pe_rsc_notify); } offset += allocate_ip(bundle_data, replica, buffer+offset, max-offset); 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 = buffer; 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 int offset = 0, max = 1024; char *buffer = calloc(1, max+1); for (int lpc = 0; lpc < bundle_data->nreplicas; lpc++) { pe__bundle_replica_t *replica = calloc(1, sizeof(pe__bundle_replica_t)); replica->offset = lpc; offset += allocate_ip(bundle_data, replica, buffer+offset, max-offset); bundle_data->replicas = g_list_append(bundle_data->replicas, replica); } bundle_data->container_host_options = buffer; } for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; if (!create_container(rsc, bundle_data, replica, data_set)) { pe_err("Failed unpacking resource %s", rsc->id); rsc->fns->free(rsc); return FALSE; } } 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; GListPtr 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; } static void print_rsc_in_list(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"); } } } 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 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_concat(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\" ", is_set(rsc->flags, pe_rsc_unique)? "true" : "false"); status_print("managed=\"%s\" ", is_set(rsc->flags, pe_rsc_managed) ? "true" : "false"); status_print("failed=\"%s\" ", is_set(rsc->flags, pe_rsc_failed) ? "true" : "false"); 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); } int pe__bundle_xml(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); pe__bundle_variant_data_t *bundle_data = NULL; int rc = 0; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); rc = pe__name_and_nvpairs_xml(out, true, "bundle", 6 , "id", rsc->id , "type", container_agent_str(bundle_data->agent_type) , "image", bundle_data->image , "unique", BOOL2STR(is_set(rsc->flags, pe_rsc_unique)) , "managed", BOOL2STR(is_set(rsc->flags, pe_rsc_managed)) , "failed", BOOL2STR(is_set(rsc->flags, pe_rsc_failed))); CRM_ASSERT(rc == 0); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; char *id = crm_itoa(replica->offset); CRM_ASSERT(replica); rc = pe__name_and_nvpairs_xml(out, true, "replica", 1, "id", id); free(id); CRM_ASSERT(rc == 0); if (replica->ip != NULL) { out->message(out, crm_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { out->message(out, crm_element_name(replica->child->xml), options, replica->child); } out->message(out, crm_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { out->message(out, crm_element_name(replica->remote->xml), options, replica->remote); } pcmk__output_xml_pop_parent(out); // replica } 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, long options) { 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); pe__common_output_html(out, rsc, buffer, node, options); } int pe__bundle_html(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); pe__bundle_variant_data_t *bundle_data = NULL; char buffer[LINE_MAX]; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); pcmk__output_create_xml_node(out, "br"); out->begin_list(out, NULL, NULL, "Container bundle%s: %s [%s]%s%s", (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); pcmk__output_xml_create_parent(out, "li"); if (is_set(options, pe_print_implicit)) { if(g_list_length(bundle_data->replicas) > 1) { snprintf(buffer, LINE_MAX, " Replica[%d]", replica->offset); xmlNodeSetContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr) buffer); } pcmk__output_create_xml_node(out, "br"); out->begin_list(out, NULL, NULL, NULL); if (replica->ip != NULL) { out->message(out, crm_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { out->message(out, crm_element_name(replica->child->xml), options, replica->child); } out->message(out, crm_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { out->message(out, crm_element_name(replica->remote->xml), options, replica->remote); } out->end_list(out); } else { pe__bundle_replica_output_html(out, replica, options); } pcmk__output_xml_pop_parent(out); } out->end_list(out); return 0; } static void pe__bundle_replica_output_text(pcmk__output_t *out, pe__bundle_replica_t *replica, - const char *pre_text, long options) + long options) { 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); - pe__common_output_text(out, rsc, pre_text, buffer, node, options); + pe__common_output_text(out, rsc, buffer, node, options); } int pe__bundle_text(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); - const char *pre_text = va_arg(args, char *); pe__bundle_variant_data_t *bundle_data = NULL; - char *child_text = NULL; - char *buf = NULL; CRM_ASSERT(rsc != NULL); get_bundle_variant_data(bundle_data, rsc); - if (pre_text == NULL) { - pre_text = " "; - } - - out->begin_list(out, NULL, NULL, "%sContainer bundle%s: %s [%s]%s%s", - pre_text, ((bundle_data->nreplicas > 1)? " set" : ""), + out->begin_list(out, NULL, NULL, "Container bundle%s: %s [%s]%s%s", + (bundle_data->nreplicas > 1)? " set" : "", rsc->id, bundle_data->image, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (GList *gIter = bundle_data->replicas; gIter != NULL; gIter = gIter->next) { pe__bundle_replica_t *replica = gIter->data; CRM_ASSERT(replica); if (is_set(options, pe_print_implicit)) { - child_text = crm_strdup_printf(" %s", pre_text); if(g_list_length(bundle_data->replicas) > 1) { - out->list_item(out, NULL, "%sReplica[%d]", pre_text, replica->offset); + out->list_item(out, NULL, "Replica[%d]", replica->offset); } out->begin_list(out, NULL, NULL, NULL); if (replica->ip != NULL) { - out->message(out, crm_element_name(replica->ip->xml), options, replica->ip, child_text); + out->message(out, crm_element_name(replica->ip->xml), options, replica->ip); } if (replica->child != NULL) { - out->message(out, crm_element_name(replica->child->xml), options, replica->child, child_text); + out->message(out, crm_element_name(replica->child->xml), options, replica->child); } - out->message(out, crm_element_name(replica->container->xml), options, replica->container, child_text); + out->message(out, crm_element_name(replica->container->xml), options, replica->container); if (replica->remote != NULL) { - out->message(out, crm_element_name(replica->remote->xml), options, replica->remote, child_text); + out->message(out, crm_element_name(replica->remote->xml), options, replica->remote); } out->end_list(out); } else { - child_text = crm_strdup_printf("%s ", pre_text); - pe__bundle_replica_output_text(out, replica, child_text, options); + pe__bundle_replica_output_text(out, replica, options); } - free(child_text); } - free(buf); out->end_list(out); return 0; } static void print_bundle_replica(pe__bundle_replica_t *replica, const char *pre_text, long options, void *print_data) { 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); } 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, is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", 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 (is_set(options, pe_print_implicit)) { child_text = crm_strdup_printf(" %s", pre_text); if(g_list_length(bundle_data->replicas) > 1) { 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); 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 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; } } diff --git a/lib/pengine/clone.c b/lib/pengine/clone.c index 388558b77c..4e79f359a7 100644 --- a/lib/pengine/clone.c +++ b/lib/pengine/clone.c @@ -1,1083 +1,1045 @@ /* * Copyright 2004-2019 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 #define VARIANT_CLONE 1 #include "./variant.h" 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 = NULL; get_clone_variant_data(clone_data, rsc); 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)); } } resource_t * find_clone_instance(resource_t * rsc, const char *sub_id, pe_working_set_t * data_set) { char *child_id = NULL; 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_concat(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; 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 = crm_itoa(clone_data->total_clones); inc_max = crm_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 (common_unpack(child_copy, &child_rsc, rsc, data_set) == FALSE) { pe_err("Failed unpacking resource %s", crm_element_value(child_copy, XML_ATTR_ID)); child_rsc = NULL; 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) { set_bit_recursive(child_rsc, pe_rsc_orphan); } add_hash_param(child_rsc->meta, XML_RSC_ATTR_INCARNATION_MAX, inc_max); print_resource(LOG_TRACE, "Added ", child_rsc, FALSE); bail: free(inc_num); free(inc_max); return child_rsc; } gboolean clone_unpack(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 *ordered = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_ORDERED); 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 (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, XML_RSC_ATTR_MASTER_MAX); } 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, XML_RSC_ATTR_MASTER_NODEMAX); } clone_data->promoted_max = crm_parse_int(promoted_max, "1"); clone_data->promoted_node_max = crm_parse_int(promoted_node_max, "1"); } // Implied by calloc() /* clone_data->xml_obj_child = NULL; */ clone_data->clone_node_max = crm_parse_int(max_clones_node, "1"); if (max_clones) { clone_data->clone_max = crm_parse_int(max_clones, "1"); } else if (g_list_length(data_set->nodes) > 0) { clone_data->clone_max = g_list_length(data_set->nodes); } else { clone_data->clone_max = 1; /* Handy during crm_verify */ } clone_data->ordered = crm_is_true(ordered); if ((rsc->flags & pe_rsc_unique) == 0 && clone_data->clone_node_max > 1) { crm_config_err("Anonymous clones (%s) may only support one copy 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", is_set(rsc->flags, pe_rsc_unique) ? "true" : "false"); pe_rsc_trace(rsc, "\tClone is promotable: %s", is_set(rsc->flags, pe_rsc_promotable) ? "true" : "false"); // Clones may contain a single group or primitive for (a_child = __xml_first_child_element(xml_obj); a_child != NULL; a_child = __xml_next_element(a_child)) { if (crm_str_eq((const char *)a_child->name, XML_CIB_TAG_RESOURCE, TRUE) || crm_str_eq((const char *)a_child->name, XML_CIB_TAG_GROUP, TRUE)) { clone_data->xml_obj_child = a_child; break; } } if (clone_data->xml_obj_child == NULL) { crm_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, is_set(rsc->flags, pe_rsc_unique) ? XML_BOOLEAN_TRUE : XML_BOOLEAN_FALSE); 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(resource_t * rsc, gboolean all) { GListPtr gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (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; } } static void short_print(char *list, const char *prefix, const char *type, const char *suffix, long options, void *print_data) { if(suffix == NULL) { suffix = ""; } if (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 void -pe__short_output_text(pcmk__output_t *out, char *list, const char *prefix, const char *type, const char *suffix, long options) -{ - if(suffix == NULL) { - suffix = ""; - } - - if (list) { - fprintf(out->dest, "%s%s: [%s ]%s", prefix, type, list, suffix); - - if (options & pe_print_suppres_nl) { - /* nothing */ - } else { - fprintf(out->dest, "\n"); - } - } -} - -static void -pe__short_output_html(pcmk__output_t *out, char *list, const char *type, const char *suffix, long options) -{ - if (list == NULL) { - return; - } - - out->list_item(out, NULL, " %s: [%s ]%s", type, list, suffix ? suffix : ""); -} - static const char * configured_role_str(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(((resource_t*)rsc->children->data)->meta, XML_RSC_ATTR_TARGET_ROLE); } return target_role; } static enum rsc_role_e configured_role(resource_t * rsc) { const char *target_role = configured_role_str(rsc); if (target_role) { return text2role(target_role); } return RSC_ROLE_UNKNOWN; } static void clone_print_xml(resource_t * rsc, const char *pre_text, long options, void *print_data) { char *child_text = crm_concat(pre_text, " ", ' '); const char *target_role = configured_role_str(rsc); GListPtr gIter = rsc->children; status_print("%sid); status_print("multi_state=\"%s\" ", is_set(rsc->flags, pe_rsc_promotable)? "true" : "false"); status_print("unique=\"%s\" ", is_set(rsc->flags, pe_rsc_unique) ? "true" : "false"); status_print("managed=\"%s\" ", is_set(rsc->flags, pe_rsc_managed) ? "true" : "false"); status_print("failed=\"%s\" ", is_set(rsc->flags, pe_rsc_failed) ? "true" : "false"); status_print("failure_ignored=\"%s\" ", is_set(rsc->flags, pe_rsc_failure_ignored) ? "true" : "false"); if (target_role) { status_print("target_role=\"%s\" ", target_role); } status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (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(resource_t * rsc, long long flag, bool any) { GListPtr gIter; bool all = !any; if(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; } void clone_print(resource_t * rsc, const char *pre_text, long options, void *print_data) { char *list_text = NULL; char *child_text = NULL; char *stopped_list = NULL; GListPtr master_list = NULL; GListPtr started_list = NULL; GListPtr 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_concat(pre_text, " ", ' '); status_print("%sClone Set: %s [%s]%s%s%s", pre_text ? pre_text : "", rsc->id, ID(clone_data->xml_obj_child), is_set(rsc->flags, pe_rsc_promotable) ? " (promotable)" : "", is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", 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; resource_t *child_rsc = (resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (options & pe_print_clone_details) { print_full = TRUE; } if (is_set(rsc->flags, pe_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || is_not_set(rsc->flags, pe_rsc_orphan)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (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 (is_not_set(child_rsc->flags, pe_rsc_orphan) && is_not_set(options, pe_print_clone_active)) { stopped_list = add_list_element(stopped_list, 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 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_SLAVE) { master_list = g_list_append(master_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"); } } } /* Masters */ master_list = g_list_sort(master_list, sort_node_uname); for (gIter = master_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } short_print(list_text, child_text, "Masters", NULL, options, print_data); g_list_free(master_list); free(list_text); list_text = NULL; /* Started/Slaves */ started_list = g_list_sort(started_list, sort_node_uname); for (gIter = started_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } if (is_set(rsc->flags, pe_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if(role == RSC_ROLE_SLAVE) { short_print(list_text, child_text, "Slaves (target-role)", NULL, options, print_data); } else { short_print(list_text, child_text, "Slaves", NULL, options, print_data); } } else { short_print(list_text, child_text, "Started", NULL, options, print_data); } g_list_free(started_list); free(list_text); list_text = NULL; if (is_not_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 (is_not_set(rsc->flags, pe_rsc_unique) && (clone_data->clone_max > active_instances)) { GListPtr nIter; GListPtr list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped list for non-unique clones */ free(stopped_list); stopped_list = NULL; if (g_list_length(list) == 0) { /* 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, sort_node_uname); for (nIter = list; nIter != NULL; nIter = nIter->next) { node_t *node = (node_t *)nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL) { stopped_list = add_list_element(stopped_list, node->details->uname); } } g_list_free(list); } short_print(stopped_list, child_text, state, NULL, options, print_data); free(stopped_list); } if (options & pe_print_html) { status_print("
      \n"); } free(child_text); } int pe__clone_xml(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); GListPtr gIter = rsc->children; int rc = pe__name_and_nvpairs_xml(out, true, "clone", 7 , "id", rsc->id , "multi_state", BOOL2STR(is_set(rsc->flags, pe_rsc_promotable)) , "unique", BOOL2STR(is_set(rsc->flags, pe_rsc_unique)) , "managed", BOOL2STR(is_set(rsc->flags, pe_rsc_managed)) , "failed", BOOL2STR(is_set(rsc->flags, pe_rsc_failed)) , "failure_ignored", BOOL2STR(is_set(rsc->flags, pe_rsc_failure_ignored)) , "target_role", configured_role_str(rsc)); CRM_ASSERT(rc == 0); for (; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (resource_t *) gIter->data; out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); } pcmk__output_xml_pop_parent(out); return rc; } int pe__clone_html(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); char *list_text = NULL; char *stopped_list = NULL; GListPtr master_list = NULL; GListPtr started_list = NULL; GListPtr gIter = rsc->children; clone_variant_data_t *clone_data = NULL; int active_instances = 0; get_clone_variant_data(clone_data, rsc); out->begin_list(out, NULL, NULL, "Clone Set: %s [%s]%s%s%s", rsc->id, ID(clone_data->xml_obj_child), is_set(rsc->flags, pe_rsc_promotable) ? " (promotable)" : "", is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; resource_t *child_rsc = (resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (options & pe_print_clone_details) { print_full = TRUE; } if (is_set(rsc->flags, pe_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || is_not_set(rsc->flags, pe_rsc_orphan)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (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 (is_not_set(child_rsc->flags, pe_rsc_orphan) && is_not_set(options, pe_print_clone_active)) { stopped_list = add_list_element(stopped_list, 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 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_SLAVE) { master_list = g_list_append(master_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) { - pcmk__output_xml_create_parent(out, "li"); out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); - pcmk__output_xml_pop_parent(out); } } /* Masters */ master_list = g_list_sort(master_list, sort_node_uname); for (gIter = master_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } if (list_text != NULL) { - pe__short_output_html(out, list_text, "Masters", NULL, options); + out->list_item(out, NULL, " Masters: [%s ]", list_text); g_list_free(master_list); free(list_text); list_text = NULL; } /* Started/Slaves */ started_list = g_list_sort(started_list, sort_node_uname); for (gIter = started_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } if (list_text != NULL) { if (is_set(rsc->flags, pe_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if(role == RSC_ROLE_SLAVE) { - pe__short_output_html(out, list_text, "Slaves (target-role)", NULL, options); + out->list_item(out, NULL, " Slaves (target-role): [%s ]", list_text); } else { - pe__short_output_html(out, list_text, "Slaves", NULL, options); + out->list_item(out, NULL, " Slaves: [%s ]", list_text); } } else { - pe__short_output_html(out, list_text, "Started", NULL, options); + out->list_item(out, NULL, " Started: [%s ]", list_text); } g_list_free(started_list); free(list_text); list_text = NULL; } if (is_not_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 (is_not_set(rsc->flags, pe_rsc_unique) && (clone_data->clone_max > active_instances)) { GListPtr nIter; GListPtr list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped list for non-unique clones */ free(stopped_list); stopped_list = NULL; if (g_list_length(list) == 0) { /* 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, sort_node_uname); for (nIter = list; nIter != NULL; nIter = nIter->next) { node_t *node = (node_t *)nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL) { stopped_list = add_list_element(stopped_list, node->details->uname); } } g_list_free(list); } if (stopped_list != NULL) { - pe__short_output_html(out, stopped_list, state, NULL, options); + out->list_item(out, NULL, " %s: [%s ]", state, stopped_list); free(stopped_list); } } out->end_list(out); return 0; } int pe__clone_text(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); - const char *pre_text = va_arg(args, char *); char *list_text = NULL; - char *child_text = NULL; char *stopped_list = NULL; GListPtr master_list = NULL; GListPtr started_list = NULL; GListPtr gIter = rsc->children; clone_variant_data_t *clone_data = NULL; int active_instances = 0; - if (pre_text == NULL) { - pre_text = " "; - } - get_clone_variant_data(clone_data, rsc); - child_text = crm_concat(pre_text, " ", ' '); - - fprintf(out->dest, "%sClone Set: %s [%s]%s%s%s", - pre_text, rsc->id, ID(clone_data->xml_obj_child), - is_set(rsc->flags, pe_rsc_promotable) ? " (promotable)" : "", - is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", - is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); + out->begin_list(out, NULL, NULL, "Clone Set: %s [%s]%s%s%s", + rsc->id, ID(clone_data->xml_obj_child), + is_set(rsc->flags, pe_rsc_promotable) ? " (promotable)" : "", + is_set(rsc->flags, pe_rsc_unique) ? " (unique)" : "", + is_set(rsc->flags, pe_rsc_managed) ? "" : " (unmanaged)"); for (; gIter != NULL; gIter = gIter->next) { gboolean print_full = FALSE; resource_t *child_rsc = (resource_t *) gIter->data; gboolean partially_active = child_rsc->fns->active(child_rsc, FALSE); if (options & pe_print_clone_details) { print_full = TRUE; } if (is_set(rsc->flags, pe_rsc_unique)) { // Print individual instance when unique (except stopped orphans) if (partially_active || is_not_set(rsc->flags, pe_rsc_orphan)) { print_full = TRUE; } // Everything else in this block is for anonymous clones } else if (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 (is_not_set(child_rsc->flags, pe_rsc_orphan) && is_not_set(options, pe_print_clone_active)) { stopped_list = add_list_element(stopped_list, 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 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_SLAVE) { master_list = g_list_append(master_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) { - out->message(out, crm_element_name(child_rsc->xml), options, child_rsc, child_text); + out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); } } /* Masters */ master_list = g_list_sort(master_list, sort_node_uname); for (gIter = master_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } if (list_text != NULL) { - pe__short_output_text(out, list_text, child_text, "Masters", NULL, options); + out->list_item(out, "Masters", "[%s ]", list_text); g_list_free(master_list); free(list_text); list_text = NULL; } /* Started/Slaves */ started_list = g_list_sort(started_list, sort_node_uname); for (gIter = started_list; gIter; gIter = gIter->next) { node_t *host = gIter->data; list_text = add_list_element(list_text, host->details->uname); active_instances++; } if (list_text != NULL) { if (is_set(rsc->flags, pe_rsc_promotable)) { enum rsc_role_e role = configured_role(rsc); if(role == RSC_ROLE_SLAVE) { - pe__short_output_text(out, list_text, child_text, "Slaves (target-role)", NULL, options); + out->list_item(out, "Slaves (target-role)", "[%s ]", list_text); } else { - pe__short_output_text(out, list_text, child_text, "Slaves", NULL, options); + out->list_item(out, "Slaves", "[%s ]", list_text); } } else { - pe__short_output_text(out, list_text, child_text, "Started", NULL, options); + out->list_item(out, "Started", "[%s ]", list_text); } g_list_free(started_list); free(list_text); list_text = NULL; } if (is_not_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 (is_not_set(rsc->flags, pe_rsc_unique) && (clone_data->clone_max > active_instances)) { GListPtr nIter; GListPtr list = g_hash_table_get_values(rsc->allowed_nodes); /* Custom stopped list for non-unique clones */ free(stopped_list); stopped_list = NULL; if (g_list_length(list) == 0) { /* 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, sort_node_uname); for (nIter = list; nIter != NULL; nIter = nIter->next) { node_t *node = (node_t *)nIter->data; if (pe_find_node(rsc->running_on, node->details->uname) == NULL) { stopped_list = add_list_element(stopped_list, node->details->uname); } } g_list_free(list); } if (stopped_list != NULL) { - pe__short_output_text(out, stopped_list, child_text, state, NULL, options); + out->list_item(out, state, "[%s ]", stopped_list); free(stopped_list); } } - free(child_text); + out->end_list(out); return 0; } void clone_free(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 (GListPtr gIter = rsc->children; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (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 resource_t * rsc, gboolean current) { enum rsc_role_e clone_role = RSC_ROLE_UNKNOWN; GListPtr gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (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(pe_resource_t *rsc, pe_working_set_t *data_set) { if (pe_rsc_is_clone(rsc)) { clone_variant_data_t *clone_data = NULL; get_clone_variant_data(clone_data, rsc); if (clone_data->clone_max == g_list_length(data_set->nodes)) { return TRUE; } } return FALSE; } diff --git a/lib/pengine/native.c b/lib/pengine/native.c index 5d2c3624e4..40012d4cab 100644 --- a/lib/pengine/native.c +++ b/lib/pengine/native.c @@ -1,1620 +1,1480 @@ /* * Copyright 2004-2019 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 #define VARIANT_NATIVE 1 #include "./variant.h" /*! * \internal * \brief Check whether a resource is active on multiple nodes */ static bool is_multiply_active(pe_resource_t *rsc) { unsigned int count = 0; if (rsc->variant == pe_native) { pe__find_active_requires(rsc, &count); } return count > 1; } void native_add_running(resource_t * rsc, node_t * node, pe_working_set_t * data_set) { GListPtr gIter = rsc->running_on; CRM_CHECK(node != NULL, return); for (; gIter != NULL; gIter = gIter->next) { node_t *a_node = (node_t *) gIter->data; CRM_CHECK(a_node != NULL, return); if (safe_str_eq(a_node->details->id, node->details->id)) { return; } } pe_rsc_trace(rsc, "Adding %s to %s %s", rsc->id, node->details->uname, 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); } if (rsc->variant == pe_native && node->details->maintenance) { clear_bit(rsc->flags, pe_rsc_managed); } if (is_not_set(rsc->flags, pe_rsc_managed)) { 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; 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 = node_hash_from_list(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_stop_start: break; case recovery_block: clear_bit(rsc->flags, pe_rsc_managed); set_bit(rsc->flags, 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) { GListPtr gIter = rsc->parent->children; for (; gIter != NULL; gIter = gIter->next) { resource_t *child = (resource_t *) gIter->data; clear_bit(child->flags, pe_rsc_managed); set_bit(child->flags, pe_rsc_block); } } break; } crm_debug("%s is active on multiple nodes including %s: %s", rsc->id, node->details->uname, recovery2text(rsc->recovery_type)); } else { pe_rsc_trace(rsc, "Resource %s is active on: %s", rsc->id, node->details->uname); } if (rsc->parent != NULL) { native_add_running(rsc->parent, node, data_set); } } static void recursive_clear_unique(pe_resource_t *rsc) { clear_bit(rsc->flags, pe_rsc_unique); add_hash_param(rsc->meta, XML_RSC_ATTR_UNIQUE, XML_BOOLEAN_FALSE); for (GList *child = rsc->children; child != NULL; child = child->next) { recursive_clear_unique((pe_resource_t *) child->data); } } gboolean native_unpack(resource_t * rsc, pe_working_set_t * data_set) { resource_t *parent = uber_parent(rsc); native_variant_data_t *native_data = NULL; 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); native_data = calloc(1, sizeof(native_variant_data_t)); rsc->variant_opaque = native_data; // Only some agent standards support unique and promotable clones if (is_not_set(ra_caps, pcmk_ra_cap_unique) && 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 descendents 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); recursive_clear_unique(rsc); } if (is_not_set(ra_caps, pcmk_ra_cap_promotable) && 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(resource_t *rsc, const node_t *node, int flags) { pe_rsc_trace(rsc, "Checking whether %s is on %s", rsc->id, node->details->uname); if (is_set(flags, pe_find_current) && rsc->running_on) { for (GListPtr iter = rsc->running_on; iter; iter = iter->next) { node_t *loc = (node_t *) iter->data; if (loc->details == node->details) { return TRUE; } } } else if (is_set(flags, pe_find_inactive) && (rsc->running_on == NULL)) { return TRUE; } else if (is_not_set(flags, pe_find_current) && rsc->allocated_to && (rsc->allocated_to->details == node->details)) { return TRUE; } return FALSE; } resource_t * native_find_rsc(resource_t * rsc, const char *id, const node_t *on_node, int flags) { bool match = FALSE; 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(uber_parent(rsc))) { match = FALSE; } else if (!strcmp(id, rsc->id) || safe_str_eq(id, rid)) { match = TRUE; } } else if (!strcmp(id, rsc->id)) { match = TRUE; } else if (is_set(flags, pe_find_renamed) && rsc->clone_name && strcmp(rsc->clone_name, id) == 0) { match = TRUE; } else if (is_set(flags, pe_find_any) || (is_set(flags, pe_find_anon) && is_not_set(rsc->flags, pe_rsc_unique))) { match = pe_base_name_eq(rsc, id); } if (match && on_node) { bool match_node = rsc_is_on_node(rsc, on_node, flags); if (match_node == FALSE) { match = FALSE; } } if (match) { return rsc; } for (GListPtr gIter = rsc->children; gIter != NULL; gIter = gIter->next) { resource_t *child = (resource_t *) gIter->data; result = rsc->fns->find_rsc(child, id, on_node, flags); if (result) { return result; } } return NULL; } char * native_parameter(resource_t * rsc, node_t * node, gboolean create, const char *name, pe_working_set_t * data_set) { char *value_copy = NULL; const char *value = NULL; GHashTable *hash = NULL; GHashTable *local_hash = 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); if (create || g_hash_table_size(rsc->parameters) == 0) { if (node != NULL) { pe_rsc_trace(rsc, "Creating hash with node %s", node->details->uname); } else { pe_rsc_trace(rsc, "Creating default hash"); } local_hash = crm_str_table_new(); get_rsc_attributes(local_hash, rsc, node, data_set); hash = local_hash; } else { hash = rsc->parameters; } value = g_hash_table_lookup(hash, name); if (value == NULL) { /* try meta attributes instead */ value = g_hash_table_lookup(rsc->meta, name); } if (value != NULL) { value_copy = strdup(value); } if (local_hash != NULL) { g_hash_table_destroy(local_hash); } return value_copy; } gboolean native_active(resource_t * rsc, gboolean all) { GListPtr gIter = rsc->running_on; for (; gIter != NULL; gIter = gIter->next) { node_t *a_node = (node_t *) gIter->data; if (a_node->details->unclean) { crm_debug("Resource %s: node %s is unclean", rsc->id, a_node->details->uname); return TRUE; } else if (a_node->details->online == FALSE) { crm_debug("Resource %s: node %s is offline", rsc->id, a_node->details->uname); } else { crm_debug("Resource %s active on %s", rsc->id, a_node->details->uname); return TRUE; } } return FALSE; } struct print_data_s { long options; void *print_data; }; static void native_print_attr(gpointer key, gpointer value, gpointer user_data) { long options = ((struct print_data_s *)user_data)->options; void *print_data = ((struct print_data_s *)user_data)->print_data; status_print("Option: %s = %s\n", (char *)key, (char *)value); } -static void -pe__native_output_attr_html(gpointer key, gpointer value, gpointer user_data) -{ - pcmk__output_t *out = (pcmk__output_t *)user_data; - char content[LINE_MAX]; - - snprintf(content, LINE_MAX, "Option: %s = %s
      ", (char *)key, (char *)value); - xmlNodeAddContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)content); -} - -static void -pe__native_output_attr_text(gpointer key, gpointer value, gpointer user_data) -{ - pcmk__output_t *out = (pcmk__output_t *)user_data; - fprintf(out->dest, "Option: %s = %s\n", (char *)key, (char *)value); -} - static const char * native_pending_state(resource_t * rsc) { const char *pending_state = NULL; if (safe_str_eq(rsc->pending_task, CRMD_ACTION_START)) { pending_state = "Starting"; } else if (safe_str_eq(rsc->pending_task, CRMD_ACTION_STOP)) { pending_state = "Stopping"; } else if (safe_str_eq(rsc->pending_task, CRMD_ACTION_MIGRATE)) { pending_state = "Migrating"; } else if (safe_str_eq(rsc->pending_task, CRMD_ACTION_MIGRATED)) { /* Work might be done in here. */ pending_state = "Migrating"; } else if (safe_str_eq(rsc->pending_task, CRMD_ACTION_PROMOTE)) { pending_state = "Promoting"; } else if (safe_str_eq(rsc->pending_task, CRMD_ACTION_DEMOTE)) { pending_state = "Demoting"; } return pending_state; } static const char * native_pending_task(resource_t * rsc) { const char *pending_task = NULL; if (safe_str_eq(rsc->pending_task, CRMD_ACTION_STATUS)) { 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 (safe_str_eq(rsc->pending_task, "probe")) { pending_task = "Checking"; */ } return pending_task; } static enum rsc_role_e native_displayable_role(resource_t *rsc) { enum rsc_role_e role = rsc->role; if ((role == RSC_ROLE_STARTED) && is_set(uber_parent(rsc)->flags, pe_rsc_promotable)) { role = RSC_ROLE_SLAVE; } return role; } static const char * native_displayable_state(resource_t *rsc, long options) { const char *rsc_state = NULL; if (options & pe_print_pending) { rsc_state = native_pending_state(rsc); } if (rsc_state == NULL) { rsc_state = role2text(native_displayable_role(rsc)); } return rsc_state; } static void native_print_xml(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, options); 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\" ", rsc->fns->active(rsc, TRUE) ? "true" : "false"); status_print("orphaned=\"%s\" ", is_set(rsc->flags, pe_rsc_orphan) ? "true" : "false"); status_print("blocked=\"%s\" ", is_set(rsc->flags, pe_rsc_block) ? "true" : "false"); status_print("managed=\"%s\" ", is_set(rsc->flags, pe_rsc_managed) ? "true" : "false"); status_print("failed=\"%s\" ", is_set(rsc->flags, pe_rsc_failed) ? "true" : "false"); status_print("failure_ignored=\"%s\" ", is_set(rsc->flags, pe_rsc_failure_ignored) ? "true" : "false"); 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); } } if (options & pe_print_dev) { status_print("provisional=\"%s\" ", is_set(rsc->flags, pe_rsc_provisional) ? "true" : "false"); status_print("runnable=\"%s\" ", is_set(rsc->flags, pe_rsc_runnable) ? "true" : "false"); status_print("priority=\"%f\" ", (double)rsc->priority); status_print("variant=\"%s\" ", crm_element_name(rsc->xml)); } /* 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) { GListPtr gIter = rsc->running_on; status_print(">\n"); for (; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; status_print("%s \n", pre_text, node->details->uname, node->details->id, node->details->online ? "false" : "true"); } status_print("%s\n", pre_text); } else { status_print("/>\n"); } } /* making this inline rather than a macro prevents a coverity "unreachable" * warning on the first usage */ static inline const char * comma_if(int i) { return i? ", " : ""; } -void -pe__common_output_html(pcmk__output_t *out, resource_t * rsc, - const char *name, node_t *node, long options) -{ - const char *desc = NULL; - const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); - const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); - const char *target_role = NULL; - enum rsc_role_e role = native_displayable_role(rsc); - - int offset = 0; - int flagOffset = 0; - char buffer[LINE_MAX]; - char buffer2[LINE_MAX*3]; - char flagBuffer[LINE_MAX]; - const char *color = 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) && is_not_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_rsconly) || g_list_length(rsc->running_on) > 1) { - node = NULL; - } - - pcmk__output_xml_create_parent(out, "font"); - - if (is_not_set(rsc->flags, pe_rsc_managed)) { - color = "yellow"; - - } else if (is_set(rsc->flags, pe_rsc_failed)) { - color = "red"; - - } else if (rsc->variant == pe_native && (rsc->running_on == NULL)) { - color = "red"; - - } else if (g_list_length(rsc->running_on) > 1) { - color = "orange"; - - } else if (is_set(rsc->flags, pe_rsc_failure_ignored)) { - color = "yellow"; - - } else { - color = "green"; - } - xmlSetProp(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)"color", (pcmkXmlStr)color); - - offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", name); - offset += snprintf(buffer + offset, LINE_MAX - offset, "\t(%s", class); - if (is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { - const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); - offset += snprintf(buffer + offset, LINE_MAX - offset, "::%s", prov); - } - offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s):\t", kind); - if(is_set(rsc->flags, pe_rsc_orphan)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, " ORPHANED "); - } - if(role > RSC_ROLE_SLAVE && is_set(rsc->flags, pe_rsc_failed)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED %s", role2text(role)); - } else if(is_set(rsc->flags, pe_rsc_failed)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED"); - } else { - const char *rsc_state = native_displayable_state(rsc, options); - - offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_state); - } +static char * +flags_string(resource_t *rsc, node_t *node, long options, const char *target_role) { + gchar **flags = calloc(6, sizeof(gchar *)); + int ndx = 0; - if(node) { - offset += snprintf(buffer + offset, LINE_MAX - offset, " %s", node->details->uname); - - if (node->details->online == FALSE && node->details->unclean) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sUNCLEAN", comma_if(flagOffset)); - } + if (node && node->details->online == FALSE && node->details->unclean) { + flags[ndx] = strdup("UNCLEAN"); + ndx++; } - if (options & pe_print_pending) { + if (is_set(options, pe_print_pending)) { const char *pending_task = native_pending_task(rsc); if (pending_task) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%s%s", comma_if(flagOffset), pending_task); + flags[ndx] = strdup(pending_task); + ndx++; } } if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); /* Ignore target role Started, as it is the default anyways * (and would also allow a Master to be Master). * Show if target role limits our abilities. */ if (target_role_e == RSC_ROLE_STOPPED) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sdisabled", comma_if(flagOffset)); + flags[ndx] = strdup("disabled"); + ndx++; rsc->cluster->disabled_resources++; } else if (is_set(uber_parent(rsc)->flags, pe_rsc_promotable) && target_role_e == RSC_ROLE_SLAVE) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%starget-role:%s", comma_if(flagOffset), target_role); + flags[ndx] = crm_strdup_printf("target-role:%s", target_role); + ndx++; rsc->cluster->disabled_resources++; } } if (is_set(rsc->flags, pe_rsc_block)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sblocked", comma_if(flagOffset)); + flags[ndx] = strdup("blocked"); + ndx++; rsc->cluster->blocked_resources++; } else if (is_not_set(rsc->flags, pe_rsc_managed)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sunmanaged", comma_if(flagOffset)); + flags[ndx] = strdup("unmanaged"); + ndx++; } - if(is_set(rsc->flags, pe_rsc_failure_ignored)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sfailure ignored", comma_if(flagOffset)); + if (is_set(rsc->flags, pe_rsc_failure_ignored)) { + flags[ndx] = strdup("failure ignored"); } - if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { - desc = crm_element_value(rsc->xml, XML_ATTR_DESC); + if (flags[0] != NULL) { + char *total = g_strjoinv(" ", flags); + char *flags_s = crm_strdup_printf(" (%s)", total); + + g_free(total); + g_strfreev(flags); + return flags_s; } - CRM_LOG_ASSERT(offset > 0); - if(flagOffset > 0) { - snprintf(buffer2, LINE_MAX*3, "%s (%s)%s%s", buffer, flagBuffer, desc?" ":"", desc?desc:""); + return NULL; +} + +static char * +native_output_string(resource_t *rsc, const char *name, node_t *node, long options, + const char *target_role) { + const char *desc = NULL; + const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); + const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); + enum rsc_role_e role = native_displayable_role(rsc); + + char *retval = NULL; + + char *unames = NULL; + char *provider = NULL; + const char *orphan = NULL; + char *role_s = NULL; + char *node_s = NULL; + char *print_dev_s = NULL; + char *flags_s = NULL; + + CRM_ASSERT(kind != NULL); + + if (is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { + provider = crm_strdup_printf("::%s", crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER)); + } + + if (is_set(rsc->flags, pe_rsc_orphan)) { + orphan = " ORPHANED"; + } + + if (role > RSC_ROLE_SLAVE && is_set(rsc->flags, pe_rsc_failed)) { + role_s = crm_strdup_printf(" FAILED %s", role2text(role)); + } else if (is_set(rsc->flags, pe_rsc_failed)) { + role_s = crm_strdup_printf(" FAILED"); } else { - snprintf(buffer2, LINE_MAX*3, "%s%s%s", buffer, desc?" ":"", desc?desc:""); + role_s = crm_strdup_printf(" %s", native_displayable_state(rsc, options)); } - xmlNodeSetContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)buffer2); - pcmk__output_xml_pop_parent(out); // + if (node) { + node_s = crm_strdup_printf(" %s", node->details->uname); + } - if ((options & pe_print_rsconly)) { - /* nothing */ - } else if (g_list_length(rsc->running_on) > 1) { - GListPtr gIter = rsc->running_on; + if (is_set(options, pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { + desc = crm_element_value(rsc->xml, XML_ATTR_DESC); + } - out->begin_list(out, NULL, NULL, NULL); + if (is_not_set(options, pe_print_rsconly) && g_list_length(rsc->running_on) > 1) { + GListPtr gIter = rsc->running_on; + gchar **arr = calloc(g_list_length(rsc->running_on)+1, sizeof(gchar *)); + int i = 0; + char *total = NULL; for (; gIter != NULL; gIter = gIter->next) { node_t *n = (node_t *) gIter->data; - out->list_item(out, NULL, "%s", n->details->uname); + arr[i] = (gchar *) strdup(n->details->uname); + i++; } - out->end_list(out); - } - - pcmk__output_create_xml_node(out, "br"); + total = g_strjoinv(" ", arr); + unames = crm_strdup_printf(" [ %s ]", total); - if (options & pe_print_details) { - g_hash_table_foreach(rsc->parameters, pe__native_output_attr_html, out); + g_free(total); + g_strfreev(arr); } - if (options & pe_print_dev) { - GHashTableIter iter; - node_t *n = NULL; - - snprintf(buffer, LINE_MAX, " \t(%s%svariant=%s, priority=%f)", - is_set(rsc->flags, pe_rsc_provisional) ? "provisional, " : "", - is_set(rsc->flags, pe_rsc_runnable) ? "" : "non-startable, ", - crm_element_name(rsc->xml), (double)rsc->priority); - xmlNodeAddContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)buffer); - xmlNodeAddContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)" \tAllowed Nodes"); - g_hash_table_iter_init(&iter, rsc->allowed_nodes); - while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { - snprintf(buffer, LINE_MAX, " \t * %s %d", n->details->uname, n->weight); - xmlNodeAddContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)buffer); - } + if (is_set(options, pe_print_dev)) { + print_dev_s = crm_strdup_printf(" (%s%svariant=%s, priority=%f)", + is_set(rsc->flags, pe_rsc_provisional) ? "provisional, " : "", + is_set(rsc->flags, pe_rsc_runnable) ? "" : "non-startable, ", + crm_element_name(rsc->xml), (double)rsc->priority); } - if (options & pe_print_max_details) { - GHashTableIter iter; - node_t *n = NULL; + flags_s = flags_string(rsc, node, options, target_role); - xmlNodeAddContent(pcmk__output_xml_peek_parent(out), (pcmkXmlStr)" \t=== Allowed Nodes\n"); + retval = crm_strdup_printf("%s\t(%s%s:%s):\t%s%s%s%s%s%s%s%s", + name, class, + provider ? provider : "", + kind, + orphan ? orphan : "", + role_s, + node_s ? node_s : "", + print_dev_s ? print_dev_s : "", + flags_s ? flags_s : "", + desc ? " " : "", desc ? desc : "", + unames ? unames : ""); - g_hash_table_iter_init(&iter, rsc->allowed_nodes); - while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { - pe__output_node(n, FALSE, out); - } - } + free(provider); + free(role_s); + free(node_s); + free(unames); + free(print_dev_s); + free(flags_s); + + return retval; } void -pe__common_output_text(pcmk__output_t *out, resource_t * rsc, const char *pre_text, +pe__common_output_html(pcmk__output_t *out, resource_t * rsc, const char *name, node_t *node, long options) { - const char *desc = NULL; - const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); + char *s = NULL; const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *target_role = NULL; - enum rsc_role_e role = native_displayable_role(rsc); - int offset = 0; - int flagOffset = 0; - char buffer[LINE_MAX]; - char flagBuffer[LINE_MAX]; + 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) && is_not_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 (pre_text == NULL) { - pre_text = " "; - } - if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { node = NULL; } - if(pre_text) { - offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", pre_text); - } - offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", name); - offset += snprintf(buffer + offset, LINE_MAX - offset, "\t(%s", class); - if (is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { - const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); - offset += snprintf(buffer + offset, LINE_MAX - offset, "::%s", prov); - } - offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s):\t", kind); - if(is_set(rsc->flags, pe_rsc_orphan)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, " ORPHANED "); - } - if(role > RSC_ROLE_SLAVE && is_set(rsc->flags, pe_rsc_failed)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED %s", role2text(role)); - } else if(is_set(rsc->flags, pe_rsc_failed)) { - offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED"); - } else { - const char *rsc_state = native_displayable_state(rsc, options); + if (is_not_set(rsc->flags, pe_rsc_managed)) { + cl = "rsc-managed"; - offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_state); - } + } else if (is_set(rsc->flags, pe_rsc_failed)) { + cl = "rsc-failed"; - if(node) { - offset += snprintf(buffer + offset, LINE_MAX - offset, " %s", node->details->uname); + } else if (rsc->variant == pe_native && (rsc->running_on == NULL)) { + cl = "rsc-failed"; - if (node->details->online == FALSE && node->details->unclean) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sUNCLEAN", comma_if(flagOffset)); - } - } + } else if (g_list_length(rsc->running_on) > 1) { + cl = "rsc-multiple"; - if (options & pe_print_pending) { - const char *pending_task = native_pending_task(rsc); + } else if (is_set(rsc->flags, pe_rsc_failure_ignored)) { + cl = "rsc-failure-ignored"; - if (pending_task) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%s%s", comma_if(flagOffset), pending_task); - } + } else { + cl = "rsc-ok"; } - if (target_role) { - enum rsc_role_e target_role_e = text2role(target_role); + s = native_output_string(rsc, name, node, options, target_role); + list_node = pcmk__output_create_html_node(out, "li", NULL, NULL, NULL); + pcmk_create_html_node(list_node, "span", NULL, cl, s); + free(s); - /* Ignore target role Started, as it is the default anyways - * (and would also allow a Master to be Master). - * Show if target role limits our abilities. */ - if (target_role_e == RSC_ROLE_STOPPED) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sdisabled", comma_if(flagOffset)); - rsc->cluster->disabled_resources++; + if (is_set(options, pe_print_details)) { + GHashTableIter iter; + gpointer key, value; - } else if (is_set(uber_parent(rsc)->flags, pe_rsc_promotable) - && target_role_e == RSC_ROLE_SLAVE) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%starget-role:%s", comma_if(flagOffset), target_role); - rsc->cluster->disabled_resources++; + out->begin_list(out, NULL, NULL, "Options"); + g_hash_table_iter_init(&iter, rsc->parameters); + while (g_hash_table_iter_next(&iter, &key, &value)) { + out->list_item(out, NULL, "Option: %s = %s", (char *) key, (char *) value); } + out->end_list(out); } - if (is_set(rsc->flags, pe_rsc_block)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sblocked", comma_if(flagOffset)); - rsc->cluster->blocked_resources++; + if (is_set(options, pe_print_dev)) { + GHashTableIter iter; + node_t *n = NULL; - } else if (is_not_set(rsc->flags, pe_rsc_managed)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sunmanaged", comma_if(flagOffset)); + out->begin_list(out, NULL, NULL, "Allowed Nodes"); + g_hash_table_iter_init(&iter, rsc->allowed_nodes); + while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { + out->list_item(out, NULL, "%s %d", n->details->uname, n->weight); + } + out->end_list(out); } - if(is_set(rsc->flags, pe_rsc_failure_ignored)) { - flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, - "%sfailure ignored", comma_if(flagOffset)); + if (is_set(options, pe_print_max_details)) { + GHashTableIter iter; + node_t *n = NULL; + + out->begin_list(out, NULL, NULL, "=== Allowed Nodes"); + g_hash_table_iter_init(&iter, rsc->allowed_nodes); + while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { + pe__output_node(n, FALSE, out); + } + out->end_list(out); } +} - if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { - desc = crm_element_value(rsc->xml, XML_ATTR_DESC); +void +pe__common_output_text(pcmk__output_t *out, resource_t * rsc, + const char *name, node_t *node, long options) +{ + char *s = NULL; + 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) && is_not_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); } - CRM_LOG_ASSERT(offset > 0); - if(flagOffset > 0) { - fprintf(out->dest, "%s (%s)%s%s", buffer, flagBuffer, desc?" ":"", desc?desc:""); - } else { - fprintf(out->dest, "%s%s%s", buffer, desc?" ":"", desc?desc:""); + if (is_set(options, pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { + node = NULL; } - if ((options & pe_print_rsconly)) { - if (options & pe_print_suppres_nl) { - /* nothing */ - } else { - fprintf(out->dest, "\n"); - } - } else if (g_list_length(rsc->running_on) > 1) { - GListPtr gIter = rsc->running_on; + s = native_output_string(rsc, name, node, options, target_role); + out->list_item(out, NULL, "%s", s); + free(s); - fprintf(out->dest, "["); + if (is_set(options, pe_print_details)) { + GHashTableIter iter; + gpointer key, value; - for (; gIter != NULL; gIter = gIter->next) { - node_t *n = (node_t *) gIter->data; - fprintf(out->dest, " %s", n->details->uname); + out->begin_list(out, NULL, NULL, "Options"); + g_hash_table_iter_init(&iter, rsc->parameters); + while (g_hash_table_iter_next(&iter, &key, &value)) { + out->list_item(out, NULL, "Option: %s = %s", (char *) key, (char *) value); } - fprintf(out->dest, " ]"); - if (options & pe_print_suppres_nl) { - /* nothing */ - } else { - fprintf(out->dest, "\n"); - } - } - - if (options & pe_print_details) { - g_hash_table_foreach(rsc->parameters, pe__native_output_attr_text, out); + out->end_list(out); } - if (options & pe_print_dev) { + if (is_set(options, pe_print_dev)) { GHashTableIter iter; node_t *n = NULL; - fprintf(out->dest, "%s\t(%s%svariant=%s, priority=%f)", pre_text, - is_set(rsc->flags, pe_rsc_provisional) ? "provisional, " : "", - is_set(rsc->flags, pe_rsc_runnable) ? "" : "non-startable, ", - crm_element_name(rsc->xml), (double)rsc->priority); - fprintf(out->dest, "%s\tAllowed Nodes", pre_text); + out->begin_list(out, NULL, NULL, "Allowed Nodes"); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { - fprintf(out->dest, "%s\t * %s %d", pre_text, n->details->uname, n->weight); + out->list_item(out, NULL, "%s %d", n->details->uname, n->weight); } + out->end_list(out); } - if (options & pe_print_max_details) { + if (is_set(options, pe_print_max_details)) { GHashTableIter iter; node_t *n = NULL; - fprintf(out->dest, "%s\t=== Allowed Nodes\n", pre_text); + out->begin_list(out, NULL, NULL, "=== Allowed Nodes"); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { pe__output_node(n, FALSE, out); } + out->end_list(out); } } void common_print(resource_t * rsc, const char *pre_text, const char *name, node_t *node, long options, void *print_data) { const char *desc = NULL; const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); const char *target_role = NULL; enum rsc_role_e role = native_displayable_role(rsc); int offset = 0; int flagOffset = 0; char buffer[LINE_MAX]; char flagBuffer[LINE_MAX]; 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) && is_not_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 (pre_text == NULL && (options & pe_print_printf)) { pre_text = " "; } if (options & pe_print_xml) { native_print_xml(rsc, pre_text, options, print_data); return; } if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { node = NULL; } if (options & pe_print_html) { if (is_not_set(rsc->flags, pe_rsc_managed)) { status_print(""); } else if (is_set(rsc->flags, pe_rsc_failed)) { status_print(""); } else if (rsc->variant == pe_native && (rsc->running_on == NULL)) { status_print(""); } else if (g_list_length(rsc->running_on) > 1) { status_print(""); } else if (is_set(rsc->flags, pe_rsc_failure_ignored)) { status_print(""); } else { status_print(""); } } if(pre_text) { offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", pre_text); } offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", name); offset += snprintf(buffer + offset, LINE_MAX - offset, "\t(%s", class); if (is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); offset += snprintf(buffer + offset, LINE_MAX - offset, "::%s", prov); } offset += snprintf(buffer + offset, LINE_MAX - offset, ":%s):\t", kind); if(is_set(rsc->flags, pe_rsc_orphan)) { offset += snprintf(buffer + offset, LINE_MAX - offset, " ORPHANED "); } if(role > RSC_ROLE_SLAVE && is_set(rsc->flags, pe_rsc_failed)) { offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED %s", role2text(role)); } else if(is_set(rsc->flags, pe_rsc_failed)) { offset += snprintf(buffer + offset, LINE_MAX - offset, "FAILED"); } else { const char *rsc_state = native_displayable_state(rsc, options); offset += snprintf(buffer + offset, LINE_MAX - offset, "%s", rsc_state); } if(node) { offset += snprintf(buffer + offset, LINE_MAX - offset, " %s", node->details->uname); if (node->details->online == FALSE && node->details->unclean) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%sUNCLEAN", comma_if(flagOffset)); } } if (options & pe_print_pending) { const char *pending_task = native_pending_task(rsc); if (pending_task) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%s%s", comma_if(flagOffset), pending_task); } } if (target_role) { enum rsc_role_e target_role_e = text2role(target_role); /* Ignore target role Started, as it is the default anyways * (and would also allow a Master to be Master). * Show if target role limits our abilities. */ if (target_role_e == RSC_ROLE_STOPPED) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%sdisabled", comma_if(flagOffset)); rsc->cluster->disabled_resources++; } else if (is_set(uber_parent(rsc)->flags, pe_rsc_promotable) && target_role_e == RSC_ROLE_SLAVE) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%starget-role:%s", comma_if(flagOffset), target_role); rsc->cluster->disabled_resources++; } } if (is_set(rsc->flags, pe_rsc_block)) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%sblocked", comma_if(flagOffset)); rsc->cluster->blocked_resources++; } else if (is_not_set(rsc->flags, pe_rsc_managed)) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%sunmanaged", comma_if(flagOffset)); } if(is_set(rsc->flags, pe_rsc_failure_ignored)) { flagOffset += snprintf(flagBuffer + flagOffset, LINE_MAX - flagOffset, "%sfailure ignored", comma_if(flagOffset)); } if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { desc = crm_element_value(rsc->xml, XML_ATTR_DESC); } CRM_LOG_ASSERT(offset > 0); if(flagOffset > 0) { status_print("%s (%s)%s%s", buffer, flagBuffer, desc?" ":"", desc?desc:""); } else { status_print("%s%s%s", buffer, desc?" ":"", desc?desc:""); } #if CURSES_ENABLED if ((options & pe_print_rsconly) || g_list_length(rsc->running_on) > 1) { /* Done */ } else if (options & pe_print_ncurses) { /* coverity[negative_returns] False positive */ move(-1, 0); } #endif if (options & pe_print_html) { status_print(" "); } if ((options & pe_print_rsconly)) { } else if (g_list_length(rsc->running_on) > 1) { GListPtr 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) { node_t *n = (node_t *) gIter->data; counter++; if (options & pe_print_html) { status_print("
      • \n%s", n->details->uname); } else if ((options & pe_print_printf) || (options & pe_print_ncurses)) { status_print(" %s", n->details->uname); } else if ((options & pe_print_log)) { status_print("\t%d : %s", counter, n->details->uname); } else { status_print("%s", n->details->uname); } 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"); } if (options & pe_print_details) { struct print_data_s pdata; pdata.options = options; pdata.print_data = print_data; g_hash_table_foreach(rsc->parameters, native_print_attr, &pdata); } if (options & pe_print_dev) { GHashTableIter iter; node_t *n = NULL; status_print("%s\t(%s%svariant=%s, priority=%f)", pre_text, is_set(rsc->flags, pe_rsc_provisional) ? "provisional, " : "", is_set(rsc->flags, pe_rsc_runnable) ? "" : "non-startable, ", crm_element_name(rsc->xml), (double)rsc->priority); status_print("%s\tAllowed Nodes", pre_text); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { status_print("%s\t * %s %d", pre_text, n->details->uname, n->weight); } } if (options & pe_print_max_details) { GHashTableIter iter; node_t *n = NULL; status_print("%s\t=== Allowed Nodes\n", pre_text); g_hash_table_iter_init(&iter, rsc->allowed_nodes); while (g_hash_table_iter_next(&iter, NULL, (void **)&n)) { print_node("\t", n, FALSE); } } } void native_print(resource_t * rsc, const char *pre_text, long options, void *print_data) { 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); } int pe__resource_xml(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); 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, options); long is_print_pending = options & pe_print_pending; long is_print_dev = options & pe_print_dev; char ra_name[LINE_MAX]; char *nodes_running_on = NULL; char *priority = NULL; int rc = 0; CRM_ASSERT(rsc->variant == pe_native); /* resource information. */ sprintf(ra_name, "%s%s%s:%s", class, prov ? "::" : "", prov ? prov : "" , crm_element_value(rsc->xml, XML_ATTR_TYPE)); nodes_running_on = crm_itoa(g_list_length(rsc->running_on)); priority = crm_ftoa(rsc->priority); rc = pe__name_and_nvpairs_xml(out, true, "resource", 16 , "id", rsc_printable_id(rsc) , "resource_agent", ra_name , "role", rsc_state , "target_role", (rsc->meta ? g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE) : NULL) , "active", BOOL2STR(rsc->fns->active(rsc, TRUE)) , "orphaned", BOOL2STR(is_set(rsc->flags, pe_rsc_orphan)) , "blocked", BOOL2STR(is_set(rsc->flags, pe_rsc_block)) , "managed", BOOL2STR(is_set(rsc->flags, pe_rsc_managed)) , "failed", BOOL2STR(is_set(rsc->flags, pe_rsc_failed)) , "failure_ignored", BOOL2STR(is_set(rsc->flags, pe_rsc_failure_ignored)) , "nodes_running_on", nodes_running_on , "pending", (is_print_pending ? native_pending_task(rsc) : NULL) , "provisional", (is_print_dev ? BOOL2STR(is_set(rsc->flags, pe_rsc_provisional)) : NULL) , "runnable", (is_print_dev ? BOOL2STR(is_set(rsc->flags, pe_rsc_runnable)) : NULL) , "priority", (is_print_dev ? priority : NULL) , "variant", (is_print_dev ? crm_element_name(rsc->xml) : NULL)); free(priority); free(nodes_running_on); CRM_ASSERT(rc == 0); if (rsc->running_on != NULL) { GListPtr gIter = rsc->running_on; for (; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; rc = pe__name_and_nvpairs_xml(out, false, "node", 3 , "name", node->details->uname , "id", node->details->id , "cached", BOOL2STR(node->details->online)); CRM_ASSERT(rc == 0); } } pcmk__output_xml_pop_parent(out); return rc; } int pe__resource_html(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); node_t *node = pe__current_node(rsc); 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; } pe__common_output_html(out, rsc, rsc_printable_id(rsc), node, options); return 0; } int pe__resource_text(pcmk__output_t *out, va_list args) { long options = va_arg(args, int); resource_t *rsc = va_arg(args, resource_t *); - const char *pre_text = va_arg(args, char *); node_t *node = pe__current_node(rsc); 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; } - pe__common_output_text(out, rsc, pre_text, rsc_printable_id(rsc), node, options); + pe__common_output_text(out, rsc, rsc_printable_id(rsc), node, options); return 0; } void native_free(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 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 known, 1 = running, 2 = running or pending * * \return If list contains only one node, that node */ pe_node_t * native_location(const pe_resource_t *rsc, GList **list, int current) { node_t *one = NULL; GListPtr result = NULL; if (rsc->children) { GListPtr gIter = rsc->children; for (; gIter != NULL; gIter = gIter->next) { resource_t *child = (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) { GListPtr gIter = result; for (; gIter != NULL; gIter = gIter->next) { node_t *node = (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(GListPtr rsc_list, GHashTable * rsc_table, GHashTable * active_table) { GListPtr gIter = rsc_list; for (; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (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 (is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider)) { const char *prov = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER); offset += snprintf(buffer + offset, LINE_MAX - offset, "::%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) { GListPtr gIter2 = rsc->running_on; for (; gIter2 != NULL; gIter2 = gIter2->next) { node_t *node = (node_t *) gIter2->data; GHashTable *node_table = NULL; if (node->details->unclean == FALSE && node->details->online == FALSE) { continue; } node_table = g_hash_table_lookup(active_table, node->details->uname); if (node_table == NULL) { node_table = crm_str_table_new(); 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); } } void print_rscs_brief(GListPtr rsc_list, const char *pre_text, long options, void *print_data, gboolean print_all) { GHashTable *rsc_table = crm_str_table_new(); GHashTable *active_table = g_hash_table_new_full(crm_str_hash, g_str_equal, 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; } } void -pe__rscs_brief_output_text(pcmk__output_t *out, GListPtr rsc_list, const char *pre_text, - long options, gboolean print_all) -{ - GHashTable *rsc_table = crm_str_table_new(); - GHashTable *active_table = g_hash_table_new_full(crm_str_hash, g_str_equal, - 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 (print_all) { - fprintf(out->dest, "%s%d/%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", - *active_counter, - rsc_counter ? *rsc_counter : 0, type, - (*active_counter > 0) && node_name ? node_name : ""); - } else { - fprintf(out->dest, "%s%d\t(%s):\tActive %s\n", pre_text ? pre_text : "", - *active_counter, type, - (*active_counter > 0) && node_name ? node_name : ""); - } - } - - if (print_all && active_counter_all == 0) { - fprintf(out->dest, "%s%d/%d\t(%s):\tActive\n", pre_text ? pre_text : "", - active_counter_all, - rsc_counter ? *rsc_counter : 0, type); - } - } - - if (rsc_table) { - g_hash_table_destroy(rsc_table); - rsc_table = NULL; - } - if (active_table) { - g_hash_table_destroy(active_table); - active_table = NULL; - } -} - -void -pe__rscs_brief_output_html(pcmk__output_t *out, GListPtr rsc_list, long options, gboolean print_all) +pe__rscs_brief_output(pcmk__output_t *out, GListPtr rsc_list, long options, gboolean print_all) { GHashTable *rsc_table = crm_str_table_new(); GHashTable *active_table = g_hash_table_new_full(crm_str_hash, g_str_equal, 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 (print_all) { 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 : ""); } } if (print_all && active_counter_all == 0) { out->list_item(out, NULL, " %d/%d\t(%s):\tActive", active_counter_all, rsc_counter ? *rsc_counter : 0, type); } } if (rsc_table) { g_hash_table_destroy(rsc_table); rsc_table = NULL; } if (active_table) { g_hash_table_destroy(active_table); active_table = NULL; } } diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index b0b9dcb764..f94f96e4bc 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1,191 +1,244 @@ /* * Copyright 2019 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 int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name , size_t pairs_count, ...) { xmlNodePtr xml_node = NULL; va_list args; CRM_ASSERT(tag_name != NULL); xml_node = pcmk__output_xml_peek_parent(out); CRM_ASSERT(xml_node != NULL); xml_node = is_list ? create_xml_node(xml_node, tag_name) : xmlNewChild(xml_node, NULL, (pcmkXmlStr) tag_name, NULL); va_start(args, pairs_count); while(pairs_count--) { const char *param_name = va_arg(args, const char *); const char *param_value = va_arg(args, const char *); if (param_name && param_value) { xmlSetProp(xml_node, (pcmkXmlStr)param_name, (pcmkXmlStr)param_value); } }; va_end(args); if (is_list) { pcmk__output_xml_push_parent(out, xml_node); } return 0; } static int pe__group_xml(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); GListPtr gIter = rsc->children; char *count = crm_itoa(g_list_length(gIter)); int rc = pe__name_and_nvpairs_xml(out, true, "group", 2 , "id", rsc->id , "number_resources", count); free(count); CRM_ASSERT(rc == 0); for (; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (resource_t *) gIter->data; out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); } pcmk__output_xml_pop_parent(out); return rc; } static int pe__group_html(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); out->begin_list(out, NULL, NULL, "Resource Group: %s", rsc->id); if (options & pe_print_brief) { - pe__rscs_brief_output_html(out, rsc->children, options, TRUE); + pe__rscs_brief_output(out, rsc->children, options, TRUE); } else { for (GListPtr gIter = rsc->children; gIter; gIter = gIter->next) { resource_t *child_rsc = (resource_t *) gIter->data; - - pcmk__output_create_xml_node(out, "li"); out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); - pcmk__output_xml_pop_parent(out); } } out->end_list(out); return 0; } static int pe__group_text(pcmk__output_t *out, va_list args) { long options = va_arg(args, long); resource_t *rsc = va_arg(args, resource_t *); - const char *pre_text = va_arg(args, char *); - char *child_text = NULL; - - if (pre_text == NULL) { - pre_text = " "; - } - child_text = crm_concat(pre_text, " ", ' '); + out->begin_list(out, NULL, NULL, "Resource Group: %s", rsc->id); - fprintf(out->dest, "%sResource Group: %s", pre_text, rsc->id); if (options & pe_print_brief) { - pe__rscs_brief_output_text(out, rsc->children, child_text, options, TRUE); + pe__rscs_brief_output(out, rsc->children, options, TRUE); } else { for (GListPtr gIter = rsc->children; gIter; gIter = gIter->next) { resource_t *child_rsc = (resource_t *) gIter->data; - out->message(out, crm_element_name(child_rsc->xml), options, child_rsc, child_text); + out->message(out, crm_element_name(child_rsc->xml), options, child_rsc); } } + out->end_list(out); + + return 0; +} + +static int +pe__ticket_html(pcmk__output_t *out, va_list args) { + ticket_t *ticket = va_arg(args, ticket_t *); + + if (ticket->last_granted > -1) { + char *time = pcmk_format_named_time("last-granted", ticket->last_granted); + out->list_item(out, NULL, "%s:\t%s%s %s", ticket->id, + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : "", + time); + free(time); + } else { + out->list_item(out, NULL, "%s:\t%s%s", ticket->id, + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : ""); + } + + return 0; +} + +static int +pe__ticket_text(pcmk__output_t *out, va_list args) { + ticket_t *ticket = va_arg(args, ticket_t *); + + if (ticket->last_granted > -1) { + char *time = pcmk_format_named_time("last-granted", ticket->last_granted); + out->list_item(out, ticket->id, "\t%s%s %s", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : "", + time); + free(time); + } else { + out->list_item(out, ticket->id, "\t%s%s", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : ""); + } + + return 0; +} + +static int +pe__ticket_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = NULL; + + ticket_t *ticket = va_arg(args, ticket_t *); + + node = pcmk__output_create_xml_node(out, "ticket"); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) ticket->id); + xmlSetProp(node, (pcmkXmlStr) "status", (pcmkXmlStr) (ticket->granted ? "granted" : "revoked")); + xmlSetProp(node, (pcmkXmlStr) "standby", (pcmkXmlStr) (ticket->standby ? "true" : "false")); + + if (ticket->last_granted > -1) { + xmlSetProp(node, (pcmkXmlStr) "last-granted", + (pcmkXmlStr) crm_now_string(&ticket->last_granted)); + } - free(child_text); return 0; } static pcmk__message_entry_t fmt_functions[] = { { "bundle", "xml", pe__bundle_xml }, { "bundle", "html", pe__bundle_html }, { "bundle", "text", pe__bundle_text }, { "clone", "xml", pe__clone_xml }, { "clone", "html", pe__clone_html }, { "clone", "text", pe__clone_text }, { "group", "xml", pe__group_xml }, { "group", "html", pe__group_html }, { "group", "text", pe__group_text }, { "primitive", "xml", pe__resource_xml }, { "primitive", "html", pe__resource_html }, { "primitive", "text", pe__resource_text }, + { "ticket", "html", pe__ticket_html }, + { "ticket", "text", pe__ticket_text }, + { "ticket", "xml", pe__ticket_xml }, { NULL, NULL, NULL } }; void pe__register_messages(pcmk__output_t *out) { pcmk__register_messages(out, fmt_functions); } void pe__output_node(node_t *node, gboolean details, pcmk__output_t *out) { if (node == NULL) { crm_trace(""); return; } CRM_ASSERT(node->details); crm_trace("%sNode %s: (weight=%d, fixed=%s)", node->details->online ? "" : "Unavailable/Unclean ", node->details->uname, node->weight, node->fixed ? "True" : "False"); if (details) { char *pe_mutable = strdup("\t\t"); GListPtr gIter = node->details->running_rsc; crm_trace("\t\t===Node Attributes"); g_hash_table_foreach(node->details->attrs, print_str_str, pe_mutable); free(pe_mutable); crm_trace("\t\t=== Resources"); for (; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (resource_t *) gIter->data; pe__output_resource(LOG_TRACE, rsc, FALSE, out); } } } void pe__output_resource(int log_level, resource_t *rsc, gboolean details, pcmk__output_t *out) { long options = pe_print_log | pe_print_pending; if (rsc == NULL) { do_crm_log(log_level - 1, ""); return; } if (details) { options |= pe_print_details; } out->message(out, crm_element_name(rsc->xml), options, rsc); } diff --git a/tools/Makefile.am b/tools/Makefile.am index d43a544dc4..5a7b3825bd 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,158 +1,158 @@ # # Copyright 2004-2019 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/Makefile.common if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h crm_resource.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif noinst_SCRIPTS = pcmk_simtimes EXTRA_DIST = crm_mon.sysconfig \ crm_mon.8.inc \ crm_node.8.inc \ stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see Makefile.common). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la -crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_print.c crm_mon_runtime.c +crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_output.c crm_mon_print.c crm_mon_runtime.c crm_mon_xml.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c crm_resource_ban.c crm_resource_runtime.c crm_resource_print.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 10ad532caf..20c15729ce 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -1,1837 +1,1854 @@ /* * Copyright 2004-2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* crm_ends_with_ext */ #include #include #include #include #include #include #include #include #include #include #include #include #include "crm_mon.h" #define SUMMARY "Provides a summary of cluster's current state.\n\n" \ "Outputs varying levels of detail in a number of different formats." /* * Definitions indicating which items to print */ static unsigned int show = mon_show_default; /* * Definitions indicating how to output */ static mon_output_format_t output_format = mon_output_unset; -static char *output_filename = NULL; /* if sending output to a file, its name */ - /* other globals */ static GMainLoop *mainloop = NULL; static guint timer_id = 0; static mainloop_timer_t *refresh_timer = NULL; static pe_working_set_t *mon_data_set = NULL; static cib_t *cib = NULL; static stonith_t *st = NULL; static xmlNode *current_cib = NULL; static pcmk__common_args_t *args = NULL; static pcmk__output_t *out = NULL; static GOptionContext *context = NULL; /* FIXME allow, detect, and correctly interpret glob pattern or regex? */ const char *print_neg_location_prefix = ""; static time_t last_refresh = 0; crm_trigger_t *refresh_trigger = NULL; static pcmk__supported_format_t formats[] = { #if CURSES_ENABLED CRM_MON_SUPPORTED_FORMAT_CURSES, #endif PCMK__SUPPORTED_FORMAT_HTML, PCMK__SUPPORTED_FORMAT_NONE, PCMK__SUPPORTED_FORMAT_TEXT, - PCMK__SUPPORTED_FORMAT_XML, + CRM_MON_SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; /* Define exit codes for monitoring-compatible output * For nagios plugins, the possibilities are * OK=0, WARN=1, CRIT=2, and UNKNOWN=3 */ #define MON_STATUS_WARN CRM_EX_ERROR #define MON_STATUS_CRIT CRM_EX_INVALID_PARAM #define MON_STATUS_UNKNOWN CRM_EX_UNIMPLEMENT_FEATURE struct { int reconnect_msec; int fence_history_level; gboolean daemonize; gboolean show_bans; char *pid_file; char *external_agent; char *external_recipient; unsigned int mon_ops; } options = { .reconnect_msec = 5000, .fence_history_level = 1, .mon_ops = mon_op_default }; static void clean_up_connections(void); static crm_exit_t clean_up(crm_exit_t exit_code); static void crm_diff_update(const char *event, xmlNode * msg); static gboolean mon_refresh_display(gpointer user_data); static int cib_connect(gboolean full); static void mon_st_callback_event(stonith_t * st, stonith_event_t * e); static void mon_st_callback_display(stonith_t * st, stonith_event_t * e); static void kick_refresh(gboolean data_updated); static gboolean as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (args->output_ty != NULL) { free(args->output_ty); } args->output_ty = strdup("html"); output_format = mon_output_cgi; options.mon_ops |= mon_op_one_shot; return TRUE; } static gboolean as_html_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (args->output_ty != NULL) { free(args->output_ty); } + if (args->output_dest != NULL) { + free(args->output_dest); + } + + if (optarg != NULL) { + args->output_dest = strdup(optarg); + } + args->output_ty = strdup("html"); output_format = mon_output_html; - output_filename = strdup(optarg); umask(S_IWGRP | S_IWOTH); return TRUE; } static gboolean as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (args->output_ty != NULL) { free(args->output_ty); } args->output_ty = strdup("text"); output_format = mon_output_monitor; options.mon_ops |= mon_op_one_shot; return TRUE; } static gboolean as_xml_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (args->output_ty != NULL) { free(args->output_ty); } args->output_ty = strdup("xml"); output_format = mon_output_legacy_xml; options.mon_ops |= mon_op_one_shot; return TRUE; } static gboolean fence_history_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { int rc = crm_atoi(optarg, "2"); if (rc == -1 || rc > 3) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3"); return FALSE; } else { options.fence_history_level = rc; } return TRUE; } static gboolean group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_group_by_node; return TRUE; } static gboolean hide_headers_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show &= ~mon_show_headers; return TRUE; } static gboolean inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_inactive_resources; return TRUE; } static gboolean no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { output_format = mon_output_plain; return TRUE; } static gboolean one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_one_shot; return TRUE; } static gboolean print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_print_brief; return TRUE; } static gboolean print_clone_detail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_print_clone_detail; return TRUE; } static gboolean print_pending_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_print_pending; return TRUE; } static gboolean print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_print_timing; show |= mon_show_operations; return TRUE; } static gboolean reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { int rc = crm_get_msec(optarg); if (rc == -1) { g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg); return FALSE; } else { options.reconnect_msec = crm_get_msec(optarg); } return TRUE; } static gboolean show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show |= mon_show_attributes; return TRUE; } static gboolean show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show |= mon_show_bans; if (optarg != NULL) { print_neg_location_prefix = optarg; } return TRUE; } static gboolean show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show |= mon_show_failcounts; return TRUE; } static gboolean show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show |= mon_show_operations; return TRUE; } static gboolean show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { show |= mon_show_tickets; return TRUE; } static gboolean use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { setenv("CIB_file", optarg, 1); options.mon_ops |= mon_op_one_shot; return TRUE; } static gboolean watch_fencing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { options.mon_ops |= mon_op_watch_fencing; return TRUE; } #define INDENT " " /* *INDENT-OFF* */ static GOptionEntry addl_entries[] = { { "interval", 'i', 0, G_OPTION_ARG_CALLBACK, reconnect_cb, "Update frequency in seconds", "SECONDS" }, { "one-shot", '1', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, one_shot_cb, "Display the cluster status once on the console and exit", NULL }, { "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb, "Disable the use of ncurses", NULL }, { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize, - "Run in the background as a daemon", + "Run in the background as a daemon.\n" + INDENT "Requires at least one of --output-to and --external-agent.", NULL }, { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &options.pid_file, "(Advanced) Daemon pid file location", "FILE" }, { "external-agent", 'E', 0, G_OPTION_ARG_FILENAME, &options.external_agent, "A program to run when resource operations take place", "FILE" }, { "external-recipient", 'e', 0, G_OPTION_ARG_STRING, &options.external_recipient, "A recipient for your program (assuming you want the program to send something to someone).", "RCPT" }, { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb, NULL, NULL }, { NULL } }; static GOptionEntry display_entries[] = { { "group-by-node", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, group_by_node_cb, "Group resources by node", NULL }, { "inactive", 'r', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, inactive_resources_cb, "Display inactive resources", NULL }, { "failcounts", 'f', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_failcounts_cb, "Display resource fail counts", NULL }, { "operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_operations_cb, "Display resource operation history", NULL }, { "timing-details", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_timing_cb, "Display resource operation history with timing details", NULL }, { "tickets", 'c', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_tickets_cb, "Display cluster tickets", NULL }, { "watch-fencing", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, watch_fencing_cb, "Listen for fencing events. For use with --external-agent", NULL }, { "fence-history", 'm', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, fence_history_cb, "Show fence history:\n" INDENT "0=off, 1=failures and pending (default without option),\n" INDENT "2=add successes (default without value for option),\n" INDENT "3=show full history without reduction to most recent of each flavor", "LEVEL" }, { "neg-locations", 'L', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, show_bans_cb, "Display negative location constraints [optionally filtered by id prefix]", NULL }, { "show-node-attributes", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_attributes_cb, "Display node attributes", NULL }, { "hide-headers", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, hide_headers_cb, "Hide all headers", NULL }, { "show-detail", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_clone_detail_cb, "Show more details (node IDs, individual clone instances)", NULL }, { "brief", 'b', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_brief_cb, "Brief output", NULL }, { "pending", 'j', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, print_pending_cb, "Display pending state if 'record-pending' is enabled", NULL }, { NULL } }; static GOptionEntry mode_entries[] = { { "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb, "Write cluster status to the named HTML file", "FILE" }, { "as-xml", 'X', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_xml_cb, "Write cluster status as XML to stdout. This will enable one-shot mode.", NULL }, { "web-cgi", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_cgi_cb, "Web mode with output suitable for CGI (preselected when run as *.cgi)", NULL }, { "simple-status", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, as_simple_cb, "Display the cluster status once as a simple one line output (suitable for nagios)", NULL }, { NULL } }; /* *INDENT-ON* */ static gboolean mon_timer_popped(gpointer data) { int rc = pcmk_ok; #if CURSES_ENABLED if (output_format == mon_output_console) { clear(); refresh(); } #endif if (timer_id > 0) { g_source_remove(timer_id); timer_id = 0; } print_as(output_format, "Reconnecting...\n"); rc = cib_connect(TRUE); if (rc != pcmk_ok) { timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL); } return FALSE; } static void mon_cib_connection_destroy(gpointer user_data) { print_as(output_format, "Connection to the cluster-daemons terminated\n"); if (refresh_timer != NULL) { /* we'll trigger a refresh after reconnect */ mainloop_timer_stop(refresh_timer); } if (timer_id) { /* we'll trigger a new reconnect-timeout at the end */ g_source_remove(timer_id); timer_id = 0; } if (st) { /* the client API won't properly reconnect notifications * if they are still in the table - so remove them */ st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT); st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE); st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY); if (st->state != stonith_disconnected) { st->cmds->disconnect(st); } } if (cib) { cib->cmds->signoff(cib); timer_id = g_timeout_add(options.reconnect_msec, mon_timer_popped, NULL); } return; } /* * Mainloop signal handler. */ static void mon_shutdown(int nsig) { clean_up(CRM_EX_OK); } #if CURSES_ENABLED static sighandler_t ncurses_winch_handler; static void mon_winresize(int nsig) { static int not_done; int lines = 0, cols = 0; if (!not_done++) { if (ncurses_winch_handler) /* the original ncurses WINCH signal handler does the * magic of retrieving the new window size; * otherwise, we'd have to use ioctl or tgetent */ (*ncurses_winch_handler) (SIGWINCH); getmaxyx(stdscr, lines, cols); resizeterm(lines, cols); mainloop_set_trigger(refresh_trigger); } not_done--; } #endif static int cib_connect(gboolean full) { int rc = pcmk_ok; static gboolean need_pass = TRUE; CRM_CHECK(cib != NULL, return -EINVAL); if (getenv("CIB_passwd") != NULL) { need_pass = FALSE; } if (is_set(options.mon_ops, mon_op_fence_connect) && st == NULL) { st = stonith_api_new(); } if (is_set(options.mon_ops, mon_op_fence_connect) && st != NULL && st->state == stonith_disconnected) { rc = st->cmds->connect(st, crm_system_name, NULL); if (rc == pcmk_ok) { crm_trace("Setting up stonith callbacks"); if (is_set(options.mon_ops, mon_op_watch_fencing)) { st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, mon_st_callback_event); st->cmds->register_notification(st, T_STONITH_NOTIFY_FENCE, mon_st_callback_event); } else { st->cmds->register_notification(st, T_STONITH_NOTIFY_DISCONNECT, mon_st_callback_display); st->cmds->register_notification(st, T_STONITH_NOTIFY_HISTORY, mon_st_callback_display); } } } if (cib->state != cib_connected_query && cib->state != cib_connected_command) { crm_trace("Connecting to the CIB"); if ((output_format == mon_output_console) && need_pass && (cib->variant == cib_remote)) { need_pass = FALSE; print_as(output_format, "Password:"); } rc = cib->cmds->signon(cib, crm_system_name, cib_query); if (rc != pcmk_ok) { return rc; } rc = cib->cmds->query(cib, NULL, ¤t_cib, cib_scope_local | cib_sync_call); if (rc == pcmk_ok) { mon_refresh_display(&output_format); } if (rc == pcmk_ok && full) { if (rc == pcmk_ok) { rc = cib->cmds->set_connection_dnotify(cib, mon_cib_connection_destroy); if (rc == -EPROTONOSUPPORT) { print_as (output_format, "Notification setup not supported, won't be able to reconnect after failure"); if (output_format == mon_output_console) { sleep(2); } rc = pcmk_ok; } } if (rc == pcmk_ok) { cib->cmds->del_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update); rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, crm_diff_update); } if (rc != pcmk_ok) { print_as(output_format, "Notification setup failed, could not monitor CIB actions"); if (output_format == mon_output_console) { sleep(2); } clean_up_connections(); } } } return rc; } #if CURSES_ENABLED static const char * get_option_desc(char c) { const char *desc = "No help available"; for (GOptionEntry *entry = display_entries; entry != NULL; entry++) { if (entry->short_name == c) { desc = entry->description; break; } } return desc; } #define print_option_help(output_format, option, condition) \ print_as(output_format, "%c %c: \t%s\n", ((condition)? '*': ' '), option, get_option_desc(option)); static gboolean detect_user_input(GIOChannel *channel, GIOCondition condition, gpointer user_data) { int c; gboolean config_mode = FALSE; while (1) { /* Get user input */ c = getchar(); switch (c) { case 'm': if (!options.fence_history_level) { options.mon_ops |= mon_op_fence_history; options.mon_ops |= mon_op_fence_connect; if (st == NULL) { mon_cib_connection_destroy(NULL); } } show ^= mon_show_fence_history; break; case 'c': show ^= mon_show_tickets; break; case 'f': show ^= mon_show_failcounts; break; case 'n': options.mon_ops ^= mon_op_group_by_node; break; case 'o': show ^= mon_show_operations; if ((show & mon_show_operations) == 0) { options.mon_ops &= ~mon_op_print_timing; } break; case 'r': options.mon_ops ^= mon_op_inactive_resources; break; case 'R': options.mon_ops ^= mon_op_print_clone_detail; break; case 't': options.mon_ops ^= mon_op_print_timing; if (is_set(options.mon_ops, mon_op_print_timing)) { show |= mon_show_operations; } break; case 'A': show ^= mon_show_attributes; break; case 'L': show ^= mon_show_bans; break; case 'D': /* If any header is shown, clear them all, otherwise set them all */ if (show & mon_show_headers) { show &= ~mon_show_headers; } else { show |= mon_show_headers; } break; case 'b': options.mon_ops ^= mon_op_print_brief; break; case 'j': options.mon_ops ^= mon_op_print_pending; break; case '?': config_mode = TRUE; break; default: goto refresh; } if (!config_mode) goto refresh; blank_screen(); print_as(output_format, "Display option change mode\n"); print_as(output_format, "\n"); print_option_help(output_format, 'c', show & mon_show_tickets); print_option_help(output_format, 'f', show & mon_show_failcounts); print_option_help(output_format, 'n', is_set(options.mon_ops, mon_op_group_by_node)); print_option_help(output_format, 'o', show & mon_show_operations); print_option_help(output_format, 'r', is_set(options.mon_ops, mon_op_inactive_resources)); print_option_help(output_format, 't', is_set(options.mon_ops, mon_op_print_timing)); print_option_help(output_format, 'A', show & mon_show_attributes); print_option_help(output_format, 'L', show & mon_show_bans); print_option_help(output_format, 'D', (show & mon_show_headers) == 0); print_option_help(output_format, 'R', is_set(options.mon_ops, mon_op_print_clone_detail)); print_option_help(output_format, 'b', is_set(options.mon_ops, mon_op_print_brief)); print_option_help(output_format, 'j', is_set(options.mon_ops, mon_op_print_pending)); print_option_help(output_format, 'm', (show & mon_show_fence_history)); print_as(output_format, "\n"); print_as(output_format, "Toggle fields via field letter, type any other key to return"); } refresh: mon_refresh_display(NULL); return TRUE; } #endif // Basically crm_signal_handler(SIGCHLD, SIG_IGN) plus the SA_NOCLDWAIT flag static void avoid_zombies() { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); if (sigemptyset(&sa.sa_mask) < 0) { crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno)); return; } sa.sa_handler = SIG_IGN; sa.sa_flags = SA_RESTART|SA_NOCLDWAIT; if (sigaction(SIGCHLD, &sa, NULL) < 0) { crm_warn("Cannot avoid zombies: %s", pcmk_strerror(errno)); } } static GOptionContext * build_arg_context(pcmk__common_args_t *args) { GOptionContext *context = NULL; GOptionEntry extra_prog_entries[] = { { "quiet", 'Q', 0, G_OPTION_ARG_NONE, &(args->quiet), "Be less descriptive in output.", NULL }, { NULL } }; const char *examples = "Examples:\n\n" "Display the cluster status on the console with updates as they occur:\n\n" "\tcrm_mon\n\n" "Display the cluster status on the console just once then exit:\n\n" "\tcrm_mon -1\n\n" "Display your cluster status, group resources by node, and include inactive resources in the list:\n\n" "\tcrm_mon --group-by-node --inactive\n\n" "Start crm_mon as a background daemon and have it write the cluster status to an HTML file:\n\n" "\tcrm_mon --daemonize --output-as html --output-to /path/to/docroot/filename.html\n\n" "Start crm_mon and export the current cluster status as XML to stdout, then exit:\n\n" "\tcrm_mon --output-as xml\n"; context = pcmk__build_arg_context(args, "console (default), html, text, xml"); pcmk__add_main_args(context, extra_prog_entries); g_option_context_set_description(context, examples); pcmk__add_arg_group(context, "mode", "Mode Options (mutually exclusive):", "Show mode options", mode_entries); pcmk__add_arg_group(context, "display", "Display Options:", "Show display options", display_entries); pcmk__add_arg_group(context, "additional", "Additional Options:", "Show additional options", addl_entries); return context; } +/* If certain format options were specified, we want to set some extra + * options. We can just process these like they were given on the + * command line. + */ +static void +add_output_args() { + GError *error = NULL; + + if (output_format == mon_output_plain) { + if (!pcmk__force_args(context, &error, "%s --output-fancy", g_get_prgname())) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } + } else if (output_format == mon_output_html) { + if (!pcmk__force_args(context, &error, "%s --output-meta-refresh %d --output-title \"Cluster Status\"", + g_get_prgname(), options.reconnect_msec/1000)) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } + } else if (output_format == mon_output_cgi) { + if (!pcmk__force_args(context, &error, "%s --output-cgi --output-title \"Cluster Status\"", g_get_prgname())) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } + } else if (output_format == mon_output_xml) { + if (!pcmk__force_args(context, &error, "%s --output-simple-list", g_get_prgname())) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } + } else if (output_format == mon_output_legacy_xml) { + output_format = mon_output_xml; + if (!pcmk__force_args(context, &error, "%s --output-legacy-xml", g_get_prgname())) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } + } +} + +/* Which output format to use could come from two places: The --as-xml + * style arguments we gave in mode_entries above, or the formatted output + * arguments added by pcmk__register_formats. If the latter were used, + * output_format will be mon_output_unset. + * + * Call the callbacks as if those older style arguments were provided so + * the various things they do get done. + */ +static void +reconcile_output_format(pcmk__common_args_t *args) { + gboolean retval = TRUE; + GError *error = NULL; + + if (output_format != mon_output_unset) { + return; + } + + if (safe_str_eq(args->output_ty, "html")) { + char *dest = NULL; + + if (args->output_dest != NULL) { + dest = strdup(args->output_dest); + } + + retval = as_html_cb("h", dest, NULL, &error); + free(dest); + } else if (safe_str_eq(args->output_ty, "text")) { + retval = no_curses_cb("N", NULL, NULL, &error); + } else if (safe_str_eq(args->output_ty, "xml")) { + if (args->output_ty != NULL) { + free(args->output_ty); + } + + args->output_ty = strdup("xml"); + output_format = mon_output_xml; + options.mon_ops |= mon_op_one_shot; + } else if (is_set(options.mon_ops, mon_op_one_shot)) { + if (args->output_ty != NULL) { + free(args->output_ty); + } + + args->output_ty = strdup("text"); + output_format = mon_output_plain; + } else { + /* Neither old nor new arguments were given, so set the default. */ + if (args->output_ty != NULL) { + free(args->output_ty); + } + + args->output_ty = strdup("console"); + output_format = mon_output_console; + } + + if (!retval) { + fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); + clean_up(CRM_EX_USAGE); + } +} + int main(int argc, char **argv) { int rc = pcmk_ok; char **processed_args = NULL; GError *error = NULL; args = pcmk__new_common_args(SUMMARY); context = build_arg_context(args); pcmk__register_formats(context, formats); options.pid_file = strdup("/tmp/ClusterMon.pid"); crm_log_cli_init("crm_mon"); // Avoid needing to wait for subprocesses forked for -E/--external-agent avoid_zombies(); if (crm_ends_with_ext(argv[0], ".cgi") == TRUE) { output_format = mon_output_cgi; options.mon_ops |= mon_op_one_shot; } processed_args = pcmk__cmdline_preproc(argc, argv, "ehimpxEL"); if (!g_option_context_parse_strv(context, &processed_args, &error)) { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); return clean_up(CRM_EX_USAGE); } for (int i = 0; i < args->verbosity; i++) { crm_bump_log_level(argc, argv); } - /* Which output format to use could come from two places: The --as-xml - * style arguments we gave in mode_entries above, or the formatted output - * arguments added by pcmk__register_formats. If the latter were used, - * output_format will be mon_output_unset. - * - * Call the callbacks as if those older style arguments were provided so - * the various things they do get done. - */ - if (output_format == mon_output_unset) { - gboolean retval = TRUE; + if (!args->version) { + if (args->quiet) { + show &= ~mon_show_times; + } - g_clear_error(&error); + if (is_set(options.mon_ops, mon_op_watch_fencing)) { + options.mon_ops |= mon_op_fence_connect; + /* don't moan as fence_history_level == 1 is default */ + options.fence_history_level = 0; + } - /* NOTE: There is no way to specify CGI mode or simple mode with --output-as. - * Those will need to get handled eventually, at which point something else - * will need to be added to this block. + /* create the cib-object early to be able to do further + * decisions based on the cib-source */ - if (safe_str_eq(args->output_ty, "html")) { - retval = as_html_cb("h", args->output_dest, NULL, &error); - } else if (safe_str_eq(args->output_ty, "text")) { - retval = no_curses_cb("N", NULL, NULL, &error); - } else if (safe_str_eq(args->output_ty, "xml")) { - if (args->output_ty != NULL) { - free(args->output_ty); + cib = cib_new(); + + if (cib == NULL) { + rc = -EINVAL; + } else { + switch (cib->variant) { + + case cib_native: + /* cib & fencing - everything available */ + break; + + case cib_file: + /* Don't try to connect to fencing as we + * either don't have a running cluster or + * the fencing-information would possibly + * not match the cib data from a file. + * As we don't expect cib-updates coming + * in enforce one-shot. */ + options.fence_history_level = 0; + options.mon_ops |= mon_op_one_shot; + break; + + case cib_remote: + /* updates coming in but no fencing */ + options.fence_history_level = 0; + break; + + case cib_undefined: + case cib_database: + default: + /* something is odd */ + rc = -EINVAL; + crm_err("Invalid cib-source"); + break; } + } - args->output_ty = strdup("xml"); - output_format = mon_output_xml; - options.mon_ops |= mon_op_one_shot; - } else if (is_set(options.mon_ops, mon_op_one_shot)) { - if (args->output_ty != NULL) { - free(args->output_ty); + switch (options.fence_history_level) { + case 3: + options.mon_ops |= mon_op_fence_full_history; + /* fall through to next lower level */ + case 2: + show |= mon_show_fence_history; + /* fall through to next lower level */ + case 1: + options.mon_ops |= mon_op_fence_history; + options.mon_ops |= mon_op_fence_connect; + break; + default: + break; + } + + if (is_set(options.mon_ops, mon_op_one_shot)) { + if (output_format == mon_output_console) { + output_format = mon_output_plain; } - args->output_ty = strdup("text"); - output_format = mon_output_plain; - } else { - /* Neither old nor new arguments were given, so set the default. */ - if (args->output_ty != NULL) { - free(args->output_ty); + } else if (options.daemonize) { + if ((output_format == mon_output_console) || (output_format == mon_output_plain)) { + output_format = mon_output_none; } + crm_enable_stderr(FALSE); - args->output_ty = strdup("console"); - output_format = mon_output_console; - } + if ((args->output_dest == NULL || safe_str_eq(args->output_dest, "-")) && !options.external_agent) { + printf("--daemonize requires at least one of --output-to and --external-agent\n"); + return clean_up(CRM_EX_USAGE); + } - if (!retval) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); + if (cib) { + /* to be on the safe side don't have cib-object around + * when we are forking + */ + cib_delete(cib); + cib = NULL; + crm_make_daemon(crm_system_name, TRUE, options.pid_file); + cib = cib_new(); + if (cib == NULL) { + rc = -EINVAL; + } + /* otherwise assume we've got the same cib-object we've just destroyed + * in our parent + */ + } + + + } else if (output_format == mon_output_console) { +#if CURSES_ENABLED + crm_enable_stderr(FALSE); +#else + options.mon_ops |= mon_op_one_shot; + output_format = mon_output_plain; + printf("Defaulting to one-shot mode\n"); + printf("You need to have curses available at compile time to enable console mode\n"); +#endif } } - /* If certain format options were specified, we want to set some extra - * options. We can just process these like they were given on the - * command line. - */ - g_clear_error(&error); + reconcile_output_format(args); + add_output_args(); - if (output_format == mon_output_plain) { - if (!pcmk__force_args(context, &error, "%s --output-fancy", g_get_prgname())) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); - } - } else if (output_format == mon_output_html) { - if (!pcmk__force_args(context, &error, "%s --output-meta-refresh %d --output-title \"Cluster Status\"", - g_get_prgname(), options.reconnect_msec/1000)) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); - } - } else if (output_format == mon_output_cgi) { - if (!pcmk__force_args(context, &error, "%s --output-cgi --output-title \"Cluster Status\"", g_get_prgname())) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); - } - } else if (output_format == mon_output_xml) { - if (!pcmk__force_args(context, &error, "%s --output-simple-list", g_get_prgname())) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); - } - } else if (output_format == mon_output_legacy_xml) { - output_format = mon_output_xml; - if (!pcmk__force_args(context, &error, "%s --output-legacy-xml", g_get_prgname())) { - fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); - return clean_up(CRM_EX_USAGE); - } + /* Create the output format - output_format must not be changed after this point. */ + if (args->version && output_format == mon_output_console) { + /* Use the text output format here if we are in curses mode but were given + * --version. Displaying version information uses printf, and then we + * immediately exit. We don't want to initialize curses for that. + */ + rc = pcmk__output_new(&out, "text", args->output_dest, argv); + } else { + rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); } - rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv); if (rc != 0) { fprintf(stderr, "Error creating output format %s: %s\n", args->output_ty, pcmk_strerror(rc)); return clean_up(CRM_EX_ERROR); } + crm_mon_register_messages(out); + pe__register_messages(out); stonith__register_messages(out); if (args->version) { - /* FIXME: For the moment, this won't do anything on XML or HTML formats - * because finish is not getting called. That's commented out in - * clean_up. - */ out->version(out, false); return clean_up(CRM_EX_OK); } - if (args->quiet) { - show &= ~mon_show_times; - } - - if (is_set(options.mon_ops, mon_op_watch_fencing)) { - options.mon_ops |= mon_op_fence_connect; - /* don't moan as fence_history_level == 1 is default */ - options.fence_history_level = 0; - } - - /* create the cib-object early to be able to do further - * decisions based on the cib-source - */ - cib = cib_new(); - - if (cib == NULL) { - rc = -EINVAL; - } else { - switch (cib->variant) { - - case cib_native: - /* cib & fencing - everything available */ - break; - - case cib_file: - /* Don't try to connect to fencing as we - * either don't have a running cluster or - * the fencing-information would possibly - * not match the cib data from a file. - * As we don't expect cib-updates coming - * in enforce one-shot. */ - options.fence_history_level = 0; - options.mon_ops |= mon_op_one_shot; - break; - - case cib_remote: - /* updates coming in but no fencing */ - options.fence_history_level = 0; - break; - - case cib_undefined: - case cib_database: - default: - /* something is odd */ - rc = -EINVAL; - crm_err("Invalid cib-source"); - break; - } - } - - switch (options.fence_history_level) { - case 3: - options.mon_ops |= mon_op_fence_full_history; - /* fall through to next lower level */ - case 2: - show |= mon_show_fence_history; - /* fall through to next lower level */ - case 1: - options.mon_ops |= mon_op_fence_history; - options.mon_ops |= mon_op_fence_connect; - break; - default: - break; - } - /* Extra sanity checks when in CGI mode */ if (output_format == mon_output_cgi) { - if (output_filename != NULL) { - fprintf(stderr, "CGI mode cannot be used with -h\n"); - return clean_up(CRM_EX_USAGE); - } else if (cib && cib->variant == cib_file) { + if (cib && cib->variant == cib_file) { fprintf(stderr, "CGI mode used with CIB file\n"); return clean_up(CRM_EX_USAGE); } else if (options.external_agent != NULL) { fprintf(stderr, "CGI mode cannot be used with --external-agent\n"); return clean_up(CRM_EX_USAGE); } else if (options.daemonize == TRUE) { fprintf(stderr, "CGI mode cannot be used with -d\n"); return clean_up(CRM_EX_USAGE); } } /* XML output always prints everything */ - if (output_format == mon_output_xml) { + if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) { show = mon_show_all; options.mon_ops |= mon_op_print_timing; } - if (is_set(options.mon_ops, mon_op_one_shot)) { - if (output_format == mon_output_console) { - output_format = mon_output_plain; - } - - } else if (options.daemonize) { - if ((output_format == mon_output_console) || (output_format == mon_output_plain)) { - output_format = mon_output_none; - } - crm_enable_stderr(FALSE); - - if ((output_format != mon_output_html) - && !options.external_agent) { - printf ("Looks like you forgot to specify one or more of: " - "--as-html, --external-agent\n"); - return clean_up(CRM_EX_USAGE); - } - - if (cib) { - /* to be on the safe side don't have cib-object around - * when we are forking - */ - cib_delete(cib); - cib = NULL; - crm_make_daemon(crm_system_name, TRUE, options.pid_file); - cib = cib_new(); - if (cib == NULL) { - rc = -EINVAL; - } - /* otherwise assume we've got the same cib-object we've just destroyed - * in our parent - */ - } - - - } else if (output_format == mon_output_console) { -#if CURSES_ENABLED - initscr(); - cbreak(); - noecho(); - crm_enable_stderr(FALSE); -#else - options.mon_ops |= mon_op_one_shot; - output_format = mon_output_plain; - printf("Defaulting to one-shot mode\n"); - printf("You need to have curses available at compile time to enable console mode\n"); -#endif - } - crm_info("Starting %s", crm_system_name); if (cib) { do { if (is_not_set(options.mon_ops, mon_op_one_shot)) { print_as(output_format ,"Waiting until cluster is available on this node ...\n"); } rc = cib_connect(is_not_set(options.mon_ops, mon_op_one_shot)); if (is_set(options.mon_ops, mon_op_one_shot)) { break; } else if (rc != pcmk_ok) { sleep(options.reconnect_msec / 1000); #if CURSES_ENABLED if (output_format == mon_output_console) { clear(); refresh(); } #endif } else { - if (output_format == mon_output_html) { - print_as(output_format, "Writing html to %s ...\n", output_filename); + if (output_format == mon_output_html && out->dest != stdout) { + printf("Writing html to %s ...\n", args->output_dest); } } } while (rc == -ENOTCONN); } if (rc != pcmk_ok) { if (output_format == mon_output_monitor) { printf("CLUSTER CRIT: Connection to cluster failed: %s\n", pcmk_strerror(rc)); return clean_up(MON_STATUS_CRIT); } else { if (rc == -ENOTCONN) { print_as(output_format ,"\nError: cluster is not available on this node\n"); } else { print_as(output_format ,"\nConnection to cluster failed: %s\n", pcmk_strerror(rc)); } } if (output_format == mon_output_console) { sleep(2); } return clean_up(crm_errno2exit(rc)); } if (is_set(options.mon_ops, mon_op_one_shot)) { return clean_up(CRM_EX_OK); } mainloop = g_main_loop_new(NULL, FALSE); mainloop_add_signal(SIGTERM, mon_shutdown); mainloop_add_signal(SIGINT, mon_shutdown); #if CURSES_ENABLED if (output_format == mon_output_console) { ncurses_winch_handler = crm_signal_handler(SIGWINCH, mon_winresize); if (ncurses_winch_handler == SIG_DFL || ncurses_winch_handler == SIG_IGN || ncurses_winch_handler == SIG_ERR) ncurses_winch_handler = NULL; g_io_add_watch(g_io_channel_unix_new(STDIN_FILENO), G_IO_IN, detect_user_input, NULL); } #endif refresh_trigger = mainloop_add_trigger(G_PRIORITY_LOW, mon_refresh_display, NULL); g_main_loop_run(mainloop); g_main_loop_unref(mainloop); crm_info("Exiting %s", crm_system_name); return clean_up(CRM_EX_OK); } #define mon_warn(output_format, mon_ops, fmt...) do { \ if (is_not_set(mon_ops, mon_op_has_warnings)) { \ print_as(output_format, "CLUSTER WARN:"); \ } else { \ print_as(output_format, ","); \ } \ print_as(output_format, fmt); \ mon_ops |= mon_op_has_warnings; \ } while(0) /*! * \internal * \brief Print one-line status suitable for use with monitoring software * * \param[in] data_set Working set of CIB state * \param[in] history List of stonith actions * * \note This function's output (and the return code when the program exits) * should conform to https://www.monitoring-plugins.org/doc/guidelines.html */ static void print_simple_status(pe_working_set_t * data_set, stonith_history_t *history, unsigned int mon_ops, mon_output_format_t output_format) { GListPtr gIter = NULL; int nodes_online = 0; int nodes_standby = 0; int nodes_maintenance = 0; if (data_set->dc_node == NULL) { mon_warn(output_format, mon_ops, " No DC"); } for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; if (node->details->standby && node->details->online) { nodes_standby++; } else if (node->details->maintenance && node->details->online) { nodes_maintenance++; } else if (node->details->online) { nodes_online++; } else { mon_warn(output_format, mon_ops, " offline node: %s", node->details->uname); } } if (is_not_set(mon_ops, mon_op_has_warnings)) { int nresources = count_resources(data_set, NULL); - print_as(output_format, "CLUSTER OK: %d node%s online", nodes_online, s_if_plural(nodes_online)); + printf("CLUSTER OK: %d node%s online", nodes_online, s_if_plural(nodes_online)); if (nodes_standby > 0) { - print_as(output_format, ", %d standby node%s", nodes_standby, s_if_plural(nodes_standby)); + printf(", %d standby node%s", nodes_standby, s_if_plural(nodes_standby)); } if (nodes_maintenance > 0) { - print_as(output_format, ", %d maintenance node%s", nodes_maintenance, s_if_plural(nodes_maintenance)); + printf(", %d maintenance node%s", nodes_maintenance, s_if_plural(nodes_maintenance)); } - print_as(output_format, ", %d resource%s configured", nresources, s_if_plural(nresources)); + printf(", %d resource%s configured", nresources, s_if_plural(nresources)); } - print_as(output_format, "\n"); + printf("\n"); } /*! * \internal * \brief Reduce the stonith-history * for successful actions we keep the last of every action-type & target * for failed actions we record as well who had failed * for actions in progress we keep full track * * \param[in] history List of stonith actions * */ static stonith_history_t * reduce_stonith_history(stonith_history_t *history) { stonith_history_t *new = history, *hp, *np; if (new) { hp = new->next; new->next = NULL; while (hp) { stonith_history_t *hp_next = hp->next; hp->next = NULL; for (np = new; ; np = np->next) { if ((hp->state == st_done) || (hp->state == st_failed)) { /* action not in progress */ if (safe_str_eq(hp->target, np->target) && safe_str_eq(hp->action, np->action) && (hp->state == np->state) && ((hp->state == st_done) || safe_str_eq(hp->delegate, np->delegate))) { /* purge older hp */ stonith_history_free(hp); break; } } if (!np->next) { np->next = hp; break; } } hp = hp_next; } } return new; } static int send_custom_trap(const char *node, const char *rsc, const char *task, int target_rc, int rc, int status, const char *desc) { pid_t pid; /*setenv needs chars, these are ints */ char *rc_s = crm_itoa(rc); char *status_s = crm_itoa(status); char *target_rc_s = crm_itoa(target_rc); crm_debug("Sending external notification to '%s' via '%s'", options.external_recipient, options.external_agent); if(rsc) { setenv("CRM_notify_rsc", rsc, 1); } if (options.external_recipient) { setenv("CRM_notify_recipient", options.external_recipient, 1); } setenv("CRM_notify_node", node, 1); setenv("CRM_notify_task", task, 1); setenv("CRM_notify_desc", desc, 1); setenv("CRM_notify_rc", rc_s, 1); setenv("CRM_notify_target_rc", target_rc_s, 1); setenv("CRM_notify_status", status_s, 1); pid = fork(); if (pid == -1) { crm_perror(LOG_ERR, "notification fork() failed."); } if (pid == 0) { /* crm_debug("notification: I am the child. Executing the nofitication program."); */ execl(options.external_agent, options.external_agent, NULL); exit(CRM_EX_ERROR); } crm_trace("Finished running custom notification program '%s'.", options.external_agent); free(target_rc_s); free(status_s); free(rc_s); return 0; } static void handle_rsc_op(xmlNode * xml, const char *node_id) { int rc = -1; int status = -1; int target_rc = -1; gboolean notify = TRUE; char *rsc = NULL; char *task = NULL; const char *desc = NULL; const char *magic = NULL; const char *id = NULL; const char *node = NULL; xmlNode *n = xml; xmlNode * rsc_op = xml; if(strcmp((const char*)xml->name, XML_LRM_TAG_RSC_OP) != 0) { xmlNode *cIter; for(cIter = xml->children; cIter; cIter = cIter->next) { handle_rsc_op(cIter, node_id); } return; } id = crm_element_value(rsc_op, XML_LRM_ATTR_TASK_KEY); if (id == NULL) { /* Compatibility with <= 1.1.5 */ id = ID(rsc_op); } magic = crm_element_value(rsc_op, XML_ATTR_TRANSITION_MAGIC); if (magic == NULL) { /* non-change */ return; } if (!decode_transition_magic(magic, NULL, NULL, NULL, &status, &rc, &target_rc)) { crm_err("Invalid event %s detected for %s", magic, id); return; } if (parse_op_key(id, &rsc, &task, NULL) == FALSE) { crm_err("Invalid event detected for %s", id); goto bail; } node = crm_element_value(rsc_op, XML_LRM_ATTR_TARGET); while (n != NULL && safe_str_neq(XML_CIB_TAG_STATE, TYPE(n))) { n = n->parent; } if(node == NULL && n) { node = crm_element_value(n, XML_ATTR_UNAME); } if (node == NULL && n) { node = ID(n); } if (node == NULL) { node = node_id; } if (node == NULL) { crm_err("No node detected for event %s (%s)", magic, id); goto bail; } /* look up where we expected it to be? */ desc = pcmk_strerror(pcmk_ok); if (status == PCMK_LRM_OP_DONE && target_rc == rc) { crm_notice("%s of %s on %s completed: %s", task, rsc, node, desc); if (rc == PCMK_OCF_NOT_RUNNING) { notify = FALSE; } } else if (status == PCMK_LRM_OP_DONE) { desc = services_ocf_exitcode_str(rc); crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc); } else { desc = services_lrm_status_str(status); crm_warn("%s of %s on %s failed: %s", task, rsc, node, desc); } if (notify && options.external_agent) { send_custom_trap(node, rsc, task, target_rc, rc, status, desc); } bail: free(rsc); free(task); } static gboolean mon_trigger_refresh(gpointer user_data) { mainloop_set_trigger(refresh_trigger); return FALSE; } #define NODE_PATT "/lrm[@id=" static char * get_node_from_xpath(const char *xpath) { char *nodeid = NULL; char *tmp = strstr(xpath, NODE_PATT); if(tmp) { tmp += strlen(NODE_PATT); tmp += 1; nodeid = strdup(tmp); tmp = strstr(nodeid, "\'"); CRM_ASSERT(tmp); tmp[0] = 0; } return nodeid; } static void crm_diff_update_v2(const char *event, xmlNode * msg) { xmlNode *change = NULL; xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT); for (change = __xml_first_child(diff); change != NULL; change = __xml_next(change)) { const char *name = NULL; const char *op = crm_element_value(change, XML_DIFF_OP); const char *xpath = crm_element_value(change, XML_DIFF_PATH); xmlNode *match = NULL; const char *node = NULL; if(op == NULL) { continue; } else if(strcmp(op, "create") == 0) { match = change->children; } else if(strcmp(op, "move") == 0) { continue; } else if(strcmp(op, "delete") == 0) { continue; } else if(strcmp(op, "modify") == 0) { match = first_named_child(change, XML_DIFF_RESULT); if(match) { match = match->children; } } if(match) { name = (const char *)match->name; } crm_trace("Handling %s operation for %s %p, %s", op, xpath, match, name); if(xpath == NULL) { /* Version field, ignore */ } else if(name == NULL) { crm_debug("No result for %s operation to %s", op, xpath); CRM_ASSERT(strcmp(op, "delete") == 0 || strcmp(op, "move") == 0); } else if(strcmp(name, XML_TAG_CIB) == 0) { xmlNode *state = NULL; xmlNode *status = first_named_child(match, XML_CIB_TAG_STATUS); for (state = __xml_first_child_element(status); state != NULL; state = __xml_next_element(state)) { node = crm_element_value(state, XML_ATTR_UNAME); if (node == NULL) { node = ID(state); } handle_rsc_op(state, node); } } else if(strcmp(name, XML_CIB_TAG_STATUS) == 0) { xmlNode *state = NULL; for (state = __xml_first_child_element(match); state != NULL; state = __xml_next_element(state)) { node = crm_element_value(state, XML_ATTR_UNAME); if (node == NULL) { node = ID(state); } handle_rsc_op(state, node); } } else if(strcmp(name, XML_CIB_TAG_STATE) == 0) { node = crm_element_value(match, XML_ATTR_UNAME); if (node == NULL) { node = ID(match); } handle_rsc_op(match, node); } else if(strcmp(name, XML_CIB_TAG_LRM) == 0) { node = ID(match); handle_rsc_op(match, node); } else if(strcmp(name, XML_LRM_TAG_RESOURCES) == 0) { char *local_node = get_node_from_xpath(xpath); handle_rsc_op(match, local_node); free(local_node); } else if(strcmp(name, XML_LRM_TAG_RESOURCE) == 0) { char *local_node = get_node_from_xpath(xpath); handle_rsc_op(match, local_node); free(local_node); } else if(strcmp(name, XML_LRM_TAG_RSC_OP) == 0) { char *local_node = get_node_from_xpath(xpath); handle_rsc_op(match, local_node); free(local_node); } else { crm_trace("Ignoring %s operation for %s %p, %s", op, xpath, match, name); } } } static void crm_diff_update_v1(const char *event, xmlNode * msg) { /* Process operation updates */ xmlXPathObject *xpathObj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_LRM_TAG_RSC_OP); int lpc = 0, max = numXpathResults(xpathObj); for (lpc = 0; lpc < max; lpc++) { xmlNode *rsc_op = getXpathResult(xpathObj, lpc); handle_rsc_op(rsc_op, NULL); } freeXpathObject(xpathObj); } static void crm_diff_update(const char *event, xmlNode * msg) { int rc = -1; static bool stale = FALSE; gboolean cib_updated = FALSE; xmlNode *diff = get_message_xml(msg, F_CIB_UPDATE_RESULT); print_dot(output_format); if (current_cib != NULL) { rc = xml_apply_patchset(current_cib, diff, TRUE); switch (rc) { case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(current_cib); current_cib = NULL; break; case pcmk_ok: cib_updated = TRUE; break; default: crm_notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); free_xml(current_cib); current_cib = NULL; } } if (current_cib == NULL) { crm_trace("Re-requesting the full cib"); cib->cmds->query(cib, NULL, ¤t_cib, cib_scope_local | cib_sync_call); } if (options.external_agent) { int format = 0; crm_element_value_int(diff, "format", &format); switch(format) { case 1: crm_diff_update_v1(event, msg); break; case 2: crm_diff_update_v2(event, msg); break; default: crm_err("Unknown patch format: %d", format); } } if (current_cib == NULL) { if(!stale) { print_as(output_format, "--- Stale data ---"); } stale = TRUE; return; } stale = FALSE; kick_refresh(cib_updated); } static gboolean mon_refresh_display(gpointer user_data) { xmlNode *cib_copy = copy_xml(current_cib); stonith_history_t *stonith_history = NULL; /* stdout for everything except the HTML case, which does a bunch of file * renaming. We'll handle changing stream in print_html_status. */ mon_state_t state = { .stream = stdout, .output_format = output_format, .out = out }; last_refresh = time(NULL); if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) { if (cib) { cib->cmds->signoff(cib); } print_as(output_format, "Upgrade failed: %s", pcmk_strerror(-pcmk_err_schema_validation)); if (output_format == mon_output_console) { sleep(2); } clean_up(CRM_EX_CONFIG); return FALSE; } /* get the stonith-history if there is evidence we need it */ while (is_set(options.mon_ops, mon_op_fence_history)) { if (st != NULL) { if (st->cmds->history(st, st_opt_sync_call, NULL, &stonith_history, 120)) { fprintf(stderr, "Critical: Unable to get stonith-history\n"); mon_cib_connection_destroy(NULL); } else { stonith_history = stonith__sort_history(stonith_history); if (is_not_set(options.mon_ops, mon_op_fence_full_history) && output_format != mon_output_xml) { stonith_history = reduce_stonith_history(stonith_history); } break; /* all other cases are errors */ } } else { fprintf(stderr, "Critical: No stonith-API\n"); } free_xml(cib_copy); print_as(output_format, "Reading stonith-history failed"); if (output_format == mon_output_console) { sleep(2); } return FALSE; } if (mon_data_set == NULL) { mon_data_set = pe_new_working_set(); CRM_ASSERT(mon_data_set != NULL); } mon_data_set->input = cib_copy; cluster_status(mon_data_set); /* Unpack constraints if any section will need them * (tickets may be referenced in constraints but not granted yet, * and bans need negative location constraints) */ if (show & (mon_show_bans | mon_show_tickets)) { xmlNode *cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, mon_data_set->input); unpack_constraints(cib_constraints, mon_data_set); } switch (output_format) { case mon_output_html: case mon_output_cgi: - if (print_html_status(&state, mon_data_set, output_filename, - stonith_history, options.mon_ops, show, - print_neg_location_prefix, options.reconnect_msec) != 0) { + if (print_html_status(&state, mon_data_set, stonith_history, + options.mon_ops, show, print_neg_location_prefix, + options.reconnect_msec) != 0) { fprintf(stderr, "Critical: Unable to output html file\n"); clean_up(CRM_EX_CANTCREAT); return FALSE; } break; case mon_output_legacy_xml: case mon_output_xml: print_xml_status(&state, mon_data_set, stonith_history, options.mon_ops, show, print_neg_location_prefix); break; case mon_output_monitor: print_simple_status(mon_data_set, stonith_history, options.mon_ops, output_format); if (is_set(options.mon_ops, mon_op_has_warnings)) { clean_up(MON_STATUS_WARN); return FALSE; } break; case mon_output_plain: case mon_output_console: print_status(&state, mon_data_set, stonith_history, options.mon_ops, show, print_neg_location_prefix); break; case mon_output_unset: case mon_output_none: break; } stonith_history_free(stonith_history); stonith_history = NULL; pe_reset_working_set(mon_data_set); return TRUE; } static void mon_st_callback_event(stonith_t * st, stonith_event_t * e) { if (st->state == stonith_disconnected) { /* disconnect cib as well and have everything reconnect */ mon_cib_connection_destroy(NULL); } else if (options.external_agent) { char *desc = crm_strdup_printf("Operation %s requested by %s for peer %s: %s (ref=%s)", e->operation, e->origin, e->target, pcmk_strerror(e->result), e->id); send_custom_trap(e->target, NULL, e->operation, pcmk_ok, e->result, 0, desc); free(desc); } } static void kick_refresh(gboolean data_updated) { static int updates = 0; time_t now = time(NULL); if (data_updated) { updates++; } if(refresh_timer == NULL) { refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL); } /* Refresh * - immediately if the last update was more than 5s ago * - every 10 cib-updates * - at most 2s after the last update */ if ((now - last_refresh) > (options.reconnect_msec / 1000)) { mainloop_set_trigger(refresh_trigger); mainloop_timer_stop(refresh_timer); updates = 0; } else if(updates >= 10) { mainloop_set_trigger(refresh_trigger); mainloop_timer_stop(refresh_timer); updates = 0; } else { mainloop_timer_start(refresh_timer); } } static void mon_st_callback_display(stonith_t * st, stonith_event_t * e) { if (st->state == stonith_disconnected) { /* disconnect cib as well and have everything reconnect */ mon_cib_connection_destroy(NULL); } else { print_dot(output_format); kick_refresh(TRUE); } } static void clean_up_connections(void) { if (cib != NULL) { cib->cmds->signoff(cib); cib_delete(cib); cib = NULL; } if (st != NULL) { if (st->state != stonith_disconnected) { st->cmds->remove_notification(st, T_STONITH_NOTIFY_DISCONNECT); st->cmds->remove_notification(st, T_STONITH_NOTIFY_FENCE); st->cmds->remove_notification(st, T_STONITH_NOTIFY_HISTORY); st->cmds->disconnect(st); } stonith_api_delete(st); st = NULL; } } /* * De-init ncurses, disconnect from the CIB manager, disconnect fencing, * deallocate memory and show usage-message if requested. * * We don't actually return, but nominally returning crm_exit_t allows a usage * like "return clean_up(exit_code);" which helps static analysis understand the * code flow. */ static crm_exit_t clean_up(crm_exit_t exit_code) { #if CURSES_ENABLED if (output_format == mon_output_console) { output_format = mon_output_plain; echo(); nocbreak(); endwin(); } #endif clean_up_connections(); - free(output_filename); free(options.pid_file); pe_free_working_set(mon_data_set); mon_data_set = NULL; if (exit_code == CRM_EX_USAGE) { if (output_format == mon_output_cgi) { fprintf(stdout, "Content-Type: text/plain\n" "Status: 500\n\n"); } else { fprintf(stderr, "%s", g_option_context_get_help(context, TRUE, NULL)); } } g_option_context_free(context); if (out != NULL) { - /* FIXME: When we are ready to enable formatted output, uncomment - * the following line: - */ - /* out->finish(out, exit_code, true, NULL); */ + out->finish(out, exit_code, true, NULL); pcmk__output_free(out); } crm_exit(exit_code); } diff --git a/tools/crm_mon.h b/tools/crm_mon.h index 0ebc3a1e20..6bf886f089 100644 --- a/tools/crm_mon.h +++ b/tools/crm_mon.h @@ -1,131 +1,136 @@ /* * Copyright 2019 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 /* Never display node attributes whose name starts with one of these prefixes */ #define FILTER_STR { CRM_FAIL_COUNT_PREFIX, CRM_LAST_FAILURE_PREFIX, \ "shutdown", "terminate", "standby", "probe_complete", \ "#", NULL } /* Convenience macro for prettifying output (e.g. "node" vs "nodes") */ #define s_if_plural(i) (((i) == 1)? "" : "s") #if CURSES_ENABLED # define print_dot(output_format) if (output_format == mon_output_console) { \ printw("."); \ clrtoeol(); \ refresh(); \ } else { \ fprintf(stdout, "."); \ } #else # define print_dot(output_format) fprintf(stdout, "."); #endif #if CURSES_ENABLED # define print_as(output_format, fmt, args...) if (output_format == mon_output_console) { \ printw(fmt, ##args); \ clrtoeol(); \ refresh(); \ } else { \ fprintf(stdout, fmt, ##args); \ } #else # define print_as(output_format, fmt, args...) fprintf(stdout, fmt, ##args); #endif typedef enum mon_output_format_e { mon_output_unset, mon_output_none, mon_output_monitor, mon_output_plain, mon_output_console, mon_output_xml, mon_output_legacy_xml, mon_output_html, mon_output_cgi } mon_output_format_t; #define mon_show_times (0x0001U) #define mon_show_stack (0x0002U) #define mon_show_dc (0x0004U) #define mon_show_count (0x0008U) #define mon_show_nodes (0x0010U) #define mon_show_resources (0x0020U) #define mon_show_attributes (0x0040U) #define mon_show_failcounts (0x0080U) #define mon_show_operations (0x0100U) #define mon_show_tickets (0x0200U) #define mon_show_bans (0x0400U) #define mon_show_fence_history (0x0800U) #define mon_show_headers (mon_show_times | mon_show_stack | mon_show_dc \ | mon_show_count) #define mon_show_default (mon_show_headers | mon_show_nodes \ | mon_show_resources) #define mon_show_all (mon_show_default | mon_show_attributes \ | mon_show_failcounts | mon_show_operations \ | mon_show_tickets | mon_show_bans \ | mon_show_fence_history) #define mon_op_group_by_node (0x0001U) #define mon_op_inactive_resources (0x0002U) #define mon_op_one_shot (0x0004U) #define mon_op_has_warnings (0x0008U) #define mon_op_print_timing (0x0010U) #define mon_op_watch_fencing (0x0020U) #define mon_op_fence_history (0x0040U) #define mon_op_fence_full_history (0x0080U) #define mon_op_fence_connect (0x0100U) #define mon_op_print_brief (0x0200U) #define mon_op_print_pending (0x0400U) #define mon_op_print_clone_detail (0x0800U) #define mon_op_default (mon_op_print_pending) typedef struct { FILE *stream; mon_output_format_t output_format; pcmk__output_t *out; } mon_state_t; void print_status(mon_state_t *state, pe_working_set_t *data_set, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, const char *prefix); void print_xml_status(mon_state_t *state, pe_working_set_t *data_set, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, const char *prefix); int print_html_status(mon_state_t *state, pe_working_set_t *data_set, - const char *filename, stonith_history_t *stonith_history, - unsigned int mon_ops, unsigned int show, const char *prefix, + stonith_history_t *stonith_history, unsigned int mon_ops, + unsigned int show, const char *prefix, unsigned int reconnect_msec); GList *append_attr_list(GList *attr_list, char *name); void blank_screen(void); int count_resources(pe_working_set_t *data_set, resource_t *rsc); void crm_mon_get_parameters(resource_t *rsc, pe_working_set_t *data_set); const char *get_cluster_stack(pe_working_set_t *data_set); char *get_node_display_name(node_t *node, unsigned int mon_ops); int get_resource_display_options(unsigned int mon_ops, mon_output_format_t output_format); +void crm_mon_register_messages(pcmk__output_t *out); + pcmk__output_t *crm_mon_mk_curses_output(char **argv); void curses_indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); void curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0); #if CURSES_ENABLED extern GOptionEntry crm_mon_curses_output_entries[]; #define CRM_MON_SUPPORTED_FORMAT_CURSES { "console", crm_mon_mk_curses_output, crm_mon_curses_output_entries } #endif + +pcmk__output_t *crm_mon_mk_xml_output(char **argv); +#define CRM_MON_SUPPORTED_FORMAT_XML { "xml", crm_mon_mk_xml_output, pcmk__xml_output_entries } diff --git a/tools/crm_mon_curses.c b/tools/crm_mon_curses.c index 538feb4c4c..bc32e6fef4 100644 --- a/tools/crm_mon_curses.c +++ b/tools/crm_mon_curses.c @@ -1,288 +1,292 @@ /* * Copyright 2019 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 "crm_mon.h" #if CURSES_ENABLED GOptionEntry crm_mon_curses_output_entries[] = { { NULL } }; typedef struct curses_list_data_s { unsigned int len; char *singular_noun; char *plural_noun; } curses_list_data_t; typedef struct private_data_s { GQueue *parent_q; } private_data_t; static void curses_free_priv(pcmk__output_t *out) { private_data_t *priv = out->priv; if (priv == NULL) { return; } g_queue_free(priv->parent_q); free(priv); } static bool curses_init(pcmk__output_t *out) { private_data_t *priv = NULL; /* If curses_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } else { out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; } priv->parent_q = g_queue_new(); + initscr(); + cbreak(); + noecho(); + return true; } static void curses_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { } static void curses_reset(pcmk__output_t *out) { CRM_ASSERT(out->priv != NULL); curses_free_priv(out); curses_init(out); } static void curses_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { if (proc_stdout != NULL) { printw("%s\n", proc_stdout); } if (proc_stderr != NULL) { printw("%s\n", proc_stderr); } clrtoeol(); refresh(); } /* curses_version is defined in curses.h, so we can't use that name here. * Note that this function prints out via text, not with curses. */ static void curses_ver(pcmk__output_t *out, bool extended) { if (extended) { printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { printf("Pacemaker %s\n", PACEMAKER_VERSION); printf("Written by Andrew Beekhof\n"); } } G_GNUC_PRINTF(2, 3) static void curses_err_info(pcmk__output_t *out, const char *format, ...) { va_list ap; /* Informational output does not get indented, to separate it from other * potentially indented list output. */ va_start(ap, format); vw_printw(stdscr, format, ap); va_end(ap); /* Add a newline. */ addch('\n'); clrtoeol(); refresh(); } static void curses_output_xml(pcmk__output_t *out, const char *name, const char *buf) { private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); curses_indented_printf(out, "%s", buf); } G_GNUC_PRINTF(4, 5) static void curses_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { private_data_t *priv = out->priv; curses_list_data_t *new_list = NULL; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); curses_indented_vprintf(out, format, ap); printw(":\n"); va_end(ap); new_list = calloc(1, sizeof(curses_list_data_t)); new_list->len = 0; new_list->singular_noun = singular_noun == NULL ? NULL : strdup(singular_noun); new_list->plural_noun = plural_noun == NULL ? NULL : strdup(plural_noun); g_queue_push_tail(priv->parent_q, new_list); } G_GNUC_PRINTF(3, 4) static void curses_list_item(pcmk__output_t *out, const char *id, const char *format, ...) { private_data_t *priv = out->priv; va_list ap; CRM_ASSERT(priv != NULL); va_start(ap, format); if (id != NULL) { curses_indented_printf(out, "%s: ", id); vw_printw(stdscr, format, ap); } else { curses_indented_vprintf(out, format, ap); } addch('\n'); va_end(ap); ((curses_list_data_t *) g_queue_peek_tail(priv->parent_q))->len++; } static void curses_end_list(pcmk__output_t *out) { private_data_t *priv = out->priv; curses_list_data_t *node = NULL; CRM_ASSERT(priv != NULL); node = g_queue_pop_tail(priv->parent_q); if (node->singular_noun != NULL && node->plural_noun != NULL) { if (node->len == 1) { curses_indented_printf(out, "%d %s found\n", node->len, node->singular_noun); } else { curses_indented_printf(out, "%d %s found\n", node->len, node->plural_noun); } } free(node); } pcmk__output_t * crm_mon_mk_curses_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "console"; retval->request = g_strjoinv(" ", argv); retval->supports_quiet = true; retval->init = curses_init; retval->free_priv = curses_free_priv; retval->finish = curses_finish; retval->reset = curses_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = curses_subprocess_output; retval->version = curses_ver; retval->err = curses_err_info; retval->info = curses_err_info; retval->output_xml = curses_output_xml; retval->begin_list = curses_begin_list; retval->list_item = curses_list_item; retval->end_list = curses_end_list; return retval; } G_GNUC_PRINTF(2, 0) void curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { int level = 0; private_data_t *priv = out->priv; CRM_ASSERT(priv != NULL); level = g_queue_get_length(priv->parent_q); for (int i = 0; i < level; i++) { - addch('\t'); + printw(" "); } if (level > 0) { printw("* "); } vw_printw(stdscr, format, args); clrtoeol(); refresh(); } G_GNUC_PRINTF(2, 3) void curses_indented_printf(pcmk__output_t *out, const char *format, ...) { va_list ap; va_start(ap, format); curses_indented_vprintf(out, format, ap); va_end(ap); } #else pcmk__output_t * crm_mon_mk_curses_output(char **argv) { /* curses was disabled in the build, so fall back to text. */ return pcmk__mk_text_output(argv); } G_GNUC_PRINTF(2, 0) void curses_indented_vprintf(pcmk__output_t *out, const char *format, va_list args) { return; } G_GNUC_PRINTF(2, 3) void curses_indented_printf(pcmk__output_t *out, const char *format, va_list args) { return; } #endif diff --git a/tools/crm_mon_output.c b/tools/crm_mon_output.c new file mode 100644 index 0000000000..13c14fb857 --- /dev/null +++ b/tools/crm_mon_output.c @@ -0,0 +1,1188 @@ +/* + * Copyright 2019 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crm_mon.h" + +static char * +time_t_string(time_t when) { + crm_time_t *crm_when = crm_time_new(NULL); + char *buf = NULL; + + crm_time_set_timet(crm_when, &when); + buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); + crm_time_free(crm_when); + return buf; +} + +static char * +failed_action_string(xmlNodePtr xml_op) { + const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); + int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0"); + int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0"); + const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); + + time_t last_change = 0; + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change) == pcmk_ok) { + char *time = time_t_string(last_change); + char *buf = crm_strdup_printf("%s on %s '%s' (%d): call=%s, status='%s', exitreason='%s', last-rc-change='%s', queued=%sms, exec=%sms", + op_key ? op_key : ID(xml_op), + crm_element_value(xml_op, XML_ATTR_UNAME), + services_ocf_exitcode_str(rc), rc, + crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + services_lrm_status_str(status), + exit_reason ? exit_reason : "none", + time, + crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), + crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); + + free(time); + return buf; + } else { + return crm_strdup_printf("%s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'", + op_key ? op_key : ID(xml_op), + crm_element_value(xml_op, XML_ATTR_UNAME), + services_ocf_exitcode_str(rc), rc, + crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + services_lrm_status_str(status), + exit_reason ? exit_reason : "none"); + } +} + +static char * +last_changed_string(const char *last_written, const char *user, + const char *client, const char *origin) { + if (last_written != NULL || user != NULL || client != NULL || origin != NULL) { + return crm_strdup_printf("%s%s%s%s%s%s%s", + last_written ? last_written : "", + user ? " by " : "", + user ? user : "", + client ? " via " : "", + client ? client : "", + origin ? " origin " : "", + origin ? origin : ""); + } else { + return strdup(""); + } +} + +static char * +op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s, + int rc, unsigned int mon_ops) { + const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); + char *interval_str = NULL; + char *buf = NULL; + + if (interval_ms_s && safe_str_neq(interval_ms_s, "0")) { + char *pair = pcmk_format_nvpair("interval", interval_ms_s, "ms"); + interval_str = crm_strdup_printf(" %s", pair); + free(pair); + } + + if (is_set(mon_ops, mon_op_print_timing)) { + char *last_change_str = NULL; + char *last_run_str = NULL; + char *exec_str = NULL; + char *queue_str = NULL; + + const char *value = NULL; + + time_t epoch = 0; + + if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &epoch) == pcmk_ok) + && (epoch > 0)) { + char *time = pcmk_format_named_time(XML_RSC_OP_LAST_CHANGE, epoch); + last_change_str = crm_strdup_printf(" %s", time); + free(time); + } + + if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_RUN, &epoch) == pcmk_ok) + && (epoch > 0)) { + char *time = pcmk_format_named_time(XML_RSC_OP_LAST_RUN, epoch); + last_run_str = crm_strdup_printf(" %s", time); + free(time); + } + + value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); + if (value) { + char *pair = pcmk_format_nvpair(XML_RSC_OP_T_EXEC, value, "ms"); + exec_str = crm_strdup_printf(" %s", pair); + free(pair); + } + + value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); + if (value) { + char *pair = pcmk_format_nvpair(XML_RSC_OP_T_QUEUE, value, "ms"); + queue_str = crm_strdup_printf(" %s", pair); + free(pair); + } + + buf = crm_strdup_printf("(%s) %s:%s%s%s%s%s rc=%d (%s)", call, task, + interval_str ? interval_str : "", + last_change_str ? last_change_str : "", + last_run_str ? last_run_str : "", + exec_str ? exec_str : "", + queue_str ? queue_str : "", + rc, services_ocf_exitcode_str(rc)); + + if (last_change_str) { + free(last_change_str); + } + + if (last_run_str) { + free(last_run_str); + } + + if (exec_str) { + free(exec_str); + } + + if (queue_str) { + free(queue_str); + } + } else { + buf = crm_strdup_printf("(%s) %s:%s", call, task, + interval_str ? interval_str : ""); + } + + if (interval_str) { + free(interval_str); + } + + return buf; +} + +static char * +resource_history_string(resource_t *rsc, const char *rsc_id, gboolean all, + int failcount, time_t last_failure) { + char *buf = NULL; + + if (rsc == NULL) { + buf = crm_strdup_printf("%s: orphan", rsc_id); + } else if (all || failcount || last_failure > 0) { + char *failcount_s = failcount > 0 ? crm_strdup_printf(" %s=%d", CRM_FAIL_COUNT_PREFIX, failcount) : strdup(""); + char *lastfail_s = last_failure > 0 ? crm_strdup_printf(" %s=%s", CRM_LAST_FAILURE_PREFIX, + crm_now_string(&last_failure)) : strdup(""); + + buf = crm_strdup_printf("%s: migration-threshold=%d%s%s", + rsc_id, rsc->migration_threshold, failcount_s, lastfail_s); + free(failcount_s); + free(lastfail_s); + } else { + buf = crm_strdup_printf("%s:", rsc_id); + } + + return buf; +} + +static int +ban_html(pcmk__output_t *out, va_list args) { + pe_node_t *pe_node = va_arg(args, pe_node_t *); + pe__location_t *location = va_arg(args, pe__location_t *); + unsigned int mon_ops = va_arg(args, unsigned int); + + char *node_name = get_node_display_name(pe_node, mon_ops); + char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s", + location->id, location->rsc_lh->id, + location->role_filter == RSC_ROLE_MASTER ? "as Master " : "", + node_name); + + pcmk__output_create_html_node(out, "li", NULL, NULL, buf); + + free(node_name); + free(buf); + return 0; +} + +static int +ban_text(pcmk__output_t *out, va_list args) { + pe_node_t *pe_node = va_arg(args, pe_node_t *); + pe__location_t *location = va_arg(args, pe__location_t *); + unsigned int mon_ops = va_arg(args, unsigned int); + + char *node_name = get_node_display_name(pe_node, mon_ops); + out->list_item(out, NULL, "%s\tprevents %s from running %son %s", + location->id, location->rsc_lh->id, + location->role_filter == RSC_ROLE_MASTER ? "as Master " : "", + node_name); + + free(node_name); + return 0; +} + +static int +ban_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "ban"); + pe_node_t *pe_node = va_arg(args, pe_node_t *); + pe__location_t *location = va_arg(args, pe__location_t *); + + char *weight_s = crm_itoa(pe_node->weight); + + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) location->id); + xmlSetProp(node, (pcmkXmlStr) "resource", (pcmkXmlStr) location->rsc_lh->id); + xmlSetProp(node, (pcmkXmlStr) "node", (pcmkXmlStr) pe_node->details->uname); + xmlSetProp(node, (pcmkXmlStr) "weight", (pcmkXmlStr) weight_s); + xmlSetProp(node, (pcmkXmlStr) "master_only", + (pcmkXmlStr) (location->role_filter == RSC_ROLE_MASTER ? "true" : "false")); + + free(weight_s); + return 0; +} + +static int +cluster_counts_html(pcmk__output_t *out, va_list args) { + xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li"); + xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li"); + + unsigned int nnodes = va_arg(args, unsigned int); + unsigned int nresources = va_arg(args, unsigned int); + unsigned int ndisabled = va_arg(args, unsigned int); + unsigned int nblocked = va_arg(args, unsigned int); + + char *nnodes_str = crm_strdup_printf("%d node%s configured", nnodes, s_if_plural(nnodes)); + + pcmk_create_html_node(nodes_node, "span", NULL, NULL, nnodes_str); + free(nnodes_str); + + if (ndisabled && nblocked) { + char *s = crm_strdup_printf("%d resource%s configured (%d ", nresources, s_if_plural(nresources), ndisabled); + pcmk_create_html_node(resources_node, "span", NULL, NULL, s); + free(s); + + pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED"); + + s = crm_strdup_printf(", %d ", nblocked); + pcmk_create_html_node(resources_node, "span", NULL, NULL, s); + free(s); + + pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED"); + pcmk_create_html_node(resources_node, "span", NULL, NULL, " from starting due to failure)"); + } else if (ndisabled && !nblocked) { + char *s = crm_strdup_printf("%d resource%s configured (%d ", nresources, s_if_plural(nresources), ndisabled); + pcmk_create_html_node(resources_node, "span", NULL, NULL, s); + free(s); + + pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED"); + pcmk_create_html_node(resources_node, "span", NULL, NULL, ")"); + } else if (!ndisabled && nblocked) { + char *s = crm_strdup_printf("%d resource%s configured (%d ", nresources, s_if_plural(nresources), nblocked); + pcmk_create_html_node(resources_node, "span", NULL, NULL, s); + free(s); + + pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED"); + pcmk_create_html_node(resources_node, "span", NULL, NULL, " from starting due to failure)"); + } else { + char *s = crm_strdup_printf("%d resource%s configured", nresources, s_if_plural(nresources)); + pcmk_create_html_node(resources_node, "span", NULL, NULL, s); + free(s); + } + + return 0; +} + +static int +cluster_counts_text(pcmk__output_t *out, va_list args) { + unsigned int nnodes = va_arg(args, unsigned int); + unsigned int nresources = va_arg(args, unsigned int); + unsigned int ndisabled = va_arg(args, unsigned int); + unsigned int nblocked = va_arg(args, unsigned int); + + out->list_item(out, NULL, "%d node%s configured", nnodes, s_if_plural(nnodes)); + + if (ndisabled && nblocked) { + out->list_item(out, NULL, "%d resource%s configured (%d DISABLED, %d BLOCKED from starting due to failure", + nresources, s_if_plural(nresources), ndisabled, nblocked); + } else if (ndisabled && !nblocked) { + out->list_item(out, NULL, "%d resource%s configured (%d DISABLED)", + nresources, s_if_plural(nresources), ndisabled); + } else if (!ndisabled && nblocked) { + out->list_item(out, NULL, "%d resource%s configured (%d BLOCKED from starting due to failure)", + nresources, s_if_plural(nresources), nblocked); + } else { + out->list_item(out, NULL, "%d resource%s configured", nresources, s_if_plural(nresources)); + } + + return 0; +} + +static int +cluster_counts_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured"); + xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured"); + + unsigned int nnodes = va_arg(args, unsigned int); + unsigned int nresources = va_arg(args, unsigned int); + unsigned int ndisabled = va_arg(args, unsigned int); + unsigned int nblocked = va_arg(args, unsigned int); + + char *s = crm_itoa(nnodes); + xmlSetProp(nodes_node, (pcmkXmlStr) "number", (pcmkXmlStr) s); + free(s); + + s = crm_itoa(nresources); + xmlSetProp(resources_node, (pcmkXmlStr) "number", (pcmkXmlStr) s); + free(s); + + s = crm_itoa(ndisabled); + xmlSetProp(resources_node, (pcmkXmlStr) "disabled", (pcmkXmlStr) s); + free(s); + + s = crm_itoa(nblocked); + xmlSetProp(resources_node, (pcmkXmlStr) "blocked", (pcmkXmlStr) s); + free(s); + + return 0; +} + +static int +cluster_dc_html(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); + + node_t *dc = va_arg(args, node_t *); + const char *quorum = va_arg(args, const char *); + const char *dc_version_s = va_arg(args, const char *); + const char *dc_name = va_arg(args, const char *); + + pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: "); + + if (dc) { + if (crm_is_true(quorum)) { + char *buf = crm_strdup_printf("%s (version %s) - partition with quorum", + dc_name, dc_version_s ? dc_version_s : "unknown"); + pcmk_create_html_node(node, "span", NULL, NULL, buf); + free(buf); + } else { + char *buf = crm_strdup_printf("%s (version %s) - partition", + dc_name, dc_version_s ? dc_version_s : "unknown"); + pcmk_create_html_node(node, "span", NULL, NULL, buf); + free(buf); + + pcmk_create_html_node(node, "span", NULL, "warning", "WITHOUT"); + pcmk_create_html_node(node, "span", NULL, NULL, "quorum"); + } + } else { + pcmk_create_html_node(node ,"span", NULL, "warning", "NONE"); + } + + return 0; +} + +static int +cluster_dc_text(pcmk__output_t *out, va_list args) { + node_t *dc = va_arg(args, node_t *); + const char *quorum = va_arg(args, const char *); + const char *dc_version_s = va_arg(args, const char *); + const char *dc_name = va_arg(args, const char *); + + if (dc) { + out->list_item(out, "Current DC", "%s (version %s) - partition %s quorum", + dc_name, dc_version_s ? dc_version_s : "unknown", + crm_is_true(quorum) ? "with" : "WITHOUT"); + } else { + out->list_item(out, "Current DC", "NONE"); + } + + return 0; +} + +static int +cluster_dc_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "current_dc"); + + node_t *dc = va_arg(args, node_t *); + const char *quorum = va_arg(args, const char *); + const char *dc_version_s = va_arg(args, const char *); + + if (dc) { + xmlSetProp(node, (pcmkXmlStr) "present", (pcmkXmlStr) "true"); + xmlSetProp(node, (pcmkXmlStr) "version", (pcmkXmlStr) (dc_version_s ? dc_version_s : "")); + xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) dc->details->uname); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) dc->details->id); + xmlSetProp(node, (pcmkXmlStr) "with_quorum", (pcmkXmlStr) (crm_is_true(quorum) ? "true" : "false")); + } else { + xmlSetProp(node, (pcmkXmlStr) "present", (pcmkXmlStr) "false"); + } + + return 0; +} + +static int +cluster_options_html(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + /* Kind of a hack - close the list started by print_cluster_summary so we + * can put all the options in their own list, but just for HTML output. + */ + out->end_list(out); + + /* And then this list will be closed by print_cluster_summary since it + * wants to close the list it created unconditionally. + */ + out->begin_list(out, NULL, NULL, "Config Options"); + + out->list_item(out, NULL, "STONITH of failed nodes %s", + is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled"); + + out->list_item(out, NULL, "Cluster is %s", + is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric"); + + switch (data_set->no_quorum_policy) { + case no_quorum_freeze: + out->list_item(out, NULL, "No Quorum policy: Freeze resources"); + break; + + case no_quorum_stop: + out->list_item(out, NULL, "No Quorum policy: Stop ALL resources"); + break; + + case no_quorum_ignore: + out->list_item(out, NULL, "No Quorum policy: Ignore"); + break; + + case no_quorum_suicide: + out->list_item(out, NULL, "No Quorum policy: Suicide"); + break; + } + + if (is_set(data_set->flags, pe_flag_maintenance_mode)) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); + + pcmk_create_html_node(node, "span", NULL, "bold", "DISABLED"); + pcmk_create_html_node(node, "span", NULL, NULL, + " (the cluster will not attempt to start, stop, or recover services)"); + } else { + out->list_item(out, NULL, "Resource management enabled"); + } + + return 0; +} + +static int +cluster_options_text(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + if (is_set(data_set->flags, pe_flag_maintenance_mode)) { + fprintf(out->dest, "\n *** Resource management is DISABLED ***"); + fprintf(out->dest, "\n The cluster will not attempt to start, stop or recover services"); + fprintf(out->dest, "\n"); + } + + return 0; +} + +static int +cluster_options_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "cluster_options"); + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + xmlSetProp(node, (pcmkXmlStr) "stonith-enabled", + (pcmkXmlStr) (is_set(data_set->flags, pe_flag_stonith_enabled) ? "true" : "false")); + xmlSetProp(node, (pcmkXmlStr) "symmetric-cluster", + (pcmkXmlStr) (is_set(data_set->flags, pe_flag_symmetric_cluster) ? "true" : "false")); + + switch (data_set->no_quorum_policy) { + case no_quorum_freeze: + xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "freeze"); + break; + + case no_quorum_stop: + xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "stop"); + break; + + case no_quorum_ignore: + xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "ignore"); + break; + + case no_quorum_suicide: + xmlSetProp(node, (pcmkXmlStr) "no-quorum-policy", (pcmkXmlStr) "suicide"); + break; + } + + xmlSetProp(node, (pcmkXmlStr) "maintenance-mode", + (pcmkXmlStr) (is_set(data_set->flags, pe_flag_maintenance_mode) ? "true" : "false")); + + return 0; +} + +static int +cluster_stack_html(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "li"); + const char *stack_s = va_arg(args, const char *); + + pcmk_create_html_node(node, "span", NULL, "bold", "Stack: "); + pcmk_create_html_node(node, "span", NULL, NULL, stack_s); + + return 0; +} + +static int +cluster_stack_text(pcmk__output_t *out, va_list args) { + const char *stack_s = va_arg(args, const char *); + out->list_item(out, "Stack", "%s", stack_s); + return 0; +} + +static int +cluster_stack_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "stack"); + const char *stack_s = va_arg(args, const char *); + + xmlSetProp(node, (pcmkXmlStr) "type", (pcmkXmlStr) stack_s); + + return 0; +} + +static int +cluster_times_html(pcmk__output_t *out, va_list args) { + xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li"); + xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li"); + + const char *last_written = va_arg(args, const char *); + const char *user = va_arg(args, const char *); + const char *client = va_arg(args, const char *); + const char *origin = va_arg(args, const char *); + + char *buf = last_changed_string(last_written, user, client, origin); + + pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: "); + pcmk_create_html_node(updated_node, "span", NULL, NULL, crm_now_string(NULL)); + + pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: "); + pcmk_create_html_node(changed_node, "span", NULL, NULL, buf); + + free(buf); + return 0; +} + +static int +cluster_times_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "last_update"); + xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "last_change"); + + const char *last_written = va_arg(args, const char *); + const char *user = va_arg(args, const char *); + const char *client = va_arg(args, const char *); + const char *origin = va_arg(args, const char *); + + xmlSetProp(updated_node, (pcmkXmlStr) "time", (pcmkXmlStr) crm_now_string(NULL)); + + xmlSetProp(changed_node, (pcmkXmlStr) "time", (pcmkXmlStr) (last_written ? last_written : "")); + xmlSetProp(changed_node, (pcmkXmlStr) "user", (pcmkXmlStr) (user ? user : "")); + xmlSetProp(changed_node, (pcmkXmlStr) "client", (pcmkXmlStr) (client ? client : "")); + xmlSetProp(changed_node, (pcmkXmlStr) "origin", (pcmkXmlStr) (origin ? origin : "")); + + return 0; +} + +static int +cluster_times_text(pcmk__output_t *out, va_list args) { + const char *last_written = va_arg(args, const char *); + const char *user = va_arg(args, const char *); + const char *client = va_arg(args, const char *); + const char *origin = va_arg(args, const char *); + + char *buf = last_changed_string(last_written, user, client, origin); + + out->list_item(out, "Last updated", "%s", crm_now_string(NULL)); + out->list_item(out, "Last change", " %s", buf); + + free(buf); + return 0; +} + +static int +failed_action_console(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + char *s = failed_action_string(xml_op); + + curses_indented_printf(out, "%s\n", s); + free(s); + return 0; +} + +static int +failed_action_html(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + char *s = failed_action_string(xml_op); + + pcmk__output_create_html_node(out, "li", NULL, NULL, s); + free(s); + return 0; +} + +static int +failed_action_text(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + char *s = failed_action_string(xml_op); + + pcmk__indented_printf(out, "%s\n", s); + free(s); + return 0; +} + +static int +failed_action_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + + const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); + const char *last = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE); + int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0"); + int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0"); + const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); + + char *rc_s = crm_itoa(rc); + char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none"); + xmlNodePtr node = pcmk__output_create_xml_node(out, "failure"); + + xmlSetProp(node, (pcmkXmlStr) (op_key ? "op_key" : "id"), + (pcmkXmlStr) (op_key ? op_key : "id")); + xmlSetProp(node, (pcmkXmlStr) "node", + (pcmkXmlStr) crm_element_value(xml_op, XML_ATTR_UNAME)); + xmlSetProp(node, (pcmkXmlStr) "exitstatus", + (pcmkXmlStr) services_ocf_exitcode_str(rc)); + xmlSetProp(node, (pcmkXmlStr) "exitreason", (pcmkXmlStr) reason_s); + xmlSetProp(node, (pcmkXmlStr) "exitcode", (pcmkXmlStr) rc_s); + xmlSetProp(node, (pcmkXmlStr) "call", + (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_CALLID)); + xmlSetProp(node, (pcmkXmlStr) "status", + (pcmkXmlStr) services_lrm_status_str(status)); + + if (last) { + char *s = crm_itoa(crm_parse_ms(crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS))); + char *rc_change = time_t_string(crm_parse_int(last, "0")); + + xmlSetProp(node, (pcmkXmlStr) "last-rc-change", (pcmkXmlStr) rc_change); + xmlSetProp(node, (pcmkXmlStr) "queued", + (pcmkXmlStr) crm_element_value(xml_op, XML_RSC_OP_T_QUEUE)); + xmlSetProp(node, (pcmkXmlStr) "exec", + (pcmkXmlStr) crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); + xmlSetProp(node, (pcmkXmlStr) "interval", (pcmkXmlStr) s); + xmlSetProp(node, (pcmkXmlStr) "task", + (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_TASK)); + + free(s); + free(rc_change); + } + + free(reason_s); + free(rc_s); + return 0; +} + +static int +node_html(pcmk__output_t *out, va_list args) { + node_t *node = va_arg(args, node_t *); + unsigned int mon_ops = va_arg(args, unsigned int); + gboolean full = va_arg(args, gboolean); + + char *node_name = get_node_display_name(node, mon_ops); + char *buf = crm_strdup_printf("Node: %s", node_name); + int print_opts = get_resource_display_options(mon_ops, mon_output_html); + + if (full) { + xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li"); + + pcmk_create_html_node(item_node, "span", NULL, NULL, buf); + + if (node->details->standby_onfail && node->details->online) { + pcmk_create_html_node(item_node, "span", NULL, "standby", " standby (on-fail)"); + } else if (node->details->standby && node->details->online) { + char *s = crm_strdup_printf(" standby%s", node->details->running_rsc ? " (with active resources)" : ""); + pcmk_create_html_node(item_node, "span", NULL, " standby", s); + free(s); + } else if (node->details->standby) { + pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE (standby)"); + } else if (node->details->maintenance && node->details->online) { + pcmk_create_html_node(item_node, "span", NULL, "maint", " maintenance"); + } else if (node->details->maintenance) { + pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE (maintenance)"); + } else if (node->details->online) { + pcmk_create_html_node(item_node, "span", NULL, "online", " online"); + } else { + pcmk_create_html_node(item_node, "span", NULL, "offline", " OFFLINE"); + } + if (is_set(mon_ops, mon_op_print_brief) && is_set(mon_ops, mon_op_group_by_node)) { + out->begin_list(out, NULL, NULL, NULL); + pe__rscs_brief_output(out, node->details->running_rsc, print_opts | pe_print_rsconly, + FALSE); + out->end_list(out); + + } else if (is_set(mon_ops, mon_op_group_by_node)) { + GListPtr lpc2 = NULL; + + out->begin_list(out, NULL, NULL, NULL); + for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { + resource_t *rsc = (resource_t *) lpc2->data; + out->message(out, crm_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc); + } + out->end_list(out); + } + } else { + out->begin_list(out, NULL, NULL, "%s", buf); + } + + free(buf); + free(node_name); + return 0; +} + +static int +node_text(pcmk__output_t *out, va_list args) { + node_t *node = va_arg(args, node_t *); + unsigned int mon_ops = va_arg(args, unsigned int); + gboolean full = va_arg(args, gboolean); + + if (full) { + const char *node_mode = va_arg(args, const char *); + + char *node_name = get_node_display_name(node, mon_ops); + int print_opts = get_resource_display_options(mon_ops, mon_output_xml); + char *buf = NULL; + + /* Print the node name and status */ + if (pe__is_guest_node(node)) { + buf = crm_strdup_printf("GuestNode %s: %s", node_name, node_mode); + } else if (pe__is_remote_node(node)) { + buf = crm_strdup_printf("RemoteNode %s: %s", node_name, node_mode); + } else { + buf = crm_strdup_printf("Node %s: %s", node_name, node_mode); + } + + /* If we're grouping by node, print its resources */ + if (is_set(mon_ops, mon_op_group_by_node)) { + out->begin_list(out, NULL, NULL, "%s", buf); + out->begin_list(out, NULL, NULL, "Resources"); + + if (is_set(mon_ops, mon_op_print_brief)) { + pe__rscs_brief_output(out, node->details->running_rsc, + print_opts | pe_print_rsconly, FALSE); + } else { + GListPtr gIter2 = NULL; + + for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) { + resource_t *rsc = (resource_t *) gIter2->data; + out->message(out, crm_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc); + } + } + + out->end_list(out); + out->end_list(out); + } else { + out->list_item(out, NULL, "%s", buf); + } + + free(buf); + free(node_name); + } else { + out->begin_list(out, NULL, NULL, "Node: %s", get_node_display_name(node, mon_ops)); + } + + return 0; +} + +static int +node_xml(pcmk__output_t *out, va_list args) { + node_t *node = va_arg(args, node_t *); + unsigned int mon_ops __attribute__((unused)) = va_arg(args, unsigned int); + gboolean full = va_arg(args, gboolean); + + if (full) { + const char *node_type = "unknown"; + int print_opts = get_resource_display_options(mon_ops, mon_output_xml); + char *length_s = crm_itoa(g_list_length(node->details->running_rsc)); + + switch (node->details->type) { + case node_member: + node_type = "member"; + break; + case node_remote: + node_type = "remote"; + break; + case node_ping: + node_type = "ping"; + break; + } + pe__name_and_nvpairs_xml(out, true, "node", 13, + "name", node->details->uname, + "id", node->details->id, + "online", node->details->online ? "true" : "false", + "standby", node->details->standby ? "true" : "false", + "standby_onfail", node->details->standby_onfail ? "true" : "false", + "maintenance", node->details->maintenance ? "true" : "false", + "pending", node->details->pending ? "true" : "false", + "unclean", node->details->unclean ? "true" : "false", + "shutdown", node->details->shutdown ? "true" : "false", + "expected_up", node->details->expected_up ? "true" : "false", + "is_dc", node->details->is_dc ? "true" : "false", + "resources_running", length_s, + "type", node_type); + + if (pe__is_guest_node(node)) { + xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); + xmlSetProp(xml_node, (pcmkXmlStr) "id_as_resource", + (pcmkXmlStr) node->details->remote_rsc->container->id); + } + + if (is_set(mon_ops, mon_op_group_by_node)) { + GListPtr lpc = NULL; + + for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) { + resource_t *rsc = (resource_t *) lpc->data; + out->message(out, crm_element_name(rsc->xml), print_opts | pe_print_rsconly, rsc); + } + } + + free(length_s); + + out->end_list(out); + } else { + xmlNodePtr parent = pcmk__output_xml_create_parent(out, "node"); + xmlSetProp(parent, (pcmkXmlStr) "name", (pcmkXmlStr) node->details->uname); + } + + return 0; +} + +static int +node_attribute_text(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + gboolean add_extra = va_arg(args, gboolean); + int expected_score = va_arg(args, int); + + + if (add_extra) { + int v = crm_parse_int(value, "0"); + + if (v <= 0) { + out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value); + } else if (v < expected_score) { + out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score); + } else { + out->list_item(out, NULL, "%-32s\t: %-10s", name, value); + } + } else { + out->list_item(out, NULL, "%-32s\t: %-10s", name, value); + } + + return 0; +} + +static int +node_attribute_html(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + gboolean add_extra = va_arg(args, gboolean); + int expected_score = va_arg(args, int); + + if (add_extra) { + int v = crm_parse_int(value, "0"); + char *s = crm_strdup_printf("%s: %s", name, value); + xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li"); + + pcmk_create_html_node(item_node, "span", NULL, NULL, s); + free(s); + + if (v <= 0) { + pcmk_create_html_node(item_node, "span", NULL, "bold", "(connectivity is lost)"); + } else if (v < expected_score) { + char *buf = crm_strdup_printf("(connectivity is degraded -- expected %d", expected_score); + pcmk_create_html_node(item_node, "span", NULL, "bold", buf); + free(buf); + } + } else { + out->list_item(out, NULL, "%s: %s", name, value); + } + + return 0; +} + +static int +node_attribute_xml(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + gboolean add_extra = va_arg(args, gboolean); + int expected_score = va_arg(args, int); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute"); + xmlSetProp(node, (pcmkXmlStr) "name", (pcmkXmlStr) name); + xmlSetProp(node, (pcmkXmlStr) "value", (pcmkXmlStr) value); + + if (add_extra) { + char *buf = crm_itoa(expected_score); + xmlSetProp(node, (pcmkXmlStr) "expected", (pcmkXmlStr) buf); + free(buf); + } + + return 0; +} + +static int +op_history_text(pcmk__output_t *out, va_list args) { + xmlNode *xml_op = va_arg(args, xmlNode *); + const char *task = va_arg(args, const char *); + const char *interval_ms_s = va_arg(args, const char *); + int rc = va_arg(args, int); + unsigned int mon_ops = va_arg(args, unsigned int); + + char *buf = op_history_string(xml_op, task, interval_ms_s, rc, mon_ops); + + out->list_item(out, NULL, "%s", buf); + + free(buf); + return 0; +} + +static int +op_history_xml(pcmk__output_t *out, va_list args) { + xmlNode *xml_op = va_arg(args, xmlNode *); + const char *task = va_arg(args, const char *); + const char *interval_ms_s = va_arg(args, const char *); + int rc = va_arg(args, int); + unsigned int mon_ops = va_arg(args, unsigned int); + + char *rc_s = NULL; + + xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history"); + + xmlSetProp(node, (pcmkXmlStr) "call", + (pcmkXmlStr) crm_element_value(xml_op, XML_LRM_ATTR_CALLID)); + xmlSetProp(node, (pcmkXmlStr) "task", (pcmkXmlStr) task); + + if (interval_ms_s && safe_str_neq(interval_ms_s, "0")) { + char *s = crm_strdup_printf("%sms", interval_ms_s); + xmlSetProp(node, (pcmkXmlStr) "interval", (pcmkXmlStr) s); + free(s); + } + + if (is_set(mon_ops, mon_op_print_timing)) { + const char *value = NULL; + + value = crm_element_value(xml_op, XML_RSC_OP_LAST_CHANGE); + if (value) { + time_t int_value = (time_t) crm_parse_int(value, NULL); + if (int_value > 0) { + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_CHANGE, + (pcmkXmlStr) crm_now_string(&int_value)); + } + } + + value = crm_element_value(xml_op, XML_RSC_OP_LAST_RUN); + if (value) { + time_t int_value = (time_t) crm_parse_int(value, NULL); + if (int_value > 0) { + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_RUN, + (pcmkXmlStr) crm_now_string(&int_value)); + } + } + + value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); + if (value) { + char *s = crm_strdup_printf("%sms", value); + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_T_EXEC, (pcmkXmlStr) s); + free(s); + } + value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); + if (value) { + char *s = crm_strdup_printf("%sms", value); + xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_T_QUEUE, (pcmkXmlStr) s); + free(s); + } + } + + rc_s = crm_itoa(rc); + xmlSetProp(node, (pcmkXmlStr) "rc", (pcmkXmlStr) rc_s); + xmlSetProp(node, (pcmkXmlStr) "rc_text", (pcmkXmlStr) services_ocf_exitcode_str(rc)); + free(rc_s); + return 0; +} + +static int +resource_history_text(pcmk__output_t *out, va_list args) { + resource_t *rsc = va_arg(args, resource_t *); + const char *rsc_id = va_arg(args, const char *); + gboolean all = va_arg(args, gboolean); + int failcount = va_arg(args, int); + time_t last_failure = va_arg(args, int); + + char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure); + + out->begin_list(out, NULL, NULL, "%s", buf); + free(buf); + return 0; +} + +static int +resource_history_xml(pcmk__output_t *out, va_list args) { + resource_t *rsc = va_arg(args, resource_t *); + const char *rsc_id = va_arg(args, const char *); + gboolean all = va_arg(args, gboolean); + int failcount = va_arg(args, int); + time_t last_failure = va_arg(args, int); + + xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history"); + xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) rsc_id); + + if (rsc == NULL) { + xmlSetProp(node, (pcmkXmlStr) "orphan", (pcmkXmlStr) "true"); + } else if (all || failcount || last_failure > 0) { + char *migration_s = crm_itoa(rsc->migration_threshold); + + xmlSetProp(node, (pcmkXmlStr) "orphan", (pcmkXmlStr) "false"); + xmlSetProp(node, (pcmkXmlStr) "migration-threshold", + (pcmkXmlStr) migration_s); + free(migration_s); + + if (failcount > 0) { + char *s = crm_itoa(failcount); + xmlSetProp(node, (pcmkXmlStr) CRM_FAIL_COUNT_PREFIX, (pcmkXmlStr) s); + free(s); + } + + if (last_failure > 0) { + xmlSetProp(node, (pcmkXmlStr) CRM_LAST_FAILURE_PREFIX, + (pcmkXmlStr) crm_now_string(&last_failure)); + } + } + + return 0; +} + +static int +stonith_event_console(pcmk__output_t *out, va_list args) { + stonith_history_t *event = va_arg(args, stonith_history_t *); + int full_history = va_arg(args, int); + gboolean later_succeeded = va_arg(args, gboolean); + + char *buf = NULL; + + buf = time_t_string(event->completed); + + switch (event->state) { + case st_failed: + curses_indented_printf(out, "%s of %s failed: delegate=%s, client=%s, origin=%s, %s='%s %s'\n", + stonith_action_str(event->action), event->target, + event->delegate ? event->delegate : "", + event->client, event->origin, + full_history ? "completed" : "last-failed", buf, + later_succeeded ? "(a later attempt succeeded)" : ""); + break; + + case st_done: + curses_indented_printf(out, "%s of %s successful: delegate=%s, client=%s, origin=%s, %s='%s'\n", + stonith_action_str(event->action), event->target, + event->delegate ? event->delegate : "", + event->client, event->origin, + full_history ? "completed" : "last-successful", buf); + break; + + default: + curses_indented_printf(out, "%s of %s pending: client=%s, origin=%s\n", + stonith_action_str(event->action), event->target, + event->client, event->origin); + break; + } + + free(buf); + return 0; +} + +static int +ticket_console(pcmk__output_t *out, va_list args) { + ticket_t *ticket = va_arg(args, ticket_t *); + + if (ticket->last_granted > -1) { + char *time = pcmk_format_named_time("last-granted", ticket->last_granted); + out->list_item(out, ticket->id, "\t%s%s %s", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : "", + time); + free(time); + } else { + out->list_item(out, ticket->id, "\t%s%s", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : ""); + } + + return 0; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "ban", "console", ban_text }, + { "ban", "html", ban_html }, + { "ban", "text", ban_text }, + { "ban", "xml", ban_xml }, + { "bundle", "console", pe__bundle_text }, + { "clone", "console", pe__clone_text }, + { "cluster-counts", "console", cluster_counts_text }, + { "cluster-counts", "html", cluster_counts_html }, + { "cluster-counts", "text", cluster_counts_text }, + { "cluster-counts", "xml", cluster_counts_xml }, + { "cluster-dc", "console", cluster_dc_text }, + { "cluster-dc", "html", cluster_dc_html }, + { "cluster-dc", "text", cluster_dc_text }, + { "cluster-dc", "xml", cluster_dc_xml }, + { "cluster-options", "console", cluster_options_text }, + { "cluster-options", "html", cluster_options_html }, + { "cluster-options", "text", cluster_options_text }, + { "cluster-options", "xml", cluster_options_xml }, + { "cluster-stack", "console", cluster_stack_text }, + { "cluster-stack", "html", cluster_stack_html }, + { "cluster-stack", "text", cluster_stack_text }, + { "cluster-stack", "xml", cluster_stack_xml }, + { "cluster-times", "console", cluster_times_text }, + { "cluster-times", "html", cluster_times_html }, + { "cluster-times", "text", cluster_times_text }, + { "cluster-times", "xml", cluster_times_xml }, + { "failed-action", "console", failed_action_console }, + { "failed-action", "html", failed_action_html }, + { "failed-action", "text", failed_action_text }, + { "failed-action", "xml", failed_action_xml }, + { "node", "console", node_text }, + { "node", "html", node_html }, + { "node", "text", node_text }, + { "node", "xml", node_xml }, + { "node-attribute", "console", node_attribute_text }, + { "node-attribute", "html", node_attribute_html }, + { "node-attribute", "text", node_attribute_text }, + { "node-attribute", "xml", node_attribute_xml }, + { "op-history", "console", op_history_text }, + { "op-history", "html", op_history_text }, + { "op-history", "text", op_history_text }, + { "op-history", "xml", op_history_xml }, + { "primitive", "console", pe__resource_text }, + { "resource-history", "console", resource_history_text }, + { "resource-history", "html", resource_history_text }, + { "resource-history", "text", resource_history_text }, + { "resource-history", "xml", resource_history_xml }, + { "stonith-event", "console", stonith_event_console }, + { "ticket", "console", ticket_console }, + + { NULL, NULL, NULL } +}; + +void +crm_mon_register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} diff --git a/tools/crm_mon_print.c b/tools/crm_mon_print.c index 06e6048e45..b501e17d15 100644 --- a/tools/crm_mon_print.c +++ b/tools/crm_mon_print.c @@ -1,2600 +1,1164 @@ /* * Copyright 2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crm_mon.h" -static void print_nvpair(mon_state_t *state, const char *name, const char *value, - const char *units, time_t epoch_time); -static void print_node_start(mon_state_t *state, node_t *node, unsigned int mon_ops); -static void print_node_end(mon_state_t *state); static void print_resources_heading(mon_state_t *state, unsigned int mon_ops); static void print_resources_closing(mon_state_t *state, gboolean printed_heading, unsigned int mon_ops); -static void print_resources(mon_state_t *state, pe_working_set_t *data_set, - int print_opts, unsigned int mon_ops); -static void print_rsc_history_start(mon_state_t *state, pe_working_set_t *data_set, - node_t *node, resource_t *rsc, const char *rsc_id, - gboolean all); -static void print_rsc_history_end(mon_state_t *state); -static void print_op_history(mon_state_t *state, pe_working_set_t *data_set, - node_t *node, xmlNode *xml_op, const char *task, - const char *interval_ms_s, int rc, unsigned int mon_ops); +static gboolean print_resources(mon_state_t *state, pe_working_set_t *data_set, + int print_opts, unsigned int mon_ops); static void print_rsc_history(mon_state_t *state, pe_working_set_t *data_set, - node_t *node, xmlNode *rsc_entry, gboolean operations, - unsigned int mon_ops); + node_t *node, xmlNode *rsc_entry, unsigned int mon_ops, + GListPtr op_list); static void print_node_history(mon_state_t *state, pe_working_set_t *data_set, xmlNode *node_state, gboolean operations, unsigned int mon_ops); -static gboolean print_attr_msg(mon_state_t *state, node_t * node, GListPtr rsc_list, - const char *attrname, const char *attrvalue); +static gboolean add_extra_info(mon_state_t *state, node_t * node, GListPtr rsc_list, + const char *attrname, const char *attrvalue, int *expected_score); static void print_node_attribute(gpointer name, gpointer user_data); -static void print_node_summary(mon_state_t *state, pe_working_set_t * data_set, - gboolean operations, unsigned int mon_ops); -static void print_ticket(gpointer name, gpointer value, gpointer user_data); -static void print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set); -static void print_ban(mon_state_t *state, pe_node_t *node, pe__location_t *location, - unsigned int mon_ops); -static void print_neg_locations(mon_state_t *state, pe_working_set_t *data_set, - unsigned int mon_ops, const char *prefix); -static void print_node_attributes(mon_state_t *state, pe_working_set_t *data_set, - unsigned int mon_ops); -static void print_cluster_summary_header(mon_state_t *state); -static void print_cluster_summary_footer(mon_state_t *state); +static gboolean print_node_summary(mon_state_t *state, pe_working_set_t * data_set, + gboolean operations, unsigned int mon_ops); +static gboolean print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set); +static gboolean print_neg_locations(mon_state_t *state, pe_working_set_t *data_set, + unsigned int mon_ops, const char *prefix); +static gboolean print_node_attributes(mon_state_t *state, pe_working_set_t *data_set, + unsigned int mon_ops); static void print_cluster_times(mon_state_t *state, pe_working_set_t *data_set); -static void print_cluster_stack(mon_state_t *state, const char *stack_s); static void print_cluster_dc(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops); -static void print_cluster_counts(mon_state_t *state, pe_working_set_t *data_set, - const char *stack_s); -static void print_cluster_options(mon_state_t *state, pe_working_set_t *data_set); static void print_cluster_summary(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops, unsigned int show); -static void print_failed_action(mon_state_t *state, xmlNode *xml_op); -static void print_failed_actions(mon_state_t *state, pe_working_set_t *data_set); -static void print_stonith_action(mon_state_t *state, stonith_history_t *event, unsigned int mon_ops, stonith_history_t *top_history); -static void print_failed_stonith_actions(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); -static void print_stonith_pending(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); -static void print_stonith_history(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); - -/*! - * \internal - * \brief Print a [name]=[value][units] pair, optionally using time string - * - * \param[in] stream File stream to display output to - * \param[in] name Name to display - * \param[in] value Value to display (or NULL to convert time instead) - * \param[in] units Units to display (or NULL for no units) - * \param[in] epoch_time Epoch time to convert if value is NULL - */ -static void -print_nvpair(mon_state_t *state, const char *name, const char *value, - const char *units, time_t epoch_time) -{ - /* print name= */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " %s=", name); - break; - - case mon_output_html: - case mon_output_cgi: - case mon_output_xml: - fprintf(state->stream, " %s=", name); - break; - - default: - break; - } - - /* If we have a value (and optionally units), print it */ - if (value) { - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "%s%s", value, (units? units : "")); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "%s%s", value, (units? units : "")); - break; - - case mon_output_xml: - fprintf(state->stream, "\"%s%s\"", value, (units? units : "")); - break; - - default: - break; - } - - /* Otherwise print user-friendly time string */ - } else { - static char empty_str[] = ""; - char *c, *date_str = asctime(localtime(&epoch_time)); - - for (c = (date_str != NULL) ? date_str : empty_str; *c != '\0'; ++c) { - if (*c == '\n') { - *c = '\0'; - break; - } - } - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "'%s'", date_str); - break; - - case mon_output_html: - case mon_output_cgi: - case mon_output_xml: - fprintf(state->stream, "\"%s\"", date_str); - break; - - default: - break; - } - } -} - -/*! - * \internal - * \brief Print whatever is needed to start a node section - * - * \param[in] stream File stream to display output to - * \param[in] node Node to print - */ -static void -print_node_start(mon_state_t *state, node_t *node, unsigned int mon_ops) -{ - char *node_name; - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - node_name = get_node_display_name(node, mon_ops); - print_as(state->output_format, "* Node %s:\n", node_name); - free(node_name); - break; - - case mon_output_html: - case mon_output_cgi: - node_name = get_node_display_name(node, mon_ops); - fprintf(state->stream, "

      Node: %s

      \n
        \n", node_name); - free(node_name); - break; - - case mon_output_xml: - fprintf(state->stream, " \n", node->details->uname); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print whatever is needed to end a node section - * - * \param[in] stream File stream to display output to - */ -static void -print_node_end(mon_state_t *state) -{ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } -} +static gboolean print_failed_actions(mon_state_t *state, pe_working_set_t *data_set); +static gboolean print_failed_stonith_actions(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); +static gboolean print_stonith_pending(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); +static gboolean print_stonith_history(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops); /*! * \internal * \brief Print resources section heading appropriate to options * * \param[in] stream File stream to display output to */ static void print_resources_heading(mon_state_t *state, unsigned int mon_ops) { const char *heading; if (is_set(mon_ops, mon_op_group_by_node)) { /* Active resources have already been printed by node */ - heading = is_set(mon_ops, mon_op_inactive_resources) ? "Inactive resources" : NULL; + heading = is_set(mon_ops, mon_op_inactive_resources) ? "Inactive Resources" : NULL; } else if (is_set(mon_ops, mon_op_inactive_resources)) { - heading = "Full list of resources"; + heading = "Full List of Resources"; } else { - heading = "Active resources"; + heading = "Active Resources"; } /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\n%s:\n\n", heading); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      %s

      \n", heading); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } - + state->out->begin_list(state->out, NULL, NULL, "%s", heading); } /*! * \internal * \brief Print whatever resource section closing is appropriate * * \param[in] stream File stream to display output to */ static void -print_resources_closing(mon_state_t *state, gboolean printed_heading, +print_resources_closing(mon_state_t *state, gboolean printed_resource, unsigned int mon_ops) { const char *heading; /* What type of resources we did or did not display */ if (is_set(mon_ops, mon_op_group_by_node)) { heading = "inactive "; } else if (is_set(mon_ops, mon_op_inactive_resources)) { heading = ""; } else { heading = "active "; } - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - if (!printed_heading) { - print_as(state->output_format, "\nNo %sresources\n\n", heading); - } - break; - - case mon_output_html: - case mon_output_cgi: - if (!printed_heading) { - fprintf(state->stream, "
      \n

      No %sresources

      \n", heading); - } - break; - - case mon_output_xml: - fprintf(state->stream, " %s\n", - (printed_heading? "
      " : "")); - break; - - default: - break; + if (state->output_format != mon_output_xml && !printed_resource) { + state->out->list_item(state->out, NULL, "No %sresources", heading); } + + state->out->end_list(state->out); } /*! * \internal * \brief Print whatever resource section(s) are appropriate * * \param[in] stream File stream to display output to * \param[in] data_set Cluster state to display * \param[in] print_opts Bitmask of pe_print_options */ -static void +static gboolean print_resources(mon_state_t *state, pe_working_set_t *data_set, int print_opts, unsigned int mon_ops) { GListPtr rsc_iter; - const char *prefix = NULL; - gboolean printed_heading = FALSE; + gboolean printed_resource = FALSE; gboolean brief_output = is_set(mon_ops, mon_op_print_brief); /* If we already showed active resources by node, and * we're not showing inactive resources, we have nothing to do */ if (is_set(mon_ops, mon_op_group_by_node) && is_not_set(mon_ops, mon_op_inactive_resources)) { - return; + return FALSE; } /* XML uses an indent, and ignores brief option for resources */ if (state->output_format == mon_output_xml) { - prefix = " "; brief_output = FALSE; } + print_resources_heading(state, mon_ops); + /* If we haven't already printed resources grouped by node, * and brief output was requested, print resource summary */ if (brief_output && is_not_set(mon_ops, mon_op_group_by_node)) { - print_resources_heading(state, mon_ops); - printed_heading = TRUE; - print_rscs_brief(data_set->resources, NULL, print_opts, state->stream, - is_set(mon_ops, mon_op_inactive_resources)); + pe__rscs_brief_output(state->out, data_set->resources, print_opts, + is_set(mon_ops, mon_op_inactive_resources)); } /* For each resource, display it if appropriate */ for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) { resource_t *rsc = (resource_t *) rsc_iter->data; /* Complex resources may have some sub-resources active and some inactive */ gboolean is_active = rsc->fns->active(rsc, TRUE); gboolean partially_active = rsc->fns->active(rsc, FALSE); /* Skip inactive orphans (deleted but still in CIB) */ if (is_set(rsc->flags, pe_rsc_orphan) && !is_active) { continue; /* Skip active resources if we already displayed them by node */ } else if (is_set(mon_ops, mon_op_group_by_node)) { if (is_active) { continue; } /* Skip primitives already counted in a brief summary */ } else if (brief_output && (rsc->variant == pe_native)) { continue; /* Skip resources that aren't at least partially active, * unless we're displaying inactive resources */ } else if (!partially_active && is_not_set(mon_ops, mon_op_inactive_resources)) { continue; } /* Print this resource */ - if (printed_heading == FALSE) { - print_resources_heading(state, mon_ops); - printed_heading = TRUE; + if (printed_resource == FALSE) { + printed_resource = TRUE; } - rsc->fns->print(rsc, prefix, print_opts, state->stream); + state->out->message(state->out, crm_element_name(rsc->xml), print_opts, rsc); } - print_resources_closing(state, printed_heading, mon_ops); + print_resources_closing(state, printed_resource, mon_ops); + return TRUE; } -/*! - * \internal - * \brief Print heading for resource history - * - * \param[in] stream File stream to display output to - * \param[in] data_set Current state of CIB - * \param[in] node Node that ran this resource - * \param[in] rsc Resource to print - * \param[in] rsc_id ID of resource to print - * \param[in] all Whether to print every resource or just failed ones - */ -static void -print_rsc_history_start(mon_state_t *state, pe_working_set_t *data_set, - node_t *node, resource_t *rsc, const char *rsc_id, - gboolean all) -{ - time_t last_failure = 0; - int failcount = rsc? - pe_get_failcount(node, rsc, &last_failure, pe_fc_default, - NULL, data_set) - : 0; - - if (!all && !failcount && (last_failure <= 0)) { - return; - } - - /* Print resource ID */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " %s:", rsc_id); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
    • %s:", rsc_id); - break; - - case mon_output_xml: - fprintf(state->stream, " output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " orphan"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " orphan"); - break; - - case mon_output_xml: - fprintf(state->stream, " orphan=\"true\""); - break; - - default: - break; - } - - /* If resource is not an orphan, print some details */ - } else if (all || failcount || (last_failure > 0)) { - - /* Print migration threshold */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " migration-threshold=%d", rsc->migration_threshold); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " migration-threshold=%d", rsc->migration_threshold); - break; - - case mon_output_xml: - fprintf(state->stream, " orphan=\"false\" migration-threshold=\"%d\"", - rsc->migration_threshold); - break; - - default: - break; - } - - /* Print fail count if any */ - if (failcount > 0) { - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " " CRM_FAIL_COUNT_PREFIX "=%d", failcount); - break; - - case mon_output_xml: - fprintf(state->stream, " " CRM_FAIL_COUNT_PREFIX "=\"%d\"", - failcount); - break; - - default: - break; - } - } - - /* Print last failure time if any */ - if (last_failure > 0) { - print_nvpair(state, CRM_LAST_FAILURE_PREFIX, NULL, NULL, last_failure); - } - } - - /* End the heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "\n
        \n"); - break; - - case mon_output_xml: - fprintf(state->stream, ">\n"); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print closing for resource history - * - * \param[in] stream File stream to display output to - */ -static void -print_rsc_history_end(mon_state_t *state) -{ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n
    • \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } +static int +failure_count(pe_working_set_t *data_set, node_t *node, resource_t *rsc, time_t *last_failure) { + return rsc ? pe_get_failcount(node, rsc, last_failure, pe_fc_default, + NULL, data_set) + : 0; } -/*! - * \internal - * \brief Print operation history - * - * \param[in] stream File stream to display output to - * \param[in] data_set Current state of CIB - * \param[in] node Node this operation is for - * \param[in] xml_op Root of XML tree describing this operation - * \param[in] task Task parsed from this operation's XML - * \param[in] interval_ms_s Interval parsed from this operation's XML - * \param[in] rc Return code parsed from this operation's XML - */ -static void -print_op_history(mon_state_t *state, pe_working_set_t *data_set, node_t *node, - xmlNode *xml_op, const char *task, const char *interval_ms_s, - int rc, unsigned int mon_ops) -{ - const char *value = NULL; - const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); - - /* Begin the operation description */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " + (%s) %s:", call, task); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
    • (%s) %s:", call, task); - break; - - case mon_output_xml: - fprintf(state->stream, " 0)) { - print_nvpair(state, attr, NULL, NULL, epoch); - } - - // last-run is deprecated - attr = XML_RSC_OP_LAST_RUN; - if ((crm_element_value_epoch(xml_op, attr, &epoch) == pcmk_ok) - && (epoch > 0)) { - print_nvpair(state, attr, NULL, NULL, epoch); - } - - attr = XML_RSC_OP_T_EXEC; - value = crm_element_value(xml_op, attr); - if (value) { - print_nvpair(state, attr, value, "ms", 0); - } +static GListPtr +get_operation_list(xmlNode *rsc_entry) { + GListPtr op_list = NULL; + xmlNode *rsc_op = NULL; - attr = XML_RSC_OP_T_QUEUE; - value = crm_element_value(xml_op, attr); - if (value) { - print_nvpair(state, attr, value, "ms", 0); + for (rsc_op = __xml_first_child_element(rsc_entry); rsc_op != NULL; + rsc_op = __xml_next_element(rsc_op)) { + if (crm_str_eq((const char *) rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) { + op_list = g_list_append(op_list, rsc_op); } } - /* End the operation description */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, " rc=%d (%s)\n", rc, services_ocf_exitcode_str(rc)); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " rc=%d (%s)
    • \n", rc, services_ocf_exitcode_str(rc)); - break; - - case mon_output_xml: - fprintf(state->stream, " rc=\"%d\" rc_text=\"%s\" />\n", rc, services_ocf_exitcode_str(rc)); - break; - - default: - break; - } + op_list = g_list_sort(op_list, sort_op_by_callid); + return op_list; } /*! * \internal * \brief Print resource operation/failure history * * \param[in] stream File stream to display output to * \param[in] data_set Current state of CIB * \param[in] node Node that ran this resource * \param[in] rsc_entry Root of XML tree describing resource status - * \param[in] operations Whether to print operations or just failcounts */ static void print_rsc_history(mon_state_t *state, pe_working_set_t *data_set, node_t *node, - xmlNode *rsc_entry, gboolean operations, unsigned int mon_ops) + xmlNode *rsc_entry, unsigned int mon_ops, GListPtr op_list) { GListPtr gIter = NULL; - GListPtr op_list = NULL; gboolean printed = FALSE; const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); - xmlNode *rsc_op = NULL; - - /* If we're not showing operations, just print the resource failure summary */ - if (operations == FALSE) { - print_rsc_history_start(state, data_set, node, rsc, rsc_id, FALSE); - print_rsc_history_end(state); - return; - } - - /* Create a list of this resource's operations */ - for (rsc_op = __xml_first_child_element(rsc_entry); rsc_op != NULL; - rsc_op = __xml_next_element(rsc_op)) { - if (crm_str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, TRUE)) { - op_list = g_list_append(op_list, rsc_op); - } - } - op_list = g_list_sort(op_list, sort_op_by_callid); /* Print each operation */ for (gIter = op_list; gIter != NULL; gIter = gIter->next) { xmlNode *xml_op = (xmlNode *) gIter->data; const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK); const char *interval_ms_s = crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS); const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC); int rc = crm_parse_int(op_rc, "0"); /* Display 0-interval monitors as "probe" */ if (safe_str_eq(task, CRMD_ACTION_STATUS) && ((interval_ms_s == NULL) || safe_str_eq(interval_ms_s, "0"))) { task = "probe"; } /* Ignore notifies and some probes */ if (safe_str_eq(task, CRMD_ACTION_NOTIFY) || (safe_str_eq(task, "probe") && (rc == 7))) { continue; } /* If this is the first printed operation, print heading for resource */ if (printed == FALSE) { + time_t last_failure = 0; + int failcount = failure_count(data_set, node, rsc, &last_failure); + + state->out->message(state->out, "resource-history", rsc, rsc_id, TRUE, failcount, last_failure); printed = TRUE; - print_rsc_history_start(state, data_set, node, rsc, rsc_id, TRUE); } /* Print the operation */ - print_op_history(state, data_set, node, xml_op, task, interval_ms_s, rc, mon_ops); + state->out->message(state->out, "op-history", xml_op, task, interval_ms_s, + rc, mon_ops); } /* Free the list we created (no need to free the individual items) */ g_list_free(op_list); /* If we printed anything, close the resource */ if (printed) { - print_rsc_history_end(state); + state->out->end_list(state->out); } } /*! * \internal * \brief Print node operation/failure history * * \param[in] stream File stream to display output to * \param[in] data_set Current state of CIB * \param[in] node_state Root of XML tree describing node status * \param[in] operations Whether to print operations or just failcounts */ static void print_node_history(mon_state_t *state, pe_working_set_t *data_set, xmlNode *node_state, gboolean operations, unsigned int mon_ops) { node_t *node = pe_find_node_id(data_set->nodes, ID(node_state)); xmlNode *lrm_rsc = NULL; xmlNode *rsc_entry = NULL; + gboolean printed_header = FALSE; if (node && node->details && node->details->online) { - print_node_start(state, node, mon_ops); - lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE); lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE); /* Print history of each of the node's resources */ for (rsc_entry = __xml_first_child_element(lrm_rsc); rsc_entry != NULL; rsc_entry = __xml_next_element(rsc_entry)) { if (crm_str_eq((const char *)rsc_entry->name, XML_LRM_TAG_RESOURCE, TRUE)) { - print_rsc_history(state, data_set, node, rsc_entry, operations, mon_ops); + if (operations == FALSE) { + const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); + resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); + time_t last_failure = 0; + int failcount = failure_count(data_set, node, rsc, &last_failure); + + if (failcount > 0) { + if (printed_header == FALSE) { + printed_header = TRUE; + state->out->message(state->out, "node", node, mon_ops, FALSE); + } + + state->out->message(state->out, "resource-history", rsc, + rsc_id, FALSE, failcount, last_failure); + state->out->end_list(state->out); + } + } else { + GListPtr op_list = get_operation_list(rsc_entry); + + if (printed_header == FALSE) { + printed_header = TRUE; + state->out->message(state->out, "node", node, mon_ops, FALSE); + } + + if (g_list_length(op_list) > 0) { + print_rsc_history(state, data_set, node, rsc_entry, mon_ops, op_list); + } + } } } - print_node_end(state); + if (printed_header) { + state->out->end_list(state->out); + } } } /*! * \internal - * \brief Print extended information about an attribute if appropriate + * \brief Determine whether extended information about an attribute should be added. * * \param[in] data_set Working set of CIB state * - * \return TRUE if extended information was printed, FALSE otherwise + * \return TRUE if extended information should be printed, FALSE otherwise * \note Currently, extended information is only supported for ping/pingd * resources, for which a message will be printed if connectivity is lost * or degraded. */ static gboolean -print_attr_msg(mon_state_t *state, node_t * node, GListPtr rsc_list, - const char *attrname, const char *attrvalue) +add_extra_info(mon_state_t *state, node_t *node, GListPtr rsc_list, + const char *attrname, const char *attrvalue, int *expected_score) { GListPtr gIter = NULL; for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (resource_t *) gIter->data; const char *type = g_hash_table_lookup(rsc->meta, "type"); + const char *name = NULL; if (rsc->children != NULL) { - if (print_attr_msg(state, node, rsc->children, attrname, attrvalue)) { + if (add_extra_info(state, node, rsc->children, attrname, attrvalue, expected_score)) { return TRUE; } } - if (safe_str_eq(type, "ping") || safe_str_eq(type, "pingd")) { - const char *name = g_hash_table_lookup(rsc->parameters, "name"); - - if (name == NULL) { - name = "pingd"; - } + if (safe_str_neq(type, "ping") && safe_str_neq(type, "pingd")) { + return FALSE; + } - /* To identify the resource with the attribute name. */ - if (safe_str_eq(name, attrname)) { - int host_list_num = 0; - int expected_score = 0; - int value = crm_parse_int(attrvalue, "0"); - const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list"); - const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier"); - - if(hosts) { - char **host_list = g_strsplit(hosts, " ", 0); - host_list_num = g_strv_length(host_list); - g_strfreev(host_list); - } + name = g_hash_table_lookup(rsc->parameters, "name"); - /* pingd multiplier is the same as the default value. */ - expected_score = host_list_num * crm_parse_int(multiplier, "1"); + if (name == NULL) { + name = "pingd"; + } - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - if (value <= 0) { - print_as(state->output_format, "\t: Connectivity is lost"); - } else if (value < expected_score) { - print_as(state->output_format, "\t: Connectivity is degraded (Expected=%d)", expected_score); - } - break; - - case mon_output_html: - case mon_output_cgi: - if (value <= 0) { - fprintf(state->stream, " (connectivity is lost)"); - } else if (value < expected_score) { - fprintf(state->stream, " (connectivity is degraded -- expected %d)", - expected_score); - } - break; + /* To identify the resource with the attribute name. */ + if (safe_str_eq(name, attrname)) { + int host_list_num = 0; + /* int value = crm_parse_int(attrvalue, "0"); */ + const char *hosts = g_hash_table_lookup(rsc->parameters, "host_list"); + const char *multiplier = g_hash_table_lookup(rsc->parameters, "multiplier"); + + if (hosts) { + char **host_list = g_strsplit(hosts, " ", 0); + host_list_num = g_strv_length(host_list); + g_strfreev(host_list); + } - case mon_output_xml: - fprintf(state->stream, " expected=\"%d\"", expected_score); - break; + /* pingd multiplier is the same as the default value. */ + *expected_score = host_list_num * crm_parse_int(multiplier, "1"); - default: - break; - } - return TRUE; - } + return TRUE; } } return FALSE; } /* structure for passing multiple user data to g_list_foreach() */ struct mon_attr_data { mon_state_t *state; node_t *node; }; static void print_node_attribute(gpointer name, gpointer user_data) { const char *value = NULL; + int expected_score = 0; + gboolean add_extra = FALSE; struct mon_attr_data *data = (struct mon_attr_data *) user_data; value = pe_node_attribute_raw(data->node, name); - /* Print attribute name and value */ - switch (data->state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(data->state->output_format, " + %-32s\t: %-10s", (char *)name, value); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(data->state->stream, "
    • %s: %s", - (char *)name, value); - break; + add_extra = add_extra_info(data->state, data->node, data->node->details->running_rsc, + name, value, &expected_score); - case mon_output_xml: - fprintf(data->state->stream, - " state, data->node, data->node->details->running_rsc, - name, value); - - /* Close out the attribute */ - switch (data->state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(data->state->output_format, "\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(data->state->stream, "
    • \n"); - break; - - case mon_output_xml: - fprintf(data->state->stream, " />\n"); - break; - - default: - break; - } + /* Print attribute name and value */ + data->state->out->message(data->state->out, "node-attribute", name, value, add_extra, + expected_score); } -static void +static gboolean print_node_summary(mon_state_t *state, pe_working_set_t * data_set, gboolean operations, unsigned int mon_ops) { xmlNode *node_state = NULL; xmlNode *cib_status = get_object_root(XML_CIB_TAG_STATUS, data_set->input); - /* Print heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - if (operations) { - print_as(state->output_format, "\nOperations:\n"); - } else { - print_as(state->output_format, "\nMigration Summary:\n"); - } - break; - - case mon_output_html: - case mon_output_cgi: - if (operations) { - fprintf(state->stream, "
      \n

      Operations

      \n"); - } else { - fprintf(state->stream, "
      \n

      Migration Summary

      \n"); - } - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; + if (xmlChildElementCount(cib_status) == 0) { + return FALSE; + } - default: - break; + /* Print heading */ + if (operations) { + state->out->begin_list(state->out, NULL, NULL, "Operations"); + } else { + state->out->begin_list(state->out, NULL, NULL, "Migration Summary"); } /* Print each node in the CIB status */ for (node_state = __xml_first_child_element(cib_status); node_state != NULL; node_state = __xml_next_element(node_state)) { if (crm_str_eq((const char *)node_state->name, XML_CIB_TAG_STATE, TRUE)) { print_node_history(state, data_set, node_state, operations, mon_ops); } } /* Close section */ - switch (state->output_format) { - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } + state->out->end_list(state->out); + return TRUE; } -static void -print_ticket(gpointer name, gpointer value, gpointer user_data) +static gboolean +print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set) { - mon_state_t *data = (mon_state_t *) user_data; - ticket_t *ticket = (ticket_t *) value; - - switch (data->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(data->output_format, "* %s:\t%s%s", ticket->id, - (ticket->granted? "granted" : "revoked"), - (ticket->standby? " [standby]" : "")); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(data->stream, "
    • %s: %s%s", ticket->id, - (ticket->granted? "granted" : "revoked"), - (ticket->standby? " [standby]" : "")); - break; - - case mon_output_xml: - fprintf(data->stream, " id, (ticket->granted? "granted" : "revoked"), - (ticket->standby? "true" : "false")); - break; - - default: - break; - } - if (ticket->last_granted > -1) { - print_nvpair(data, "last-granted", NULL, NULL, ticket->last_granted); - } - switch (data->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(data->output_format, "\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(data->stream, "
    • \n"); - break; - - case mon_output_xml: - fprintf(data->stream, " />\n"); - break; + GHashTableIter iter; + gpointer key, value; - default: - break; + if (g_hash_table_size(data_set->tickets) == 0) { + return FALSE; } -} -static void -print_cluster_tickets(mon_state_t *state, pe_working_set_t * data_set) -{ /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nTickets:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Tickets

      \n
        \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } + state->out->begin_list(state->out, NULL, NULL, "Tickets"); /* Print each ticket */ - g_hash_table_foreach(data_set->tickets, print_ticket, state); - - /* Close section */ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; + g_hash_table_iter_init(&iter, data_set->tickets); + while (g_hash_table_iter_next(&iter, &key, &value)) { + ticket_t *ticket = (ticket_t *) value; + state->out->message(state->out, "ticket", ticket); } -} - -/*! - * \internal - * \brief Print a negative location constraint - * - * \param[in] stream File stream to display output to - * \param[in] node Node affected by constraint - * \param[in] location Constraint to print - */ -static void -print_ban(mon_state_t *state, pe_node_t *node, pe__location_t *location, - unsigned int mon_ops) -{ - char *node_name = NULL; - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - node_name = get_node_display_name(node, mon_ops); - print_as(state->output_format, " %s\tprevents %s from running %son %s\n", - location->id, location->rsc_lh->id, - ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""), - node_name); - break; - case mon_output_html: - case mon_output_cgi: - node_name = get_node_display_name(node, mon_ops); - fprintf(state->stream, "
    • %s prevents %s from running %son %s
    • \n", - location->id, location->rsc_lh->id, - ((location->role_filter == RSC_ROLE_MASTER)? "as Master " : ""), - node_name); - break; - - case mon_output_xml: - fprintf(state->stream, - " \n", - location->id, location->rsc_lh->id, node->details->uname, node->weight, - ((location->role_filter == RSC_ROLE_MASTER)? "true" : "false")); - break; - - default: - break; - } - free(node_name); + /* Close section */ + state->out->end_list(state->out); + return TRUE; } /*! * \internal * \brief Print section for negative location constraints * * \param[in] stream File stream to display output to * \param[in] data_set Working set corresponding to CIB status to display */ -static void +static gboolean print_neg_locations(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops, const char *prefix) { GListPtr gIter, gIter2; - - /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nNegative Location Constraints:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Negative Location Constraints

      \n
        \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } + gboolean printed_header = FALSE; /* Print each ban */ for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) { pe__location_t *location = gIter->data; if (!g_str_has_prefix(location->id, prefix)) continue; for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) { node_t *node = (node_t *) gIter2->data; if (node->weight < 0) { - print_ban(state, node, location, mon_ops); + if (printed_header == FALSE) { + printed_header = TRUE; + state->out->begin_list(state->out, NULL, NULL, "Negative Location Constraints"); + } + + state->out->message(state->out, "ban", node, location, mon_ops); } } } - /* Close section */ - switch (state->output_format) { - case mon_output_cgi: - case mon_output_html: - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; + if (printed_header) { + state->out->end_list(state->out); + return TRUE; + } else { + return FALSE; } } /*! * \internal * \brief Print node attributes section * * \param[in] stream File stream to display output to * \param[in] data_set Working set of CIB state */ -static void +static gboolean print_node_attributes(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops) { GListPtr gIter = NULL; - - /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nNode Attributes:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Node Attributes

      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } + gboolean printed_header = FALSE; /* Unpack all resource parameters (it would be more efficient to do this - * only when needed for the first time in print_attr_msg()) + * only when needed for the first time in add_extra_info()) */ for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) { crm_mon_get_parameters(gIter->data, data_set); } /* Display each node's attributes */ for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { struct mon_attr_data data; data.state = state; data.node = (node_t *) gIter->data; if (data.node && data.node->details && data.node->details->online) { GList *attr_list = NULL; GHashTableIter iter; gpointer key, value; - print_node_start(state, data.node, mon_ops); - g_hash_table_iter_init(&iter, data.node->details->attrs); while (g_hash_table_iter_next (&iter, &key, &value)) { attr_list = append_attr_list(attr_list, key); } - g_list_foreach(attr_list, print_node_attribute, &data); - g_list_free(attr_list); - print_node_end(state); - } - } - - /* Print section footer */ - switch (state->output_format) { - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print header for cluster summary if needed - * - * \param[in] stream File stream to display output to - */ -static void -print_cluster_summary_header(mon_state_t *state) -{ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "

      Cluster Summary

      \n

      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, "

      \n"); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print footer for cluster summary if needed - * - * \param[in] stream File stream to display output to - */ -static void -print_cluster_summary_footer(mon_state_t *state) -{ - switch (state->output_format) { - case mon_output_cgi: - case mon_output_html: - fprintf(state->stream, "

      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, "
      \n"); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print times the display was last updated and CIB last changed - * - * \param[in] stream File stream to display output to - * \param[in] data_set Working set of CIB state - */ -static void -print_cluster_times(mon_state_t *state, pe_working_set_t *data_set) -{ - const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN); - const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER); - const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT); - const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG); - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: { - const char *now_str = crm_now_string(NULL); - - print_as(state->output_format, "Last updated: %s", now_str ? now_str : "Could not determine current time"); - print_as(state->output_format, (user || client || origin)? "\n" : "\t\t"); - print_as(state->output_format, "Last change: %s", last_written ? last_written : ""); - if (user) { - print_as(state->output_format, " by %s", user); - } - if (client) { - print_as(state->output_format, " via %s", client); - } - if (origin) { - print_as(state->output_format, " on %s", origin); + if (g_list_length(attr_list) == 0) { + g_list_free(attr_list); + continue; } - print_as(state->output_format, "\n"); - break; - } - - case mon_output_html: - case mon_output_cgi: { - const char *now_str = crm_now_string(NULL); - fprintf(state->stream, " Last updated: %s
      \n", - now_str ? now_str : "Could not determine current time"); - fprintf(state->stream, " Last change: %s", last_written ? last_written : ""); - if (user) { - fprintf(state->stream, " by %s", user); + if (printed_header == FALSE) { + printed_header = TRUE; + state->out->begin_list(state->out, NULL, NULL, "Node Attributes"); } - if (client) { - fprintf(state->stream, " via %s", client); - } - if (origin) { - fprintf(state->stream, " on %s", origin); - } - fprintf(state->stream, "
      \n"); - break; - } - case mon_output_xml: { - const char *now_str = crm_now_string(NULL); - - fprintf(state->stream, " \n", - now_str ? now_str : "Could not determine current time"); - fprintf(state->stream, " \n", - last_written ? last_written : "", user ? user : "", - client ? client : "", origin ? origin : ""); - break; + state->out->message(state->out, "node", data.node, mon_ops, FALSE); + g_list_foreach(attr_list, print_node_attribute, &data); + g_list_free(attr_list); + state->out->end_list(state->out); } - - default: - break; - } -} - -/*! - * \internal - * \brief Print cluster stack - * - * \param[in] stream File stream to display output to - * \param[in] stack_s Stack name - */ -static void -print_cluster_stack(mon_state_t *state, const char *stack_s) -{ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "Stack: %s\n", stack_s); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " Stack: %s
      \n", stack_s); - break; - - case mon_output_xml: - fprintf(state->stream, " \n", stack_s); - break; - - default: - break; } -} - -/*! - * \internal - * \brief Print current DC and its version - * - * \param[in] stream File stream to display output to - * \param[in] data_set Working set of CIB state - */ -static void -print_cluster_dc(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops) -{ - node_t *dc = data_set->dc_node; - xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']", - data_set->input, LOG_DEBUG); - const char *dc_version_s = dc_version? - crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE) - : NULL; - const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM); - char *dc_name = dc? get_node_display_name(dc, mon_ops) : NULL; - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "Current DC: "); - if (dc) { - print_as(state->output_format, "%s (version %s) - partition %s quorum\n", - dc_name, (dc_version_s? dc_version_s : "unknown"), - (crm_is_true(quorum) ? "with" : "WITHOUT")); - } else { - print_as(state->output_format, "NONE\n"); - } - break; - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " Current DC: "); - if (dc) { - fprintf(state->stream, "%s (version %s) - partition %s quorum", - dc_name, (dc_version_s? dc_version_s : "unknown"), - (crm_is_true(quorum)? "with" : "WITHOUT")); - } else { - fprintf(state->stream, "NONE"); - } - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " stream, - "present=\"true\" version=\"%s\" name=\"%s\" id=\"%s\" with_quorum=\"%s\"", - (dc_version_s? dc_version_s : ""), dc->details->uname, dc->details->id, - (crm_is_true(quorum) ? "true" : "false")); - } else { - fprintf(state->stream, "present=\"false\""); - } - fprintf(state->stream, " />\n"); - break; - - default: - break; + /* Print section footer */ + if (printed_header) { + state->out->end_list(state->out); + return TRUE; + } else { + return FALSE; } - free(dc_name); } /*! * \internal - * \brief Print counts of configured nodes and resources + * \brief Print times the display was last updated and CIB last changed * * \param[in] stream File stream to display output to * \param[in] data_set Working set of CIB state - * \param[in] stack_s Stack name */ static void -print_cluster_counts(mon_state_t *state, pe_working_set_t *data_set, const char *stack_s) +print_cluster_times(mon_state_t *state, pe_working_set_t *data_set) { - int nnodes = g_list_length(data_set->nodes); - int nresources = count_resources(data_set, NULL); - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - - print_as(state->output_format, "\n%d node%s configured\n", nnodes, s_if_plural(nnodes)); - - print_as(state->output_format, "%d resource%s configured", - nresources, s_if_plural(nresources)); - if(data_set->disabled_resources || data_set->blocked_resources) { - print_as(state->output_format, " ("); - if (data_set->disabled_resources) { - print_as(state->output_format, "%d DISABLED", data_set->disabled_resources); - } - if (data_set->disabled_resources && data_set->blocked_resources) { - print_as(state->output_format, ", "); - } - if (data_set->blocked_resources) { - print_as(state->output_format, "%d BLOCKED from starting due to failure", - data_set->blocked_resources); - } - print_as(state->output_format, ")"); - } - print_as(state->output_format, "\n"); - - break; - - case mon_output_html: - case mon_output_cgi: - - fprintf(state->stream, " %d node%s configured
      \n", - nnodes, s_if_plural(nnodes)); - - fprintf(state->stream, " %d resource%s configured", - nresources, s_if_plural(nresources)); - if (data_set->disabled_resources || data_set->blocked_resources) { - fprintf(state->stream, " ("); - if (data_set->disabled_resources) { - fprintf(state->stream, "%d DISABLED", - data_set->disabled_resources); - } - if (data_set->disabled_resources && data_set->blocked_resources) { - fprintf(state->stream, ", "); - } - if (data_set->blocked_resources) { - fprintf(state->stream, - "%d BLOCKED from starting due to failure", - data_set->blocked_resources); - } - fprintf(state->stream, ")"); - } - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, - " \n", - g_list_length(data_set->nodes)); - fprintf(state->stream, - " \n", - count_resources(data_set, NULL), - data_set->disabled_resources, data_set->blocked_resources); - break; + const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN); + const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER); + const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT); + const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG); - default: - break; - } + state->out->message(state->out, "cluster-times", last_written, user, client, origin); } /*! * \internal - * \brief Print cluster-wide options + * \brief Print current DC and its version * * \param[in] stream File stream to display output to * \param[in] data_set Working set of CIB state - * - * \note Currently this is only implemented for HTML and XML output, and - * prints only a few options. If there is demand, more could be added. */ static void -print_cluster_options(mon_state_t *state, pe_working_set_t *data_set) +print_cluster_dc(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops) { - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - if (is_set(data_set->flags, pe_flag_maintenance_mode)) { - print_as(state->output_format, "\n *** Resource management is DISABLED ***"); - print_as(state->output_format, "\n The cluster will not attempt to start, stop or recover services"); - print_as(state->output_format, "\n"); - } - break; - - case mon_output_html: - fprintf(state->stream, "

      \n

      Config Options

      \n"); - fprintf(state->stream, " \n"); - fprintf(state->stream, " \n", - is_set(data_set->flags, pe_flag_stonith_enabled)? "enabled" : "disabled"); - - fprintf(state->stream, " \n", - is_set(data_set->flags, pe_flag_symmetric_cluster)? "" : "a"); - - fprintf(state->stream, " \n"); - - fprintf(state->stream, " \n"); - - fprintf(state->stream, "
      STONITH of failed nodes%s
      Cluster is%ssymmetric
      No Quorum Policy"); - switch (data_set->no_quorum_policy) { - case no_quorum_freeze: - fprintf(state->stream, "Freeze resources"); - break; - case no_quorum_stop: - fprintf(state->stream, "Stop ALL resources"); - break; - case no_quorum_ignore: - fprintf(state->stream, "Ignore"); - break; - case no_quorum_suicide: - fprintf(state->stream, "Suicide"); - break; - } - fprintf(state->stream, "
      Resource management"); - if (is_set(data_set->flags, pe_flag_maintenance_mode)) { - fprintf(state->stream, "DISABLED (the cluster will " - "not attempt to start, stop or recover services)"); - } else { - fprintf(state->stream, "enabled"); - } - fprintf(state->stream, "
      \n

      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " stream, " stonith-enabled=\"%s\"", - is_set(data_set->flags, pe_flag_stonith_enabled)? - "true" : "false"); - fprintf(state->stream, " symmetric-cluster=\"%s\"", - is_set(data_set->flags, pe_flag_symmetric_cluster)? - "true" : "false"); - fprintf(state->stream, " no-quorum-policy=\""); - switch (data_set->no_quorum_policy) { - case no_quorum_freeze: - fprintf(state->stream, "freeze"); - break; - case no_quorum_stop: - fprintf(state->stream, "stop"); - break; - case no_quorum_ignore: - fprintf(state->stream, "ignore"); - break; - case no_quorum_suicide: - fprintf(state->stream, "suicide"); - break; - } - fprintf(state->stream, "\""); - fprintf(state->stream, " maintenance-mode=\"%s\"", - is_set(data_set->flags, pe_flag_maintenance_mode)? - "true" : "false"); - fprintf(state->stream, " />\n"); - break; + node_t *dc = data_set->dc_node; + xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']", + data_set->input, LOG_DEBUG); + const char *dc_version_s = dc_version? + crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE) + : NULL; + const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM); + char *dc_name = dc? get_node_display_name(dc, mon_ops) : NULL; - default: - break; - } + state->out->message(state->out, "cluster-dc", dc, quorum, dc_version_s, dc_name); + free(dc_name); } /*! * \internal * \brief Print a summary of cluster-wide information * * \param[in] stream File stream to display output to * \param[in] data_set Working set of CIB state */ static void print_cluster_summary(mon_state_t *state, pe_working_set_t *data_set, unsigned int mon_ops, unsigned int show) { const char *stack_s = get_cluster_stack(data_set); gboolean header_printed = FALSE; if (show & mon_show_stack) { if (header_printed == FALSE) { - print_cluster_summary_header(state); + state->out->begin_list(state->out, NULL, NULL, "Cluster Summary"); header_printed = TRUE; } - print_cluster_stack(state, stack_s); + state->out->message(state->out, "cluster-stack", stack_s); } /* Always print DC if none, even if not requested */ if ((data_set->dc_node == NULL) || (show & mon_show_dc)) { if (header_printed == FALSE) { - print_cluster_summary_header(state); + state->out->begin_list(state->out, NULL, NULL, "Cluster Summary"); header_printed = TRUE; } print_cluster_dc(state, data_set, mon_ops); } if (show & mon_show_times) { if (header_printed == FALSE) { - print_cluster_summary_header(state); + state->out->begin_list(state->out, NULL, NULL, "Cluster Summary"); header_printed = TRUE; } print_cluster_times(state, data_set); } if (is_set(data_set->flags, pe_flag_maintenance_mode) || data_set->disabled_resources || data_set->blocked_resources || is_set(show, mon_show_count)) { if (header_printed == FALSE) { - print_cluster_summary_header(state); + state->out->begin_list(state->out, NULL, NULL, "Cluster Summary"); header_printed = TRUE; } - print_cluster_counts(state, data_set, stack_s); + state->out->message(state->out, "cluster-counts", g_list_length(data_set->nodes), + count_resources(data_set, NULL), data_set->disabled_resources, + data_set->blocked_resources); } /* There is not a separate option for showing cluster options, so show with * stack for now; a separate option could be added if there is demand */ if (show & mon_show_stack) { - print_cluster_options(state, data_set); + state->out->message(state->out, "cluster-options", data_set); } if (header_printed) { - print_cluster_summary_footer(state); - } -} - -/*! - * \internal - * \brief Print a failed action - * - * \param[in] stream File stream to display output to - * \param[in] xml_op Root of XML tree describing failed action - */ -static void -print_failed_action(mon_state_t *state, xmlNode *xml_op) -{ - const char *op_key = crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY); - const char *op_key_attr = "op_key"; - const char *node = crm_element_value(xml_op, XML_ATTR_UNAME); - const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); - const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); - int rc = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), "0"); - int status = crm_parse_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), "0"); - time_t last_change = 0; - char *exit_reason_cleaned; - - /* If no op_key was given, use id instead */ - if (op_key == NULL) { - op_key = ID(xml_op); - op_key_attr = "id"; - } - - /* If no exit reason was given, use "none" */ - if (exit_reason == NULL) { - exit_reason = "none"; - } - - /* Print common action information */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "* %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'", - op_key, node, services_ocf_exitcode_str(rc), rc, - call, services_lrm_status_str(status), exit_reason); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "

    • %s on %s '%s' (%d): call=%s, status=%s, exitreason='%s'", - op_key, node, services_ocf_exitcode_str(rc), rc, - call, services_lrm_status_str(status), exit_reason); - break; - - case mon_output_xml: - exit_reason_cleaned = crm_xml_escape(exit_reason); - fprintf(state->stream, " stream, " exitstatus=\"%s\" exitreason=\"%s\" exitcode=\"%d\"", - services_ocf_exitcode_str(rc), exit_reason_cleaned, rc); - fprintf(state->stream, " call=\"%s\" status=\"%s\"", - call, services_lrm_status_str(status)); - free(exit_reason_cleaned); - break; - - default: - break; - } - - /* If last change was given, print timing information as well */ - if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, - &last_change) == pcmk_ok) { - char *run_at_s = ctime(&last_change); - - if (run_at_s) { - run_at_s[24] = 0; /* Overwrite the newline */ - } - - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, ",\n last-rc-change='%s', queued=%sms, exec=%sms", - run_at_s? run_at_s : "", - crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), - crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, " last-rc-change='%s', queued=%sms, exec=%sms", - run_at_s? run_at_s : "", - crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), - crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); - break; - - case mon_output_xml: - fprintf(state->stream, - " last-rc-change=\"%s\" queued=\"%s\" exec=\"%s\" interval=\"%u\" task=\"%s\"", - run_at_s? run_at_s : "", - crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), - crm_element_value(xml_op, XML_RSC_OP_T_EXEC), - crm_parse_ms(crm_element_value(xml_op, XML_LRM_ATTR_INTERVAL_MS)), - crm_element_value(xml_op, XML_LRM_ATTR_TASK)); - break; - - default: - break; - } - } - - /* End the action listing */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
    • \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " />\n"); - break; - - default: - break; + state->out->end_list(state->out); } } /*! * \internal * \brief Print a section for failed actions * * \param[in] stream File stream to display output to * \param[in] data_set Working set of CIB state */ -static void +static gboolean print_failed_actions(mon_state_t *state, pe_working_set_t *data_set) { xmlNode *xml_op = NULL; - /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nFailed Resource Actions:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, - "
      \n

      Failed Resource Actions

      \n
        \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; + if (xmlChildElementCount(data_set->failed) == 0) { + return FALSE; } + /* Print section heading */ + state->out->begin_list(state->out, NULL, NULL, "Failed Resource Actions"); + /* Print each failed action */ for (xml_op = __xml_first_child(data_set->failed); xml_op != NULL; xml_op = __xml_next(xml_op)) { - print_failed_action(state, xml_op); + state->out->message(state->out, "failed-action", xml_op); } /* End section */ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } -} - -/*! - * \internal - * \brief Print a stonith action - * - * \param[in] stream File stream to display output to - * \param[in] event stonith event - */ -static void -print_stonith_action(mon_state_t *state, stonith_history_t *event, unsigned int mon_ops, stonith_history_t *top_history) -{ - const char *action_s = stonith_action_str(event->action); - char *run_at_s = ctime(&event->completed); - - if ((run_at_s) && (run_at_s[0] != 0)) { - run_at_s[strlen(run_at_s)-1] = 0; /* Overwrite the newline */ - } - - switch(state->output_format) { - case mon_output_xml: - fprintf(state->stream, " target, event->action); - switch(event->state) { - case st_done: - fprintf(state->stream, " state=\"success\""); - break; - case st_failed: - fprintf(state->stream, " state=\"failed\""); - break; - default: - fprintf(state->stream, " state=\"pending\""); - } - fprintf(state->stream, " origin=\"%s\" client=\"%s\"", - event->origin, event->client); - if (event->delegate) { - fprintf(state->stream, " delegate=\"%s\"", event->delegate); - } - switch(event->state) { - case st_done: - case st_failed: - fprintf(state->stream, " completed=\"%s\"", run_at_s?run_at_s:""); - break; - default: - break; - } - fprintf(state->stream, " />\n"); - break; - - case mon_output_plain: - case mon_output_console: - switch(event->state) { - case st_done: - print_as(state->output_format, "* %s of %s successful: delegate=%s, client=%s, origin=%s,\n" - " %s='%s'\n", - action_s, event->target, - event->delegate ? event->delegate : "", - event->client, event->origin, - is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-successful", - run_at_s?run_at_s:""); - break; - case st_failed: - print_as(state->output_format, "* %s of %s failed: delegate=%s, client=%s, origin=%s,\n" - " %s='%s' %s\n", - action_s, event->target, - event->delegate ? event->delegate : "", - event->client, event->origin, - is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-failed", - run_at_s?run_at_s:"", - stonith__later_succeeded(event, top_history) ? "(a later attempt succeeded)" : ""); - break; - default: - print_as(state->output_format, "* %s of %s pending: client=%s, origin=%s\n", - action_s, event->target, - event->client, event->origin); - } - break; - - case mon_output_html: - case mon_output_cgi: - switch(event->state) { - case st_done: - fprintf(state->stream, "
    • %s of %s successful: delegate=%s, " - "client=%s, origin=%s, %s='%s'
    • \n", - action_s, event->target, - event->delegate ? event->delegate : "", - event->client, event->origin, - is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-successful", - run_at_s?run_at_s:""); - break; - case st_failed: - fprintf(state->stream, "
    • %s of %s failed: delegate=%s, " - "client=%s, origin=%s, %s='%s' %s
    • \n", - action_s, event->target, - event->delegate ? event->delegate : "", - event->client, event->origin, - is_set(mon_ops, mon_op_fence_full_history) ? "completed" : "last-failed", - run_at_s?run_at_s:"", - stonith__later_succeeded(event, top_history) ? "(a later attempt succeeded)" : ""); - break; - default: - fprintf(state->stream, "
    • %s of %s pending: client=%s, " - "origin=%s
    • \n", - action_s, event->target, - event->client, event->origin); - } - break; - - default: - /* no support for fence history for other formats so far */ - break; - } + state->out->end_list(state->out); + return TRUE; } /*! * \internal * \brief Print a section for failed stonith actions * * \param[in] stream File stream to display output to * \param[in] history List of stonith actions * */ -static void +static gboolean print_failed_stonith_actions(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops) { stonith_history_t *hp; for (hp = history; hp; hp = hp->next) { if (hp->state == st_failed) { break; } } if (!hp) { - return; + return FALSE; } /* Print section heading */ - switch (state->output_format) { - /* no need to take care of xml in here as xml gets full - * history anyway - */ - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nFailed Fencing Actions:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Failed Fencing Actions

      \n
        \n"); - break; - - default: - break; + if (state->output_format != mon_output_xml) { + state->out->begin_list(state->out, NULL, NULL, "Failed Fencing Actions"); } /* Print each failed stonith action */ for (hp = history; hp; hp = hp->next) { if (hp->state == st_failed) { - print_stonith_action(state, hp, mon_ops, history); + state->out->message(state->out, "stonith-event", hp, mon_ops & mon_op_fence_full_history, history); } } /* End section */ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - default: - break; + if (state->output_format != mon_output_xml) { + state->out->end_list(state->out); } + + return TRUE; } /*! * \internal * \brief Print pending stonith actions * * \param[in] stream File stream to display output to * \param[in] history List of stonith actions * */ -static void +static gboolean print_stonith_pending(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops) { /* xml-output always shows the full history * so we'll never have to show pending-actions * separately */ if (history && (history->state != st_failed) && (history->state != st_done)) { stonith_history_t *hp; /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nPending Fencing Actions:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Pending Fencing Actions

      \n
        \n"); - break; - - default: - break; - } + if (state->output_format != mon_output_xml) { + state->out->begin_list(state->out, NULL, NULL, "Pending Fencing Actions"); + } history = stonith__sort_history(history); for (hp = history; hp; hp = hp->next) { if ((hp->state == st_failed) || (hp->state == st_done)) { break; } - print_stonith_action(state, hp, mon_ops, history); + state->out->message(state->out, "stonith-event", hp, mon_ops & mon_op_fence_full_history, NULL); } /* End section */ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - default: - break; + if (state->output_format != mon_output_xml) { + state->out->end_list(state->out); } + + return TRUE; } + + return FALSE; } /*! * \internal * \brief Print a section for stonith-history * * \param[in] stream File stream to display output to * \param[in] history List of stonith actions * */ -static void +static gboolean print_stonith_history(mon_state_t *state, stonith_history_t *history, unsigned int mon_ops) { stonith_history_t *hp; - /* Print section heading */ - switch (state->output_format) { - case mon_output_plain: - case mon_output_console: - print_as(state->output_format, "\nFencing History:\n"); - break; - - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n

      Fencing History

      \n
        \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; + if (history == NULL) { + return FALSE; } + /* Print section heading */ + state->out->begin_list(state->out, NULL, NULL, "Fencing History"); + stonith__sort_history(history); for (hp = history; hp; hp = hp->next) { if ((hp->state != st_failed) || (state->output_format == mon_output_xml)) { - print_stonith_action(state, hp, mon_ops, history); + state->out->message(state->out, "stonith-event", hp, mon_ops & mon_op_fence_full_history, NULL); } } /* End section */ - switch (state->output_format) { - case mon_output_html: - case mon_output_cgi: - fprintf(state->stream, "
      \n"); - break; - - case mon_output_xml: - fprintf(state->stream, " \n"); - break; - - default: - break; - } + state->out->end_list(state->out); + return TRUE; } void print_status(mon_state_t *state, pe_working_set_t *data_set, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, const char *prefix) { GListPtr gIter = NULL; int print_opts = get_resource_display_options(mon_ops, state->output_format); + gboolean printed = FALSE; /* space-separated lists of node names */ char *online_nodes = NULL; char *online_remote_nodes = NULL; char *online_guest_nodes = NULL; char *offline_nodes = NULL; char *offline_remote_nodes = NULL; if (state->output_format == mon_output_console) { blank_screen(); } print_cluster_summary(state, data_set, mon_ops, show); - print_as(state->output_format, "\n"); + + if (is_set(show, mon_show_headers)) { + state->out->info(state->out, "%s", ""); + } /* Gather node information (and print if in bad state or grouping by node) */ + state->out->begin_list(state->out, NULL, NULL, "Node List"); for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; const char *node_mode = NULL; char *node_name = get_node_display_name(node, mon_ops); /* Get node mode */ if (node->details->unclean) { if (node->details->online) { node_mode = "UNCLEAN (online)"; } else if (node->details->pending) { node_mode = "UNCLEAN (pending)"; } else { node_mode = "UNCLEAN (offline)"; } } else if (node->details->pending) { node_mode = "pending"; } else if (node->details->standby_onfail && node->details->online) { node_mode = "standby (on-fail)"; } else if (node->details->standby) { if (node->details->online) { if (node->details->running_rsc) { node_mode = "standby (with active resources)"; } else { node_mode = "standby"; } } else { node_mode = "OFFLINE (standby)"; } } else if (node->details->maintenance) { if (node->details->online) { node_mode = "maintenance"; } else { node_mode = "OFFLINE (maintenance)"; } } else if (node->details->online) { node_mode = "online"; if (is_not_set(mon_ops, mon_op_group_by_node)) { if (pe__is_guest_node(node)) { online_guest_nodes = add_list_element(online_guest_nodes, node_name); } else if (pe__is_remote_node(node)) { online_remote_nodes = add_list_element(online_remote_nodes, node_name); } else { online_nodes = add_list_element(online_nodes, node_name); } free(node_name); continue; } + } else { node_mode = "OFFLINE"; if (is_not_set(mon_ops, mon_op_group_by_node)) { if (pe__is_remote_node(node)) { offline_remote_nodes = add_list_element(offline_remote_nodes, node_name); } else if (pe__is_guest_node(node)) { /* ignore offline guest nodes */ } else { offline_nodes = add_list_element(offline_nodes, node_name); } free(node_name); continue; } } /* If we get here, node is in bad state, or we're grouping by node */ - - /* Print the node name and status */ - if (pe__is_guest_node(node)) { - print_as(state->output_format, "Guest"); - } else if (pe__is_remote_node(node)) { - print_as(state->output_format, "Remote"); - } - print_as(state->output_format, "Node %s: %s\n", node_name, node_mode); - - /* If we're grouping by node, print its resources */ - if (is_set(mon_ops, mon_op_group_by_node)) { - if (is_set(mon_ops, mon_op_print_brief)) { - print_rscs_brief(node->details->running_rsc, "\t", print_opts | pe_print_rsconly, - state->stream, FALSE); - } else { - GListPtr gIter2 = NULL; - - for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) { - resource_t *rsc = (resource_t *) gIter2->data; - - rsc->fns->print(rsc, "\t", print_opts | pe_print_rsconly, state->stream); - } - } - } - free(node_name); + state->out->message(state->out, "node", node, mon_ops, TRUE, node_mode); } /* If we're not grouping by node, summarize nodes by status */ if (online_nodes) { - print_as(state->output_format, "Online: [%s ]\n", online_nodes); + state->out->list_item(state->out, "Online", "[%s ]", online_nodes); free(online_nodes); } if (offline_nodes) { - print_as(state->output_format, "OFFLINE: [%s ]\n", offline_nodes); + state->out->list_item(state->out, "OFFLINE", "[%s ]", offline_nodes); free(offline_nodes); } if (online_remote_nodes) { - print_as(state->output_format, "RemoteOnline: [%s ]\n", online_remote_nodes); + state->out->list_item(state->out, "RemoteOnline", "[%s ]", online_remote_nodes); free(online_remote_nodes); } if (offline_remote_nodes) { - print_as(state->output_format, "RemoteOFFLINE: [%s ]\n", offline_remote_nodes); + state->out->list_item(state->out, "RemoteOFFLINE", "[%s ]", offline_remote_nodes); free(offline_remote_nodes); } if (online_guest_nodes) { - print_as(state->output_format, "GuestOnline: [%s ]\n", online_guest_nodes); + state->out->list_item(state->out, "GuestOnline", "[%s ]", online_guest_nodes); free(online_guest_nodes); } + state->out->end_list(state->out); + state->out->info(state->out, "%s", ""); + /* Print resources section, if needed */ - print_resources(state, data_set, print_opts, mon_ops); + printed = print_resources(state, data_set, print_opts, mon_ops); /* print Node Attributes section if requested */ if (show & mon_show_attributes) { - print_node_attributes(state, data_set, mon_ops); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_node_attributes(state, data_set, mon_ops); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (show & (mon_show_operations | mon_show_failcounts)) { - print_node_summary(state, data_set, - ((show & mon_show_operations)? TRUE : FALSE), mon_ops); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_node_summary(state, data_set, + ((show & mon_show_operations)? TRUE : FALSE), mon_ops); } /* If there were any failed actions, print them */ if (xml_has_children(data_set->failed)) { - print_failed_actions(state, data_set); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_failed_actions(state, data_set); } /* Print failed stonith actions */ if (is_set(mon_ops, mon_op_fence_history)) { - print_failed_stonith_actions(state, stonith_history, mon_ops); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_failed_stonith_actions(state, stonith_history, mon_ops); } /* Print tickets if requested */ if (show & mon_show_tickets) { - print_cluster_tickets(state, data_set); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_cluster_tickets(state, data_set); } /* Print negative location constraints if requested */ if (show & mon_show_bans) { - print_neg_locations(state, data_set, mon_ops, prefix); + if (printed) { + state->out->info(state->out, "%s", ""); + } + + printed = print_neg_locations(state, data_set, mon_ops, prefix); } /* Print stonith history */ if (is_set(mon_ops, mon_op_fence_history)) { + if (printed) { + state->out->info(state->out, "%s", ""); + } + if (show & mon_show_fence_history) { print_stonith_history(state, stonith_history, mon_ops); } else { print_stonith_pending(state, stonith_history, mon_ops); } } #if CURSES_ENABLED if (state->output_format == mon_output_console) { refresh(); } #endif } void print_xml_status(mon_state_t *state, pe_working_set_t *data_set, stonith_history_t *stonith_history, unsigned int mon_ops, unsigned int show, const char *prefix) { GListPtr gIter = NULL; int print_opts = get_resource_display_options(mon_ops, state->output_format); - fprintf(state->stream, "\n"); - fprintf(state->stream, "\n", VERSION); - print_cluster_summary(state, data_set, mon_ops, show); /*** NODES ***/ - fprintf(state->stream, " \n"); + state->out->begin_list(state->out, NULL, NULL, "nodes"); for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; - const char *node_type = "unknown"; - - switch (node->details->type) { - case node_member: - node_type = "member"; - break; - case node_remote: - node_type = "remote"; - break; - case node_ping: - node_type = "ping"; - break; - } - - fprintf(state->stream, " details->uname); - fprintf(state->stream, "id=\"%s\" ", node->details->id); - fprintf(state->stream, "online=\"%s\" ", node->details->online ? "true" : "false"); - fprintf(state->stream, "standby=\"%s\" ", node->details->standby ? "true" : "false"); - fprintf(state->stream, "standby_onfail=\"%s\" ", node->details->standby_onfail ? "true" : "false"); - fprintf(state->stream, "maintenance=\"%s\" ", node->details->maintenance ? "true" : "false"); - fprintf(state->stream, "pending=\"%s\" ", node->details->pending ? "true" : "false"); - fprintf(state->stream, "unclean=\"%s\" ", node->details->unclean ? "true" : "false"); - fprintf(state->stream, "shutdown=\"%s\" ", node->details->shutdown ? "true" : "false"); - fprintf(state->stream, "expected_up=\"%s\" ", node->details->expected_up ? "true" : "false"); - fprintf(state->stream, "is_dc=\"%s\" ", node->details->is_dc ? "true" : "false"); - fprintf(state->stream, "resources_running=\"%d\" ", g_list_length(node->details->running_rsc)); - fprintf(state->stream, "type=\"%s\" ", node_type); - if (pe__is_guest_node(node)) { - fprintf(state->stream, "id_as_resource=\"%s\" ", node->details->remote_rsc->container->id); - } - - if (is_set(mon_ops, mon_op_group_by_node)) { - GListPtr lpc2 = NULL; - - fprintf(state->stream, ">\n"); - for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { - resource_t *rsc = (resource_t *) lpc2->data; - - rsc->fns->print(rsc, " ", print_opts | pe_print_rsconly, state->stream); - } - fprintf(state->stream, " \n"); - } else { - fprintf(state->stream, "/>\n"); - } + state->out->message(state->out, "node", node, mon_ops, TRUE); } - fprintf(state->stream, " \n"); + state->out->end_list(state->out); /* Print resources section, if needed */ print_resources(state, data_set, print_opts, mon_ops); /* print Node Attributes section if requested */ if (show & mon_show_attributes) { print_node_attributes(state, data_set, mon_ops); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (show & (mon_show_operations | mon_show_failcounts)) { print_node_summary(state, data_set, ((show & mon_show_operations)? TRUE : FALSE), mon_ops); } /* If there were any failed actions, print them */ if (xml_has_children(data_set->failed)) { print_failed_actions(state, data_set); } /* Print stonith history */ if (is_set(mon_ops, mon_op_fence_history)) { print_stonith_history(state, stonith_history, mon_ops); } /* Print tickets if requested */ if (show & mon_show_tickets) { print_cluster_tickets(state, data_set); } /* Print negative location constraints if requested */ if (show & mon_show_bans) { print_neg_locations(state, data_set, mon_ops, prefix); } - - fprintf(state->stream, "\n"); - fflush(state->stream); } int print_html_status(mon_state_t *state, pe_working_set_t *data_set, - const char *filename, stonith_history_t *stonith_history, - unsigned int mon_ops, unsigned int show, const char *prefix, + stonith_history_t *stonith_history, unsigned int mon_ops, + unsigned int show, const char *prefix, unsigned int reconnect_msec) { GListPtr gIter = NULL; - char *filename_tmp = NULL; int print_opts = get_resource_display_options(mon_ops, state->output_format); - if (state->output_format == mon_output_cgi) { - fprintf(state->stream, "Content-Type: text/html\n\n"); - - } else { - filename_tmp = crm_concat(filename, "tmp", '.'); - state->stream = fopen(filename_tmp, "w"); - if (state->stream == NULL) { - crm_perror(LOG_ERR, "Cannot open %s for writing", filename_tmp); - free(filename_tmp); - return -1; - } - } - - fprintf(state->stream, "\n"); - fprintf(state->stream, " \n"); - fprintf(state->stream, " Cluster status\n"); - fprintf(state->stream, " \n", reconnect_msec / 1000); - fprintf(state->stream, " \n"); - fprintf(state->stream, "\n"); - print_cluster_summary(state, data_set, mon_ops, show); /*** NODE LIST ***/ - - fprintf(state->stream, "
      \n

      Node List

      \n"); - fprintf(state->stream, "
        \n"); + state->out->begin_list(state->out, NULL, NULL, "Node List"); for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t *) gIter->data; - char *node_name = get_node_display_name(node, mon_ops); - - fprintf(state->stream, "
      • Node: %s: ", node_name); - if (node->details->standby_onfail && node->details->online) { - fprintf(state->stream, "standby (on-fail)\n"); - } else if (node->details->standby && node->details->online) { - - fprintf(state->stream, "standby%s\n", - node->details->running_rsc?" (with active resources)":""); - } else if (node->details->standby) { - fprintf(state->stream, "OFFLINE (standby)\n"); - } else if (node->details->maintenance && node->details->online) { - fprintf(state->stream, "maintenance\n"); - } else if (node->details->maintenance) { - fprintf(state->stream, "OFFLINE (maintenance)\n"); - } else if (node->details->online) { - fprintf(state->stream, "online\n"); - } else { - fprintf(state->stream, "OFFLINE\n"); - } - if (is_set(mon_ops, mon_op_print_brief) && is_set(mon_ops, mon_op_group_by_node)) { - fprintf(state->stream, "
          \n"); - print_rscs_brief(node->details->running_rsc, NULL, print_opts | pe_print_rsconly, - state->stream, FALSE); - fprintf(state->stream, "
        \n"); - - } else if (is_set(mon_ops, mon_op_group_by_node)) { - GListPtr lpc2 = NULL; - - fprintf(state->stream, "
          \n"); - for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { - resource_t *rsc = (resource_t *) lpc2->data; - - fprintf(state->stream, "
        • "); - rsc->fns->print(rsc, NULL, print_opts | pe_print_rsconly, state->stream); - fprintf(state->stream, "
        • \n"); - } - fprintf(state->stream, "
        \n"); - } - fprintf(state->stream, "
      • \n"); - free(node_name); + state->out->message(state->out, "node", node, mon_ops, TRUE); } - fprintf(state->stream, "
      \n"); + state->out->end_list(state->out); /* Print resources section, if needed */ print_resources(state, data_set, print_opts, mon_ops); /* print Node Attributes section if requested */ if (show & mon_show_attributes) { print_node_attributes(state, data_set, mon_ops); } /* If requested, print resource operations (which includes failcounts) * or just failcounts */ if (show & (mon_show_operations | mon_show_failcounts)) { print_node_summary(state, data_set, ((show & mon_show_operations)? TRUE : FALSE), mon_ops); } /* If there were any failed actions, print them */ if (xml_has_children(data_set->failed)) { print_failed_actions(state, data_set); } /* Print failed stonith actions */ if (is_set(mon_ops, mon_op_fence_history)) { print_failed_stonith_actions(state, stonith_history, mon_ops); } /* Print stonith history */ if (is_set(mon_ops, mon_op_fence_history)) { if (show & mon_show_fence_history) { print_stonith_history(state, stonith_history, mon_ops); } else { print_stonith_pending(state, stonith_history, mon_ops); } } /* Print tickets if requested */ if (show & mon_show_tickets) { print_cluster_tickets(state, data_set); } /* Print negative location constraints if requested */ if (show & mon_show_bans) { print_neg_locations(state, data_set, mon_ops, prefix); } - fprintf(state->stream, "\n"); - fprintf(state->stream, "\n"); - fflush(state->stream); - - if (state->output_format != mon_output_cgi) { - if (rename(filename_tmp, filename) != 0) { - crm_perror(LOG_ERR, "Unable to rename %s->%s", filename_tmp, filename); - } - free(filename_tmp); - } return 0; } diff --git a/tools/crm_mon_xml.c b/tools/crm_mon_xml.c new file mode 100644 index 0000000000..e6816ceb66 --- /dev/null +++ b/tools/crm_mon_xml.c @@ -0,0 +1,73 @@ +/* + * Copyright 2019 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 _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include "crm_mon.h" + +G_GNUC_PRINTF(4, 5) +static void +crm_mon_xml_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, + const char *format, ...) { + va_list ap; + const char *name = NULL; + char *buf = NULL; + int len; + + va_start(ap, format); + len = vasprintf(&buf, format, ap); + CRM_ASSERT(len >= 0); + va_end(ap); + + if (!strcmp(buf, "Active Resources") || !strcmp(buf, "Full List of Resources") || + !strcmp(buf, "Inactive Resources")) { + name = "resources"; + } else if (!strcmp(buf, "Cluster Summary")) { + name = "summary"; + } else if (!strcmp(buf, "Failed Resource Actions")) { + name = "failures"; + } else if (!strcmp(buf, "Fencing History")) { + name = "fence_history"; + } else if (!strcmp(buf, "Migration Summary") || !strcmp(buf, "Operations")) { + name = "node_history"; + } else if (!strcmp(buf, "Negative Location Constraints")) { + name = "bans"; + } else if (!strcmp(buf, "Node Attributes")) { + name = "node_attributes"; + } else if (!strcmp(buf, "Tickets")) { + name = "tickets"; + } else { + name = buf; + } + + pcmk__output_xml_create_parent(out, name); + free(buf); +} + +pcmk__output_t * +crm_mon_mk_xml_output(char **argv) { + pcmk__output_t *retval = pcmk__mk_xml_output(argv); + + if (retval == NULL) { + return NULL; + } + + retval->begin_list = crm_mon_xml_begin_list; + + return retval; +} diff --git a/xml/Makefile.am b/xml/Makefile.am index 43ad0a5f0f..7f826fc2d6 100644 --- a/xml/Makefile.am +++ b/xml/Makefile.am @@ -1,263 +1,263 @@ # # Copyright 2004-2019 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/Makefile.common noarch_pkgconfig_DATA = $(builddir)/pacemaker-schemas.pc # Pacemaker has 3 schemas: the CIB schema, the API schema (for command-line # tool XML output), and a legacy schema for crm_mon --as-xml. # # See Readme.md for details on updating CIB schema files (API is similar) # The CIB and crm_mon schemas are installed directly in CRM_SCHEMA_DIRECTORY # for historical reasons, while the API schema is installed in a subdirectory. APIdir = $(CRM_SCHEMA_DIRECTORY)/api CIBdir = $(CRM_SCHEMA_DIRECTORY) MONdir = $(CRM_SCHEMA_DIRECTORY) # Extract a sorted list of available numeric schema versions # from filenames like NAME-MAJOR[.MINOR][.MINOR-MINOR].rng numeric_versions = $(shell ls -1 $(1) \ | sed -n -e 's/^.*-\([0-9][0-9.]*\).rng$$/\1/p' \ | sort -u -t. -k 1,1n -k 2,2n -k 3,3n) version_pairs = $(join \ $(1),$(addprefix \ -,$(wordlist \ 2,$(words $(1)),$(1) \ ) next \ ) \ ) version_pairs_last = $(wordlist \ $(words \ $(wordlist \ 2,$(1),$(2) \ ) \ ),$(1),$(2) \ ) # Names of API schemas that form the choices for pacemaker-result content -API_request_base = command-output stonith_admin version +API_request_base = command-output crm_mon stonith_admin version # Names of CIB schemas that form the choices for cib/configuration content CIB_cfg_base = options nodes resources constraints fencing acls tags alerts # Names of all schemas (including top level and those included by others) -API_base = $(API_request_base) item status +API_base = $(API_request_base) fence-event item status CIB_base = cib $(CIB_cfg_base) status score rule nvset # Static schema files and transforms (only CIB has transforms) # # This is more complicated than it should be due to the need to support # VPATH builds and "make distcheck". We need the absolute paths for reliable # substitution back and forth, and relative paths for distributed files. API_abs_files = $(foreach base,$(API_base),$(wildcard $(abs_srcdir)/api/$(base)*.rng)) CIB_abs_files = $(foreach base,$(CIB_base),$(wildcard $(abs_srcdir)/$(base).rng $(abs_srcdir)/$(base)-*.rng)) CIB_abs_xsl = $(abs_srcdir)/upgrade-1.3.xsl \ $(abs_srcdir)/upgrade-2.10.xsl \ $(wildcard $(abs_srcdir)/upgrade-*enter.xsl) \ $(wildcard $(abs_srcdir)/upgrade-*leave.xsl) MON_abs_files = $(abs_srcdir)/crm_mon.rng API_files = $(foreach base,$(API_base),$(wildcard $(srcdir)/api/$(base)*.rng)) CIB_files = $(foreach base,$(CIB_base),$(wildcard $(srcdir)/$(base).rng $(srcdir)/$(base)-*.rng)) CIB_xsl = $(srcdir)/upgrade-1.3.xsl \ $(srcdir)/upgrade-2.10.xsl \ $(wildcard $(srcdir)/upgrade-*enter.xsl) \ $(wildcard $(srcdir)/upgrade-*leave.xsl) MON_files = $(srcdir)/crm_mon.rng # Sorted lists of all numeric schema versions API_numeric_versions = $(call numeric_versions,${API_files}) CIB_numeric_versions = $(call numeric_versions,${CIB_files}) # The highest numeric schema version API_max ?= $(lastword $(API_numeric_versions)) CIB_max ?= $(lastword $(CIB_numeric_versions)) # Sorted lists of all schema versions (including "next") API_versions = next $(API_numeric_versions) CIB_versions = next $(CIB_numeric_versions) # Build tree locations of static schema files and transforms (for VPATH builds) API_build_copies = $(foreach f,$(API_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) CIB_build_copies = $(foreach f,$(CIB_abs_files) $(CIB_abs_xsl),$(subst $(abs_srcdir),$(abs_builddir),$(f))) MON_build_copies = $(foreach f,$(MON_abs_files),$(subst $(abs_srcdir),$(abs_builddir),$(f))) # Dynamically generated schema files API_generated = api/api-result.rng $(foreach base,$(API_versions),api/api-result-$(base).rng) CIB_generated = pacemaker.rng $(foreach base,$(CIB_versions),pacemaker-$(base).rng) versions.rng CIB_version_pairs = $(call version_pairs,${CIB_numeric_versions}) CIB_version_pairs_cnt = $(words ${CIB_version_pairs}) CIB_version_pairs_last = $(call version_pairs_last,${CIB_version_pairs_cnt},${CIB_version_pairs}) dist_API_DATA = $(API_files) dist_CIB_DATA = $(CIB_files) $(CIB_xsl) dist_MON_DATA = $(MON_files) nodist_API_DATA = $(API_generated) nodist_CIB_DATA = $(CIB_generated) EXTRA_DIST = Readme.md \ best-match.sh \ cibtr-2.rng \ context-of.xsl \ ocf-meta2man.xsl \ regression.sh \ upgrade-2.10-roundtrip.xsl \ upgrade-detail.xsl \ xslt_cibtr-2.rng \ assets \ test-2 \ test-2-enter \ test-2-leave \ test-2-roundtrip cib-versions: @echo "Max: $(CIB_max)" @echo "Available: $(CIB_versions)" api-versions: @echo "Max: $(API_max)" @echo "Available: $(API_versions)" # Dynamically generated top-level API schema api/api-result.rng: api/api-result-$(API_max).rng $(AM_V_at)$(MKDIR_P) api # might not exist in VPATH build $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ api/api-result-%.rng: $(API_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(API_request_base); do $(srcdir)/best-match.sh api/$$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh api/status $(*) $(@) " " || : $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated top-level CIB schema pacemaker.rng: pacemaker-$(CIB_max).rng $(AM_V_SCHEMA)cp $(top_builddir)/xml/$< $@ pacemaker-%.rng: $(CIB_build_copies) best-match.sh Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh cib $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)for rng in $(CIB_cfg_base); do $(srcdir)/best-match.sh $$rng $(*) $(@) " " || :; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)$(srcdir)/best-match.sh status $(*) $(@) " " $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # Dynamically generated CIB schema listing all pacemaker versions versions.rng: Makefile.am $(AM_V_at)echo '' > $@ $(AM_V_at)echo '' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' none' >> $@ $(AM_V_at)echo ' pacemaker-0.6' >> $@ $(AM_V_at)echo ' transitional-0.6' >> $@ $(AM_V_at)echo ' pacemaker-0.7' >> $@ $(AM_V_at)echo ' pacemaker-1.1' >> $@ $(AM_V_at)for rng in $(CIB_versions); do echo " pacemaker-$$rng" >> $@; done $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_at)echo ' ' >> $@ $(AM_V_SCHEMA)echo '' >> $@ # diff fails with ec=2 if no predecessor is found; # this uses '=' GNU extension to sed, if that's not available, # one can use: hline=`echo "$${p}" | grep -Fn "$${hunk}" | cut -d: -f1`; # XXX: use line information from hunk to avoid "not detected" for ambiguity version_diff = \ @for p in $(1); do \ set `echo "$${p}" | tr '-' ' '`; \ echo "\#\#\# *-$$2.rng vs. predecessor"; \ for v in *-$$2.rng; do \ echo "\#\#\#\# $${v} vs. predecessor"; b=`echo "$${v}" | cut -d- -f1`; \ old=`./best-match.sh $${b} $$1`; \ p=`diff -u "$${old}" "$${v}" 2>/dev/null`; \ case $$? in \ 1) echo "$${p}" | sed -n -e '/^@@ /!d;=;p' \ -e ':l;n;/^\([- ]\|+.*<[^ />]\+\([^/>]\+="ID\|>$$\)\)/bl;s/^[+ ]\(.*\)/\1/p' \ | while read hline; do \ read h && read i || break; \ iline=`grep -Fn "$${i}" "$${v}" | cut -d: -f1`; \ ctxt="(not detected)"; \ if test `echo "$${iline}" | wc -l` -eq 1; then \ ctxt=`{ sed -n -e "1,$$(($${iline}-1))p" "$${v}"; \ echo "$${i}"; \ sed -n -e "$$(($${iline}+1)),$$ p" "$${v}"; \ } | $(XSLTPROC) --param skip 1 context-of.xsl -`; \ fi; \ echo "$${p}" | sed -n -e "$$(($${hline}-2)),$${hline}!d" \ -e '/^\(+++\|---\)/p'; \ echo "$${h} context: $${ctxt}"; \ echo "$${p}" | sed -n -e "1,$${hline}d" \ -e '/^\(---\|@@ \)/be;p;d;:e;n;be'; \ done; \ ;; \ 2) echo "\#\#\#\#\# $${v} has no predecessor";; \ esac; \ done; \ done diff: best-match.sh @echo "# Comparing changes in + since $(CIB_max)" $(call version_diff,${CIB_version_pairs_last}) fulldiff: best-match.sh @echo "# Comparing all changes across all the subsequent increments" $(call version_diff,${CIB_version_pairs}) CLEANFILES = $(API_generated) $(CIB_generated) clean-local: if [ "x$(srcdir)" != "x$(builddir)" ]; then \ rm -f $(API_build_copies) $(CIB_build_copies) $(MON_build_copies); \ fi # Enable ability to use $@ in prerequisite .SECONDEXPANSION: # For VPATH builds, copy the static schema files into the build tree $(API_build_copies) $(CIB_build_copies) $(MON_build_copies): $$(subst $(abs_builddir),$(srcdir),$$(@)) $(AM_V_GEN)if [ "x$(srcdir)" != "x$(builddir)" ]; then \ $(MKDIR_P) "$(dir $(@))"; \ cp "$(<)" "$(@)"; \ fi diff --git a/xml/api/crm_mon-2.0.rng b/xml/api/crm_mon-2.0.rng new file mode 100644 index 0000000000..c03cd15354 --- /dev/null +++ b/xml/api/crm_mon-2.0.rng @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + unknown + member + remote + ping + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + granted + revoked + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + docker + rkt + podman + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/api/fence-event-2.0.rng b/xml/api/fence-event-2.0.rng new file mode 100644 index 0000000000..e54687cd25 --- /dev/null +++ b/xml/api/fence-event-2.0.rng @@ -0,0 +1,33 @@ + + + + + + + + + + + + failed + success + pending + + + + + + + + + + + + + + + + + + diff --git a/xml/api/stonith_admin-2.0.rng b/xml/api/stonith_admin-2.0.rng new file mode 100644 index 0000000000..260417c790 --- /dev/null +++ b/xml/api/stonith_admin-2.0.rng @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/crm_mon.rng b/xml/crm_mon.rng index 61cf598902..703d230113 100644 --- a/xml/crm_mon.rng +++ b/xml/crm_mon.rng @@ -1,381 +1,16 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - unknown - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - unknown - member - remote - ping - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - docker - rkt - podman - - - - - - - - - - - - - - +