Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4624092
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
212 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h
index 1c8c176f90..e0f146a4e0 100644
--- a/include/crm/pengine/internal.h
+++ b/include/crm/pengine/internal.h
@@ -1,442 +1,444 @@
/*
* Copyright 2004-2020 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 <string.h>
# include <crm/pengine/status.h>
# include <crm/pengine/remote_internal.h>
# include <crm/common/output.h>
# define pe_rsc_info(rsc, fmt, args...) crm_log_tag(LOG_INFO, rsc ? rsc->id : "<NULL>", fmt, ##args)
# define pe_rsc_debug(rsc, fmt, args...) crm_log_tag(LOG_DEBUG, rsc ? rsc->id : "<NULL>", fmt, ##args)
# define pe_rsc_trace(rsc, fmt, args...) crm_log_tag(LOG_TRACE, rsc ? rsc->id : "<NULL>", 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, ...);
char *pe__node_display_name(node_t *node, bool print_detail);
int pe__ban_html(pcmk__output_t *out, va_list args);
int pe__ban_text(pcmk__output_t *out, va_list args);
int pe__ban_xml(pcmk__output_t *out, va_list args);
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__cluster_counts_html(pcmk__output_t *out, va_list args);
int pe__cluster_counts_text(pcmk__output_t *out, va_list args);
int pe__cluster_counts_xml(pcmk__output_t *out, va_list args);
int pe__cluster_dc_html(pcmk__output_t *out, va_list args);
int pe__cluster_dc_text(pcmk__output_t *out, va_list args);
int pe__cluster_dc_xml(pcmk__output_t *out, va_list args);
int pe__cluster_maint_mode_html(pcmk__output_t *out, va_list args);
int pe__cluster_maint_mode_text(pcmk__output_t *out, va_list args);
int pe__cluster_options_html(pcmk__output_t *out, va_list args);
int pe__cluster_options_log(pcmk__output_t *out, va_list args);
int pe__cluster_options_text(pcmk__output_t *out, va_list args);
int pe__cluster_options_xml(pcmk__output_t *out, va_list args);
int pe__cluster_stack_html(pcmk__output_t *out, va_list args);
int pe__cluster_stack_text(pcmk__output_t *out, va_list args);
int pe__cluster_stack_xml(pcmk__output_t *out, va_list args);
+int pe__cluster_summary(pcmk__output_t *out, va_list args);
+int pe__cluster_summary_html(pcmk__output_t *out, va_list args);
int pe__cluster_times_html(pcmk__output_t *out, va_list args);
int pe__cluster_times_xml(pcmk__output_t *out, va_list args);
int pe__cluster_times_text(pcmk__output_t *out, va_list args);
int pe__failed_action_text(pcmk__output_t *out, va_list args);
int pe__failed_action_xml(pcmk__output_t *out, va_list args);
int pe__group_xml(pcmk__output_t *out, va_list args);
int pe__group_html(pcmk__output_t *out, va_list args);
int pe__group_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__node_html(pcmk__output_t *out, va_list args);
int pe__node_text(pcmk__output_t *out, va_list args);
int pe__node_xml(pcmk__output_t *out, va_list args);
int pe__node_attribute_html(pcmk__output_t *out, va_list args);
int pe__node_attribute_text(pcmk__output_t *out, va_list args);
int pe__node_attribute_xml(pcmk__output_t *out, va_list args);
int pe__op_history_text(pcmk__output_t *out, va_list args);
int pe__op_history_xml(pcmk__output_t *out, va_list args);
int pe__resource_history_text(pcmk__output_t *out, va_list args);
int pe__resource_history_xml(pcmk__output_t *out, va_list args);
int pe__resource_xml(pcmk__output_t *out, va_list args);
int pe__resource_html(pcmk__output_t *out, va_list args);
int pe__resource_text(pcmk__output_t *out, va_list args);
int pe__ticket_html(pcmk__output_t *out, va_list args);
int pe__ticket_text(pcmk__output_t *out, va_list args);
int pe__ticket_xml(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);
void pe__count_common(pe_resource_t *rsc);
void pe__count_bundle(pe_resource_t *rsc);
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 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);
void pe__show_node_weights_as(const char *file, const char *function,
int line, bool to_log, pe_resource_t *rsc,
const char *comment, GHashTable *nodes);
#define pe__show_node_weights(level, rsc, text, nodes) \
pe__show_node_weights_as(__FILE__, __FUNCTION__, __LINE__, \
(level), (rsc), (text), (nodes))
/* 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) pcmk__op_key(rsc->id, CRMD_ACTION_DELETE, 0)
# define delete_action(rsc, node, optional) custom_action( \
rsc, delete_key(rsc), CRMD_ACTION_DELETE, node, \
optional, TRUE, data_set);
# define stopped_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOPPED, 0)
# define stopped_action(rsc, node, optional) custom_action( \
rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node, \
optional, TRUE, data_set);
# define stop_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOP, 0)
# define stop_action(rsc, node, optional) custom_action( \
rsc, stop_key(rsc), CRMD_ACTION_STOP, node, \
optional, TRUE, data_set);
# define reload_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_RELOAD, 0)
# define start_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_START, 0)
# define start_action(rsc, node, optional) custom_action( \
rsc, start_key(rsc), CRMD_ACTION_START, node, \
optional, TRUE, data_set)
# define started_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STARTED, 0)
# define started_action(rsc, node, optional) custom_action( \
rsc, started_key(rsc), CRMD_ACTION_STARTED, node, \
optional, TRUE, data_set)
# define promote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTE, 0)
# define promote_action(rsc, node, optional) custom_action( \
rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node, \
optional, TRUE, data_set)
# define promoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTED, 0)
# define promoted_action(rsc, node, optional) custom_action( \
rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node, \
optional, TRUE, data_set)
# define demote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTE, 0)
# define demote_action(rsc, node, optional) custom_action( \
rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node, \
optional, TRUE, data_set)
# define demoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTED, 0)
# define demoted_action(rsc, node, optional) custom_action( \
rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node, \
optional, TRUE, 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(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);
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 *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);
bool pe__resource_is_disabled(pe_resource_t *rsc);
pe_action_t *pe__clear_resource_history(pe_resource_t *rsc, pe_node_t *node,
pe_working_set_t *data_set);
#endif
diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c
index 28a7055133..0d665205bb 100644
--- a/lib/pengine/pe_output.c
+++ b/lib/pengine/pe_output.c
@@ -1,1379 +1,1530 @@
/*
* Copyright 2019-2020 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 <crm_internal.h>
#include <crm/common/iso8601_internal.h>
#include <crm/msg_xml.h>
#include <crm/pengine/internal.h>
+#define SUMMARY_HEADER(printed, out) do { \
+ if (printed == FALSE) { \
+ out->begin_list(out, NULL, NULL, "Cluster Summary"); \
+ printed = TRUE; \
+ } \
+ } while (0)
+
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) {
crm_time_t *crm_when = crm_time_new(NULL);
char *time_s = NULL;
char *buf = NULL;
crm_time_set_timet(crm_when, &last_change);
time_s = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
buf = crm_strdup_printf("%s on %s '%s' (%d): call=%s, status='%s', "
"exitreason='%s', " XML_RSC_OP_LAST_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_s,
crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
crm_time_free(crm_when);
free(time_s);
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 const char *
get_cluster_stack(pe_working_set_t *data_set)
{
xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
data_set->input, LOG_DEBUG);
return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
}
static char *
last_changed_string(const char *last_written, const char *user,
const char *client, const char *origin) {
if (last_written != NULL || user != NULL || client != NULL || origin != NULL) {
return crm_strdup_printf("%s%s%s%s%s%s%s",
last_written ? last_written : "",
user ? " by " : "",
user ? user : "",
client ? " via " : "",
client ? client : "",
origin ? " on " : "",
origin ? origin : "");
} else {
return strdup("");
}
}
static char *
op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s,
int rc, gboolean print_timing) {
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 (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%s", call, task,
interval_str ? ":" : "",
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 = NULL;
if (last_failure > 0) {
lastfail_s = crm_strdup_printf(" %s='%s'", CRM_LAST_FAILURE_PREFIX,
pcmk__epoch2str(&last_failure));
}
buf = crm_strdup_printf("%s: migration-threshold=%d%s%s",
rsc_id, rsc->migration_threshold, failcount_s,
lastfail_s? lastfail_s : "");
free(failcount_s);
free(lastfail_s);
} else {
buf = crm_strdup_printf("%s:", rsc_id);
}
return buf;
}
+int
+pe__cluster_summary(pcmk__output_t *out, va_list args) {
+ pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+ gboolean print_clone_detail = va_arg(args, gboolean);
+ gboolean show_stack = va_arg(args, gboolean);
+ gboolean show_dc = va_arg(args, gboolean);
+ gboolean show_times = va_arg(args, gboolean);
+ gboolean show_counts = va_arg(args, gboolean);
+ gboolean show_options = va_arg(args, gboolean);
+
+ const char *stack_s = get_cluster_stack(data_set);
+ gboolean header_printed = FALSE;
+
+ if (show_stack) {
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-stack", stack_s);
+ }
+
+ /* Always print DC if none, even if not requested */
+ if (data_set->dc_node == NULL || show_dc) {
+ 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 = data_set->dc_node ? pe__node_display_name(data_set->dc_node, print_clone_detail) : NULL;
+
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-dc", data_set->dc_node, quorum, dc_version_s, dc_name);
+ free(dc_name);
+ }
+
+ if (show_times) {
+ 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);
+
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-times", last_written, user, client, origin);
+ }
+
+ if (show_counts) {
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-counts", g_list_length(data_set->nodes),
+ data_set->ninstances, data_set->disabled_resources,
+ data_set->blocked_resources);
+ }
+
+ if (show_options) {
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-options", data_set);
+ }
+
+ if (header_printed) {
+ out->end_list(out);
+ }
+
+ if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
+ out->message(out, "maint-mode");
+ }
+
+ return 0;
+}
+
+int
+pe__cluster_summary_html(pcmk__output_t *out, va_list args) {
+ pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
+ gboolean print_clone_detail = va_arg(args, gboolean);
+ gboolean show_stack = va_arg(args, gboolean);
+ gboolean show_dc = va_arg(args, gboolean);
+ gboolean show_times = va_arg(args, gboolean);
+ gboolean show_counts = va_arg(args, gboolean);
+ gboolean show_options = va_arg(args, gboolean);
+
+ const char *stack_s = get_cluster_stack(data_set);
+ gboolean header_printed = FALSE;
+
+ if (show_stack) {
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-stack", stack_s);
+ }
+
+ /* Always print DC if none, even if not requested */
+ if (data_set->dc_node == NULL || show_dc) {
+ 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 = data_set->dc_node ? pe__node_display_name(data_set->dc_node, print_clone_detail) : NULL;
+
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-dc", data_set->dc_node, quorum, dc_version_s, dc_name);
+ free(dc_name);
+ }
+
+ if (show_times) {
+ 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);
+
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-times", last_written, user, client, origin);
+ }
+
+ if (show_counts) {
+ SUMMARY_HEADER(header_printed, out);
+ out->message(out, "cluster-counts", g_list_length(data_set->nodes),
+ data_set->ninstances, data_set->disabled_resources,
+ data_set->blocked_resources);
+ }
+
+ if (show_options) {
+ /* Kind of a hack - close the list we may have opened earlier in this
+ * function so we can put all the options into their own list. We
+ * only want to do this on HTML output, though.
+ */
+ if (header_printed == TRUE) {
+ out->end_list(out);
+ }
+
+ out->begin_list(out, NULL, NULL, "Config Options");
+ out->message(out, "cluster-options", data_set);
+ }
+
+ if (header_printed) {
+ out->end_list(out);
+ }
+
+ if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
+ out->message(out, "maint-mode");
+ }
+
+ return 0;
+}
+
char *
pe__node_display_name(node_t *node, bool print_detail)
{
char *node_name;
const char *node_host = NULL;
const char *node_id = NULL;
int name_len;
CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
/* Host is displayed only if this is a guest node */
if (pe__is_guest_node(node)) {
node_t *host_node = pe__current_node(node->details->remote_rsc);
if (host_node && host_node->details) {
node_host = host_node->details->uname;
}
if (node_host == NULL) {
node_host = ""; /* so we at least get "uname@" to indicate guest */
}
}
/* Node ID is displayed if different from uname and detail is requested */
if (print_detail && safe_str_neq(node->details->uname, node->details->id)) {
node_id = node->details->id;
}
/* Determine name length */
name_len = strlen(node->details->uname) + 1;
if (node_host) {
name_len += strlen(node_host) + 1; /* "@node_host" */
}
if (node_id) {
name_len += strlen(node_id) + 3; /* + " (node_id)" */
}
/* Allocate and populate display name */
node_name = malloc(name_len);
CRM_ASSERT(node_name != NULL);
strcpy(node_name, node->details->uname);
if (node_host) {
strcat(node_name, "@");
strcat(node_name, node_host);
}
if (node_id) {
strcat(node_name, " (");
strcat(node_name, node_id);
strcat(node_name, ")");
}
return node_name;
}
int
pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
, 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;
}
int
pe__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 *);
gboolean print_clone_detail = va_arg(args, gboolean);
char *node_name = pe__node_display_name(pe_node, print_clone_detail);
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;
}
int
pe__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 *);
gboolean print_clone_detail = va_arg(args, gboolean);
char *node_name = pe__node_display_name(pe_node, print_clone_detail);
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;
}
int
pe__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;
}
int
pe__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);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, int);
char *nnodes_str = crm_strdup_printf("%d node%s configured",
nnodes, pcmk__plural_s(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 instance%s configured (%d ",
nresources, pcmk__plural_s(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 further action due to failure)");
} else if (ndisabled && !nblocked) {
char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
nresources, pcmk__plural_s(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 instance%s configured (%d ",
nresources, pcmk__plural_s(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 further action due to failure)");
} else {
char *s = crm_strdup_printf("%d resource instance%s configured",
nresources, pcmk__plural_s(nresources));
pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
free(s);
}
return 0;
}
int
pe__cluster_counts_text(pcmk__output_t *out, va_list args) {
unsigned int nnodes = va_arg(args, unsigned int);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, int);
out->list_item(out, NULL, "%d node%s configured",
nnodes, pcmk__plural_s(nnodes));
if (ndisabled && nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d DISABLED, %d BLOCKED from "
"further action due to failure)",
nresources, pcmk__plural_s(nresources), ndisabled,
nblocked);
} else if (ndisabled && !nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d DISABLED)",
nresources, pcmk__plural_s(nresources), ndisabled);
} else if (!ndisabled && nblocked) {
out->list_item(out, NULL, "%d resource instance%s configured "
"(%d BLOCKED from further action "
"due to failure)",
nresources, pcmk__plural_s(nresources), nblocked);
} else {
out->list_item(out, NULL, "%d resource instance%s configured",
nresources, pcmk__plural_s(nresources));
}
return 0;
}
int
pe__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);
int nresources = va_arg(args, int);
int ndisabled = va_arg(args, int);
int nblocked = va_arg(args, 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;
}
int
pe__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 *);
char *dc_name = va_arg(args, 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;
}
int
pe__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 *);
char *dc_name = va_arg(args, 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;
}
int
pe__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;
}
int
pe__cluster_maint_mode_text(pcmk__output_t *out, va_list args) {
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;
}
int
pe__cluster_options_html(pcmk__output_t *out, va_list args) {
pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
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, NULL, "Resource management: ");
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;
}
int
pe__cluster_options_log(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)) {
out->info(out, "Resource management is DISABLED. The cluster will not attempt to start, stop or recover services.");
}
return 0;
}
int
pe__cluster_options_text(pcmk__output_t *out, va_list args) {
pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
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;
}
return 0;
}
int
pe__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;
}
int
pe__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;
}
int
pe__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;
}
int
pe__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;
}
int
pe__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,
pcmk__epoch2str(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;
}
int
pe__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) pcmk__epoch2str(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;
}
int
pe__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", pcmk__epoch2str(NULL));
out->list_item(out, "Last change", " %s", buf);
free(buf);
return 0;
}
int
pe__failed_action_text(pcmk__output_t *out, va_list args) {
xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
char *s = failed_action_string(xml_op);
out->list_item(out, NULL, "%s", s);
free(s);
return 0;
}
int
pe__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) {
guint interval_ms = 0;
char *s = NULL;
time_t when = crm_parse_int(last, "0");
crm_time_t *crm_when = crm_time_new(NULL);
char *rc_change = NULL;
crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
s = crm_itoa(interval_ms);
crm_time_set_timet(crm_when, &when);
rc_change = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
xmlSetProp(node, (pcmkXmlStr) XML_RSC_OP_LAST_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);
crm_time_free(crm_when);
}
free(reason_s);
free(rc_s);
return 0;
}
int
pe__node_html(pcmk__output_t *out, va_list args) {
node_t *node = va_arg(args, node_t *);
unsigned int print_opts = va_arg(args, unsigned int);
gboolean full = va_arg(args, gboolean);
const char *node_mode G_GNUC_UNUSED = va_arg(args, const char *);
gboolean print_clone_detail = va_arg(args, gboolean);
gboolean print_brief = va_arg(args, gboolean);
gboolean group_by_node = va_arg(args, gboolean);
char *node_name = pe__node_display_name(node, print_clone_detail);
char *buf = crm_strdup_printf("Node: %s", node_name);
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 (print_brief && 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 (group_by_node) {
GListPtr lpc2 = NULL;
out->begin_list(out, NULL, NULL, NULL);
for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
pe_resource_t *rsc = (pe_resource_t *) lpc2->data;
out->message(out, crm_map_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;
}
int
pe__node_text(pcmk__output_t *out, va_list args) {
node_t *node = va_arg(args, node_t *);
unsigned int print_opts = va_arg(args, unsigned int);
gboolean full = va_arg(args, gboolean);
const char *node_mode = va_arg(args, const char *);
gboolean print_clone_detail = va_arg(args, gboolean);
gboolean print_brief = va_arg(args, gboolean);
gboolean group_by_node = va_arg(args, gboolean);
if (full) {
char *node_name = pe__node_display_name(node, print_clone_detail);
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 (group_by_node) {
out->begin_list(out, NULL, NULL, "%s", buf);
out->begin_list(out, NULL, NULL, "Resources");
if (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) {
pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
out->message(out, crm_map_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", pe__node_display_name(node, print_clone_detail));
}
return 0;
}
int
pe__node_xml(pcmk__output_t *out, va_list args) {
node_t *node = va_arg(args, node_t *);
unsigned int print_opts = va_arg(args, unsigned int);
gboolean full = va_arg(args, gboolean);
const char *node_mode G_GNUC_UNUSED = va_arg(args, const char *);
gboolean print_clone_detail G_GNUC_UNUSED = va_arg(args, gboolean);
gboolean print_brief G_GNUC_UNUSED = va_arg(args, gboolean);
gboolean group_by_node = va_arg(args, gboolean);
if (full) {
const char *node_type = "unknown";
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 (group_by_node) {
GListPtr lpc = NULL;
for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) {
pe_resource_t *rsc = (pe_resource_t *) lpc->data;
out->message(out, crm_map_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;
}
int
pe__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;
}
int
pe__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;
}
int
pe__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;
}
int
pe__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);
gboolean print_timing = va_arg(args, gboolean);
char *buf = op_history_string(xml_op, task, interval_ms_s, rc, print_timing);
out->list_item(out, NULL, "%s", buf);
free(buf);
return 0;
}
int
pe__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);
gboolean print_timing = va_arg(args, gboolean);
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 (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) pcmk__epoch2str(&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) pcmk__epoch2str(&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;
}
int
pe__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);
gboolean as_header = va_arg(args, gboolean);
char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure);
if (as_header) {
out->begin_list(out, NULL, NULL, "%s", buf);
} else {
out->list_item(out, NULL, "%s", buf);
}
free(buf);
return 0;
}
int
pe__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);
gboolean as_header = va_arg(args, gboolean);
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) pcmk__epoch2str(&last_failure));
}
}
if (as_header == FALSE) {
pcmk__output_xml_pop_parent(out);
}
return 0;
}
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;
}
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;
}
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) pcmk__epoch2str(&ticket->last_granted));
}
return 0;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "ban", "html", pe__ban_html },
{ "ban", "log", pe__ban_text },
{ "ban", "text", pe__ban_text },
{ "ban", "xml", pe__ban_xml },
{ "bundle", "xml", pe__bundle_xml },
{ "bundle", "html", pe__bundle_html },
{ "bundle", "text", pe__bundle_text },
{ "bundle", "log", pe__bundle_text },
{ "clone", "xml", pe__clone_xml },
{ "clone", "html", pe__clone_html },
{ "clone", "text", pe__clone_text },
{ "clone", "log", pe__clone_text },
{ "cluster-counts", "html", pe__cluster_counts_html },
{ "cluster-counts", "log", pe__cluster_counts_text },
{ "cluster-counts", "text", pe__cluster_counts_text },
{ "cluster-counts", "xml", pe__cluster_counts_xml },
{ "cluster-dc", "html", pe__cluster_dc_html },
{ "cluster-dc", "log", pe__cluster_dc_text },
{ "cluster-dc", "text", pe__cluster_dc_text },
{ "cluster-dc", "xml", pe__cluster_dc_xml },
{ "cluster-options", "html", pe__cluster_options_html },
{ "cluster-options", "log", pe__cluster_options_log },
{ "cluster-options", "text", pe__cluster_options_text },
{ "cluster-options", "xml", pe__cluster_options_xml },
+ { "cluster-summary", "html", pe__cluster_summary_html },
+ { "cluster-summary", "log", pe__cluster_summary },
+ { "cluster-summary", "text", pe__cluster_summary },
+ { "cluster-summary", "xml", pe__cluster_summary },
{ "cluster-stack", "html", pe__cluster_stack_html },
{ "cluster-stack", "log", pe__cluster_stack_text },
{ "cluster-stack", "text", pe__cluster_stack_text },
{ "cluster-stack", "xml", pe__cluster_stack_xml },
{ "cluster-times", "html", pe__cluster_times_html },
{ "cluster-times", "log", pe__cluster_times_text },
{ "cluster-times", "text", pe__cluster_times_text },
{ "cluster-times", "xml", pe__cluster_times_xml },
{ "failed-action", "html", pe__failed_action_text },
{ "failed-action", "log", pe__failed_action_text },
{ "failed-action", "text", pe__failed_action_text },
{ "failed-action", "xml", pe__failed_action_xml },
{ "group", "xml", pe__group_xml },
{ "group", "html", pe__group_html },
{ "group", "text", pe__group_text },
{ "group", "log", pe__group_text },
/* maint-mode only exists for text and log. Other formatters output it as
* part of the cluster-options handler.
*/
{ "maint-mode", "log", pe__cluster_maint_mode_text },
{ "maint-mode", "text", pe__cluster_maint_mode_text },
{ "node", "html", pe__node_html },
{ "node", "log", pe__node_text },
{ "node", "text", pe__node_text },
{ "node", "xml", pe__node_xml },
{ "node-attribute", "html", pe__node_attribute_html },
{ "node-attribute", "log", pe__node_attribute_text },
{ "node-attribute", "text", pe__node_attribute_text },
{ "node-attribute", "xml", pe__node_attribute_xml },
{ "op-history", "html", pe__op_history_text },
{ "op-history", "log", pe__op_history_text },
{ "op-history", "text", pe__op_history_text },
{ "op-history", "xml", pe__op_history_xml },
{ "primitive", "xml", pe__resource_xml },
{ "primitive", "html", pe__resource_html },
{ "primitive", "text", pe__resource_text },
{ "primitive", "log", pe__resource_text },
{ "resource-history", "html", pe__resource_history_text },
{ "resource-history", "log", pe__resource_history_text },
{ "resource-history", "text", pe__resource_history_text },
{ "resource-history", "xml", pe__resource_history_xml },
{ "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("<NULL>");
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;
// @TODO pe_print_log probably doesn't belong here
out->message(out, crm_map_element_name(rsc->xml),
pe_print_log|pe_print_pending, rsc);
}
}
}
diff --git a/tools/crm_mon.c b/tools/crm_mon.c
index 6a92dfb01a..4d31bd9993 100644
--- a/tools/crm_mon.c
+++ b/tools/crm_mon.c
@@ -1,2141 +1,2141 @@
/*
* Copyright 2004-2020 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 <crm_internal.h>
#include <sys/param.h>
#include <crm/crm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <sys/utsname.h>
#include <crm/msg_xml.h>
#include <crm/services.h>
#include <crm/lrmd.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/curses_internal.h>
#include <crm/common/internal.h> // pcmk__ends_with_ext()
#include <crm/common/ipc.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/output.h>
#include <crm/common/util.h>
#include <crm/common/xml.h>
#include <crm/cib/internal.h>
#include <crm/pengine/status.h>
#include <crm/pengine/internal.h>
#include <pacemaker-internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#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;
/*
* Definitions indicating how to output
*/
static mon_output_format_t output_format = mon_output_unset;
/* 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 GError *error = NULL;
static pcmk__common_args_t *args = NULL;
static pcmk__output_t *out = NULL;
static GOptionContext *context = NULL;
static gchar **processed_args = NULL;
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,
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
#define RECONNECT_MSECS 5000
struct {
int reconnect_msec;
gboolean daemonize;
gboolean show_bans;
char *pid_file;
char *external_agent;
char *external_recipient;
char *neg_location_prefix;
unsigned int mon_ops;
GSList *user_includes_excludes;
GSList *includes_excludes;
} options = {
.reconnect_msec = RECONNECT_MSECS,
.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 unsigned int
all_includes(mon_output_format_t fmt) {
if (fmt == mon_output_monitor || fmt == mon_output_plain || fmt == mon_output_console) {
return ~mon_show_options;
} else {
return mon_show_all;
}
}
static unsigned int
default_includes(mon_output_format_t fmt) {
switch (fmt) {
case mon_output_monitor:
case mon_output_plain:
case mon_output_console:
return mon_show_stack | mon_show_dc | mon_show_times | mon_show_counts |
mon_show_nodes | mon_show_resources | mon_show_failures;
break;
case mon_output_xml:
case mon_output_legacy_xml:
return all_includes(fmt);
break;
case mon_output_html:
case mon_output_cgi:
return mon_show_summary | mon_show_nodes | mon_show_resources |
mon_show_failures;
break;
default:
return 0;
break;
}
}
struct {
const char *name;
unsigned int bit;
} sections[] = {
{ "attributes", mon_show_attributes },
{ "bans", mon_show_bans },
{ "counts", mon_show_counts },
{ "dc", mon_show_dc },
{ "failcounts", mon_show_failcounts },
{ "failures", mon_show_failures },
{ "fencing", mon_show_fencing_all },
{ "fencing-failed", mon_show_fence_failed },
{ "fencing-pending", mon_show_fence_pending },
{ "fencing-succeeded", mon_show_fence_worked },
{ "nodes", mon_show_nodes },
{ "operations", mon_show_operations },
{ "options", mon_show_options },
{ "resources", mon_show_resources },
{ "stack", mon_show_stack },
{ "summary", mon_show_summary },
{ "tickets", mon_show_tickets },
{ "times", mon_show_times },
{ NULL }
};
static unsigned int
find_section_bit(const char *name) {
for (int i = 0; sections[i].name != NULL; i++) {
if (crm_str_eq(sections[i].name, name, FALSE)) {
return sections[i].bit;
}
}
return 0;
}
static gboolean
apply_exclude(const gchar *excludes, GError **error) {
char **parts = NULL;
parts = g_strsplit(excludes, ",", 0);
for (char **s = parts; *s != NULL; s++) {
unsigned int bit = find_section_bit(*s);
if (crm_str_eq(*s, "all", TRUE)) {
show = 0;
} else if (crm_str_eq(*s, "none", TRUE)) {
show = all_includes(output_format);
} else if (bit != 0) {
show &= ~bit;
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
"--exclude options: all, attributes, bans, counts, dc, "
"failcounts, failures, fencing, fencing-failed, "
"fencing-pending, fencing-succeeded, nodes, none, "
"operations, options, resources, stack, summary, "
"tickets, times");
return FALSE;
}
}
g_strfreev(parts);
return TRUE;
}
static gboolean
apply_include(const gchar *includes, GError **error) {
char **parts = NULL;
parts = g_strsplit(includes, ",", 0);
for (char **s = parts; *s != NULL; s++) {
unsigned int bit = find_section_bit(*s);
if (crm_str_eq(*s, "all", TRUE)) {
show = all_includes(output_format);
} else if (pcmk__starts_with(*s, "bans")) {
show |= mon_show_bans;
if (options.neg_location_prefix != NULL) {
free(options.neg_location_prefix);
options.neg_location_prefix = NULL;
}
if (strlen(*s) > 4 && (*s)[4] == ':') {
options.neg_location_prefix = strdup(*s+5);
}
} else if (crm_str_eq(*s, "default", TRUE) || crm_str_eq(*s, "defaults", TRUE)) {
show |= default_includes(output_format);
} else if (crm_str_eq(*s, "none", TRUE)) {
show = 0;
} else if (bit != 0) {
show |= bit;
} else {
g_set_error(error, G_OPTION_ERROR, CRM_EX_USAGE,
"--include options: all, attributes, bans[:PREFIX], counts, dc, "
"default, failcounts, failures, fencing, fencing-failed, "
"fencing-pending, fencing-succeeded, nodes, none, operations, "
"options, resources, stack, summary, tickets, times");
return FALSE;
}
}
g_strfreev(parts);
return TRUE;
}
static gboolean
apply_include_exclude(GSList *lst, mon_output_format_t fmt, GError **error) {
gboolean rc = TRUE;
GSList *node = lst;
/* Set the default of what to display here. Note that we OR everything to
* show instead of set show directly because it could have already had some
* settings applied to it in main.
*/
show |= default_includes(fmt);
while (node != NULL) {
char *s = node->data;
if (pcmk__starts_with(s, "--include=")) {
rc = apply_include(s+10, error);
} else if (pcmk__starts_with(s, "-I=")) {
rc = apply_include(s+3, error);
} else if (pcmk__starts_with(s, "--exclude=")) {
rc = apply_exclude(s+10, error);
} else if (pcmk__starts_with(s, "-U=")) {
rc = apply_exclude(s+3, error);
}
if (rc != TRUE) {
break;
}
node = node->next;
}
return rc;
}
static gboolean
user_include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
char *s = crm_strdup_printf("%s=%s", option_name, optarg);
options.user_includes_excludes = g_slist_append(options.user_includes_excludes, s);
return TRUE;
}
static gboolean
include_exclude_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
char *s = crm_strdup_printf("%s=%s", option_name, optarg);
options.includes_excludes = g_slist_append(options.includes_excludes, s);
return TRUE;
}
static gboolean
as_cgi_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
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 **err) {
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;
umask(S_IWGRP | S_IWOTH);
return TRUE;
}
static gboolean
as_simple_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
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 **err) {
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 **err) {
int rc = crm_atoi(optarg, "2");
switch (rc) {
case 3:
options.mon_ops |= mon_op_fence_full_history | mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing", data, err);
case 2:
options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing", data, err);
case 1:
options.mon_ops |= mon_op_fence_history | mon_op_fence_connect;
return include_exclude_cb("--include", "fencing-failed,fencing-pending", data, err);
case 0:
options.mon_ops &= ~(mon_op_fence_history | mon_op_fence_connect);
return include_exclude_cb("--exclude", "fencing", data, err);
default:
g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Fence history must be 0-3");
return FALSE;
}
}
static gboolean
group_by_node_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
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 **err) {
return include_exclude_cb("--exclude", "summary", data, err);
}
static gboolean
inactive_resources_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_inactive_resources;
return TRUE;
}
static gboolean
no_curses_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
output_format = mon_output_plain;
return TRUE;
}
static gboolean
one_shot_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_one_shot;
return TRUE;
}
static gboolean
print_brief_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
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 **err) {
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 **err) {
options.mon_ops |= mon_op_print_pending;
return TRUE;
}
static gboolean
print_timing_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
options.mon_ops |= mon_op_print_timing;
return include_exclude_cb("--include", "operations", data, err);
}
static gboolean
reconnect_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
int rc = crm_get_msec(optarg);
if (rc == -1) {
g_set_error(err, G_OPTION_ERROR, CRM_EX_INVALID_PARAM, "Invalid value for -i: %s", optarg);
return FALSE;
} else {
options.reconnect_msec = crm_parse_interval_spec(optarg);
}
return TRUE;
}
static gboolean
show_attributes_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "attributes", data, err);
}
static gboolean
show_bans_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
if (optarg != NULL) {
char *s = crm_strdup_printf("bans:%s", optarg);
gboolean rc = include_exclude_cb("--include", s, data, err);
free(s);
return rc;
} else {
return include_exclude_cb("--include", "bans", data, err);
}
}
static gboolean
show_failcounts_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "failcounts", data, err);
}
static gboolean
show_operations_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "failcounts,operations", data, err);
}
static gboolean
show_tickets_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
return include_exclude_cb("--include", "tickets", data, err);
}
static gboolean
use_cib_file_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **err) {
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 **err) {
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 (default is 5 seconds)",
"TIMESPEC" },
{ "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 },
{ "daemonize", 'd', 0, G_OPTION_ARG_NONE, &options.daemonize,
"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" },
{ "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 },
{ "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, use_cib_file_cb,
NULL,
NULL },
{ NULL }
};
static GOptionEntry display_entries[] = {
{ "include", 'I', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
"A list of sections to include in the output.\n"
INDENT "See `Output Control` help for more information.",
"SECTION(s)" },
{ "exclude", 'U', 0, G_OPTION_ARG_CALLBACK, user_include_exclude_cb,
"A list of sections to exclude from the output.\n"
INDENT "See `Output Control` help for more information.",
"SECTION(s)" },
{ "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 },
{ "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 },
{ "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 }
};
static GOptionEntry deprecated_entries[] = {
{ "as-html", 'h', G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, as_html_cb,
"Write cluster status to the named HTML file.\n"
INDENT "Use --output-as=html --output-to=FILE instead.",
"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.\n"
INDENT "Use --output-as=xml instead.",
NULL },
{ "disable-ncurses", 'N', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, no_curses_cb,
"Disable the use of ncurses.\n"
INDENT "Use --output-as=text instead.",
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).\n"
INDENT "Use --output-as=html --html-cgi instead.",
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)
{
out->err(out, "Connection to the cluster-daemons terminated");
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) {
out->err(out, "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) \
out->info(out, "%c %c: \t%s", ((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 (is_not_set(show, mon_show_fencing_all)) {
options.mon_ops |= mon_op_fence_history;
options.mon_ops |= mon_op_fence_connect;
if (st == NULL) {
mon_cib_connection_destroy(NULL);
}
}
if (is_set(show, mon_show_fence_failed) || is_set(show, mon_show_fence_pending) ||
is_set(show, mon_show_fence_worked)) {
show &= ~mon_show_fencing_all;
} else {
show |= mon_show_fencing_all;
}
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 (is_not_set(show, mon_show_operations)) {
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 (is_set(show, mon_show_stack) || is_set(show, mon_show_dc) ||
is_set(show, mon_show_times) || is_set(show, mon_show_counts)) {
show &= ~mon_show_summary;
} else {
show |= mon_show_summary;
}
/* Regardless, we don't show options in console mode. */
show &= ~mon_show_options;
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();
out->info(out, "%s", "Display option change mode\n");
print_option_help(out, 'c', is_set(show, mon_show_tickets));
print_option_help(out, 'f', is_set(show, mon_show_failcounts));
print_option_help(out, 'n', is_set(options.mon_ops, mon_op_group_by_node));
print_option_help(out, 'o', is_set(show, mon_show_operations));
print_option_help(out, 'r', is_set(options.mon_ops, mon_op_inactive_resources));
print_option_help(out, 't', is_set(options.mon_ops, mon_op_print_timing));
print_option_help(out, 'A', is_set(show, mon_show_attributes));
print_option_help(out, 'L', is_set(show,mon_show_bans));
print_option_help(out, 'D', is_not_set(show, mon_show_summary));
print_option_help(out, 'R', is_set(options.mon_ops, mon_op_print_clone_detail));
print_option_help(out, 'b', is_set(options.mon_ops, mon_op_print_brief));
print_option_help(out, 'j', is_set(options.mon_ops, mon_op_print_pending));
print_option_help(out, 'm', is_set(show, mon_show_fencing_all));
out->info(out, "%s", "\nToggle 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, GOptionGroup **group) {
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 *description = "Notes:\n\n"
"If this program is called as crm_mon.cgi, --output-as=html --html-cgi will\n"
"automatically be added to the command line arguments.\n\n"
"Time Specification:\n\n"
"The TIMESPEC in any command line option can be specified in many different\n"
"formats. It can be just an integer number of seconds, a number plus units\n"
"(ms/msec/us/usec/s/sec/m/min/h/hr), or an ISO 8601 period specification.\n\n"
"Output Control:\n\n"
"By default, a certain list of sections are written to the output destination.\n"
"The default varies based on the output format - XML includes everything, while\n"
"other output formats will display less. This list can be modified with the\n"
"--include and --exclude command line options. Each option may be given multiple\n"
"times on the command line, and each can give a comma-separated list of sections.\n"
"The options are applied to the default set, from left to right as seen on the\n"
"command line. For a list of valid sections, pass --include=list or --exclude=list.\n\n"
"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\n";
context = pcmk__build_arg_context(args, "console (default), html, text, xml", group);
pcmk__add_main_args(context, extra_prog_entries);
g_option_context_set_description(context, description);
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);
pcmk__add_arg_group(context, "deprecated", "Deprecated Options:",
"Show deprecated options", deprecated_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 *err = NULL;
if (output_format == mon_output_plain) {
if (!pcmk__force_args(context, &err, "%s --text-fancy", g_get_prgname())) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_html) {
if (!pcmk__force_args(context, &err, "%s --html-title \"Cluster Status\"",
g_get_prgname())) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_cgi) {
if (!pcmk__force_args(context, &err, "%s --html-cgi --html-title \"Cluster Status\"", g_get_prgname())) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_xml) {
if (!pcmk__force_args(context, &err, "%s --xml-simple-list", g_get_prgname())) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
} else if (output_format == mon_output_legacy_xml) {
output_format = mon_output_xml;
if (!pcmk__force_args(context, &err, "%s --xml-legacy", g_get_prgname())) {
fprintf(stderr, "%s: %s\n", g_get_prgname(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
}
}
/* Which output format to use could come from two places: The --as-xml
* style arguments we gave in deprecated_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 *err = 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, &err);
free(dest);
} else if (safe_str_eq(args->output_ty, "text")) {
retval = no_curses_cb("N", NULL, NULL, &err);
} 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(), err->message);
g_clear_error(&err);
clean_up(CRM_EX_USAGE);
}
}
int
main(int argc, char **argv)
{
int rc = pcmk_ok;
GOptionGroup *output_group = NULL;
args = pcmk__new_common_args(SUMMARY);
context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, 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 (pcmk__ends_with_ext(argv[0], ".cgi")) {
output_format = mon_output_cgi;
options.mon_ops |= mon_op_one_shot;
}
processed_args = pcmk__cmdline_preproc(argv, "ehimpxEILU");
fence_history_cb("--fence-history", "1", NULL, NULL);
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);
}
if (!args->version) {
if (args->quiet) {
include_exclude_cb("--exclude", "times", NULL, NULL);
}
if (is_set(options.mon_ops, mon_op_watch_fencing)) {
fence_history_cb("--fence-history", "0", NULL, NULL);
options.mon_ops |= mon_op_fence_connect;
}
/* 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. */
fence_history_cb("--fence-history", "0", NULL, NULL);
options.mon_ops |= mon_op_one_shot;
break;
case cib_remote:
/* updates coming in but no fencing */
fence_history_cb("--fence-history", "0", NULL, NULL);
break;
case cib_undefined:
case cib_database:
default:
/* something is odd */
rc = -EINVAL;
break;
}
}
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 ((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 (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 (rc != pcmk_ok) {
// Shouldn't really be possible
fprintf(stderr, "Invalid CIB source\n");
return clean_up(CRM_EX_ERROR);
}
reconcile_output_format(args);
add_output_args();
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);
}
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Error creating output format %s: %s\n",
args->output_ty, pcmk_rc_str(rc));
return clean_up(CRM_EX_ERROR);
}
/* output_format MUST NOT BE CHANGED AFTER THIS POINT. */
/* Apply --include/--exclude flags we used internally. There's no error reporting
* here because this would be a programming error.
*/
apply_include_exclude(options.includes_excludes, output_format, &error);
/* And now apply any --include/--exclude flags the user gave on the command line.
* These are done in a separate pass from the internal ones because we want to
* make sure whatever the user specifies overrides whatever we do.
*/
if (!apply_include_exclude(options.user_includes_excludes, output_format, &error)) {
out->err(out, "%s: %s", g_get_prgname(), error->message);
return clean_up(0);
}
crm_mon_register_messages(out);
pe__register_messages(out);
stonith__register_messages(out);
if (args->version) {
out->version(out, false);
return clean_up(CRM_EX_OK);
}
/* Extra sanity checks when in CGI mode */
if (output_format == mon_output_cgi) {
if (cib && cib->variant == cib_file) {
out->err(out, "CGI mode used with CIB file");
return clean_up(CRM_EX_USAGE);
} else if (options.external_agent != NULL) {
out->err(out, "CGI mode cannot be used with --external-agent");
return clean_up(CRM_EX_USAGE);
} else if (options.daemonize == TRUE) {
out->err(out, "CGI mode cannot be used with -d");
return clean_up(CRM_EX_USAGE);
}
}
if (output_format == mon_output_xml || output_format == mon_output_legacy_xml) {
options.mon_ops |= mon_op_print_timing;
}
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 && 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) {
out->err(out, "CLUSTER CRIT: Connection to cluster failed: %s",
pcmk_strerror(rc));
return clean_up(MON_STATUS_CRIT);
} else {
if (rc == -ENOTCONN) {
out->err(out, "\nError: cluster is not available on this node");
} else {
out->err(out, "\nConnection to cluster failed: %s", 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);
}
/*!
* \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(pcmk__output_t *out, pe_working_set_t * data_set,
stonith_history_t *history, unsigned int mon_ops)
{
GListPtr gIter = NULL;
int nodes_online = 0;
int nodes_standby = 0;
int nodes_maintenance = 0;
char *offline_nodes = NULL;
gboolean no_dc = FALSE;
gboolean offline = FALSE;
if (data_set->dc_node == NULL) {
mon_ops |= mon_op_has_warnings;
no_dc = TRUE;
}
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 {
char *s = crm_strdup_printf("offline node: %s", node->details->uname);
offline_nodes = pcmk__add_word(offline_nodes, s);
free(s);
mon_ops |= mon_op_has_warnings;
offline = TRUE;
}
}
if (is_set(mon_ops, mon_op_has_warnings)) {
out->info(out, "CLUSTER WARN:%s%s%s",
no_dc ? " No DC" : "",
no_dc && offline ? "," : "",
offline ? offline_nodes : "");
free(offline_nodes);
} else {
char *nodes_standby_s = NULL;
char *nodes_maint_s = NULL;
if (nodes_standby > 0) {
nodes_standby_s = crm_strdup_printf(", %d standby node%s", nodes_standby,
pcmk__plural_s(nodes_standby));
}
if (nodes_maintenance > 0) {
nodes_maint_s = crm_strdup_printf(", %d maintenance node%s",
nodes_maintenance,
pcmk__plural_s(nodes_maintenance));
}
out->info(out, "CLUSTER OK: %d node%s online%s%s, "
"%d resource instance%s configured",
nodes_online, pcmk__plural_s(nodes_online),
nodes_standby_s != NULL ? nodes_standby_s : "",
nodes_maint_s != NULL ? nodes_maint_s : "",
data_set->ninstances, pcmk__plural_s(data_set->ninstances));
free(nodes_standby_s);
free(nodes_maint_s);
}
}
/*!
* \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;
last_refresh = time(NULL);
if (cli_config_update(&cib_copy, NULL, FALSE) == FALSE) {
if (cib) {
cib->cmds->signoff(cib);
}
out->err(out, "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)) {
out->err(out, "Critical: Unable to get stonith-history");
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 {
out->err(out, "Critical: No stonith-API");
}
free_xml(cib_copy);
out->err(out, "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);
}
set_bit(mon_data_set->flags, pe_flag_no_compat);
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 (is_set(show, mon_show_bans) || is_set(show, 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(out, output_format, mon_data_set, stonith_history,
- options.mon_ops, show, options.neg_location_prefix) != 0) {
+ if (print_html_status(out, mon_data_set, stonith_history, options.mon_ops,
+ show, options.neg_location_prefix) != 0) {
out->err(out, "Critical: Unable to output html file");
clean_up(CRM_EX_CANTCREAT);
return FALSE;
}
break;
case mon_output_legacy_xml:
case mon_output_xml:
- print_xml_status(out, output_format, mon_data_set, stonith_history,
- options.mon_ops, show, options.neg_location_prefix);
+ print_xml_status(out, mon_data_set, stonith_history, options.mon_ops,
+ show, options.neg_location_prefix);
break;
case mon_output_monitor:
print_simple_status(out, mon_data_set, stonith_history, options.mon_ops);
if (is_set(options.mon_ops, mon_op_has_warnings)) {
clean_up(MON_STATUS_WARN);
return FALSE;
}
break;
case mon_output_console:
/* If curses is not enabled, this will just fall through to the plain
* text case.
*/
#if CURSES_ENABLED
blank_screen();
- print_status(out, output_format, mon_data_set, stonith_history, options.mon_ops,
- show, options.neg_location_prefix);
+ print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
+ options.neg_location_prefix);
refresh();
break;
#endif
case mon_output_plain:
- print_status(out, output_format, mon_data_set, stonith_history, options.mon_ops,
- show, options.neg_location_prefix);
+ print_status(out, mon_data_set, stonith_history, options.mon_ops, show,
+ options.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;
}
}
static void
handle_html_output(crm_exit_t exit_code) {
xmlNodePtr html = NULL;
pcmk__html_add_header(html, "meta", "http-equiv", "refresh", "content",
crm_itoa(options.reconnect_msec/1000), NULL);
out->finish(out, exit_code, true, (void **) &html);
}
/*
* 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)
{
clean_up_connections();
free(options.pid_file);
free(options.neg_location_prefix);
g_slist_free_full(options.includes_excludes, free);
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));
}
}
pcmk__free_arg_context(context);
g_clear_error(&error);
if (out != NULL) {
switch (output_format) {
case mon_output_cgi:
case mon_output_html:
handle_html_output(exit_code);
break;
case mon_output_console:
output_format = mon_output_plain;
out->finish(out, exit_code, true, NULL);
break;
default:
out->finish(out, exit_code, true, NULL);
break;
}
pcmk__output_free(out);
}
g_strfreev(processed_args);
crm_exit(exit_code);
}
diff --git a/tools/crm_mon.h b/tools/crm_mon.h
index df788783d0..3a5139d350 100644
--- a/tools/crm_mon.h
+++ b/tools/crm_mon.h
@@ -1,125 +1,125 @@
/*
* Copyright 2019-2020 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 <glib.h>
#include <crm/common/output.h>
#include <crm/common/curses_internal.h>
#include <crm/pengine/pe_types.h>
#include <crm/stonith-ng.h>
/* 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 }
#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_stack (1 << 0)
#define mon_show_dc (1 << 1)
#define mon_show_times (1 << 2)
#define mon_show_counts (1 << 3)
#define mon_show_options (1 << 4)
#define mon_show_nodes (1 << 5)
#define mon_show_resources (1 << 6)
#define mon_show_attributes (1 << 7)
#define mon_show_failcounts (1 << 8)
#define mon_show_operations (1 << 9)
#define mon_show_fence_failed (1 << 10)
#define mon_show_fence_pending (1 << 11)
#define mon_show_fence_worked (1 << 12)
#define mon_show_tickets (1 << 13)
#define mon_show_bans (1 << 14)
#define mon_show_failures (1 << 15)
#define mon_show_fencing_all (mon_show_fence_failed | mon_show_fence_pending | mon_show_fence_worked)
#define mon_show_summary (mon_show_stack | mon_show_dc | mon_show_times | \
mon_show_counts | mon_show_options)
#define mon_show_all (mon_show_summary | mon_show_nodes | mon_show_resources | \
mon_show_attributes | mon_show_failcounts | mon_show_operations | \
mon_show_fencing_all | mon_show_tickets | mon_show_bans | \
mon_show_failures)
#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 | mon_op_fence_history | mon_op_fence_connect)
-void print_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix);
-void print_xml_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix);
-int print_html_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix);
+void print_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix);
+void print_xml_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix);
+int print_html_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix);
GList *append_attr_list(GList *attr_list, char *name);
void blank_screen(void);
void crm_mon_get_parameters(resource_t *rsc, pe_working_set_t *data_set);
unsigned int get_resource_display_options(unsigned int mon_ops);
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 7bc0a07f41..d9327b202c 100644
--- a/tools/crm_mon_curses.c
+++ b/tools/crm_mon_curses.c
@@ -1,394 +1,395 @@
/*
* 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 <stdarg.h>
#include <stdlib.h>
#include <crm/crm.h>
#include <crm/common/curses_internal.h>
#include <crm/common/output.h>
#include <crm/pengine/internal.h>
#include <glib.h>
#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) {
echo();
nocbreak();
endwin();
}
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);
out->increment_list(out);
}
static void
curses_increment_list(pcmk__output_t *out) {
private_data_t *priv = out->priv;
gpointer tail;
CRM_ASSERT(priv != NULL);
tail = g_queue_peek_tail(priv->parent_q);
CRM_ASSERT(tail != NULL);
((curses_list_data_t *) tail)->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 = argv == NULL ? NULL : 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->increment_list = curses_increment_list;
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++) {
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);
}
static int
stonith_event_console(pcmk__output_t *out, va_list args) {
stonith_history_t *event = va_arg(args, stonith_history_t *);
gboolean full_history = va_arg(args, gboolean);
gboolean later_succeeded = va_arg(args, gboolean);
crm_time_t *crm_when = crm_time_new(NULL);
char *buf = NULL;
crm_time_set_timet(crm_when, &(event->completed));
buf = crm_time_as_string(crm_when, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone);
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);
crm_time_free(crm_when);
return 0;
}
static int
cluster_maint_mode_console(pcmk__output_t *out, va_list args) {
printw("\n *** Resource management is DISABLED ***");
printw("\n The cluster will not attempt to start, stop or recover services");
printw("\n");
clrtoeol();
refresh();
return 0;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "ban", "console", pe__ban_text },
{ "bundle", "console", pe__bundle_text },
{ "clone", "console", pe__clone_text },
{ "cluster-counts", "console", pe__cluster_counts_text },
{ "cluster-dc", "console", pe__cluster_dc_text },
{ "cluster-options", "console", pe__cluster_options_text },
{ "cluster-stack", "console", pe__cluster_stack_text },
+ { "cluster-summary", "console", pe__cluster_summary },
{ "cluster-times", "console", pe__cluster_times_text },
{ "failed-action", "console", pe__failed_action_text },
{ "group", "console", pe__group_text },
{ "maint-mode", "console", cluster_maint_mode_console },
{ "node", "console", pe__node_text },
{ "node-attribute", "console", pe__node_attribute_text },
{ "op-history", "console", pe__op_history_text },
{ "primitive", "console", pe__resource_text },
{ "resource-history", "console", pe__resource_history_text },
{ "stonith-event", "console", stonith_event_console },
{ "ticket", "console", pe__ticket_text },
{ NULL, NULL, NULL }
};
void
crm_mon_register_messages(pcmk__output_t *out) {
pcmk__register_messages(out, fmt_functions);
}
#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, ...) {
return;
}
void
crm_mon_register_messages(pcmk__output_t *out) {
return;
}
#endif
diff --git a/tools/crm_mon_print.c b/tools/crm_mon_print.c
index 2215989c4e..64809c1393 100644
--- a/tools/crm_mon_print.c
+++ b/tools/crm_mon_print.c
@@ -1,1279 +1,1200 @@
/*
* Copyright 2019-2020 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 <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <config.h>
#include <crm/cib/util.h>
#include <crm/common/curses_internal.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/xml.h>
#include <crm/msg_xml.h>
#include <crm/pengine/internal.h>
#include <crm/pengine/pe_types.h>
#include <crm/stonith-ng.h>
#include <crm/common/internal.h>
#include <crm/common/util.h>
#include <crm/fencing/internal.h>
#include "crm_mon.h"
static void print_resources_heading(pcmk__output_t *out, unsigned int mon_ops);
static void print_resources_closing(pcmk__output_t *out, unsigned int mon_ops);
static void print_resources(pcmk__output_t *out, pe_working_set_t *data_set,
unsigned int print_opts, unsigned int mon_ops, gboolean brief_output,
gboolean print_summary);
static void print_rsc_history(pcmk__output_t *out, pe_working_set_t *data_set,
node_t *node, xmlNode *rsc_entry, unsigned int mon_ops,
GListPtr op_list);
static void print_node_history(pcmk__output_t *out, pe_working_set_t *data_set,
xmlNode *node_state, gboolean operations,
unsigned int mon_ops);
static gboolean add_extra_info(pcmk__output_t *out, node_t * node, GListPtr rsc_list,
const char *attrname, int *expected_score);
static void print_node_attribute(gpointer name, gpointer user_data);
static void print_node_summary(pcmk__output_t *out, pe_working_set_t * data_set,
gboolean operations, unsigned int mon_ops);
static void print_cluster_tickets(pcmk__output_t *out, pe_working_set_t * data_set);
static void print_neg_locations(pcmk__output_t *out, pe_working_set_t *data_set,
unsigned int mon_ops, const char *prefix);
static void print_node_attributes(pcmk__output_t *out, pe_working_set_t *data_set,
unsigned int mon_ops);
-static gboolean print_cluster_summary(pcmk__output_t *out, pe_working_set_t *data_set,
- unsigned int mon_ops, unsigned int show,
- mon_output_format_t fmt);
static void print_failed_actions(pcmk__output_t *out, pe_working_set_t *data_set);
static void print_failed_stonith_actions(pcmk__output_t *out, stonith_history_t *history,
unsigned int mon_ops);
static void print_stonith_pending(pcmk__output_t *out, stonith_history_t *history,
unsigned int mon_ops);
static void print_stonith_history(pcmk__output_t *out, stonith_history_t *history,
unsigned int mon_ops);
static gboolean print_stonith_history_full(pcmk__output_t *out, stonith_history_t *history,
unsigned int mon_ops);
/*!
* \internal
* \brief Print resources section heading appropriate to options
*
* \param[in] out The output functions structure.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_resources_heading(pcmk__output_t *out, 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;
} else if (is_set(mon_ops, mon_op_inactive_resources)) {
heading = "Full List of Resources";
} else {
heading = "Active Resources";
}
/* Print section heading */
out->begin_list(out, NULL, NULL, "%s", heading);
}
/*!
* \internal
* \brief Print whatever resource section closing is appropriate
*
* \param[in] out The output functions structure.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_resources_closing(pcmk__output_t *out, 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 ";
}
out->list_item(out, NULL, "No %sresources", heading);
}
/*!
* \internal
* \brief Print whatever resource section(s) are appropriate
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] print_opts Bitmask of pe_print_*.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] brief_output Whether to display full or brief output.
* \param[in] print_summary Whether to display a failure summary.
*/
static void
print_resources(pcmk__output_t *out, pe_working_set_t *data_set,
unsigned int print_opts, unsigned int mon_ops, gboolean brief_output,
gboolean print_summary)
{
GListPtr rsc_iter;
gboolean printed_resource = FALSE;
/* 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;
}
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
print_resources_heading(out, 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)) {
pe__rscs_brief_output(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) {
pe_resource_t *rsc = (pe_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_resource == FALSE) {
printed_resource = TRUE;
}
out->message(out, crm_map_element_name(rsc->xml), print_opts, rsc);
}
if (print_summary && !printed_resource) {
print_resources_closing(out, mon_ops);
}
out->end_list(out);
}
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;
}
static GListPtr
get_operation_list(xmlNode *rsc_entry) {
GListPtr op_list = NULL;
xmlNode *rsc_op = NULL;
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);
return op_list;
}
/*!
* \internal
* \brief Print resource operation/failure history
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] node Node that ran this resource.
* \param[in] rsc_entry Root of XML tree describing resource status.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] op_list A list of operations to print.
*/
static void
print_rsc_history(pcmk__output_t *out, pe_working_set_t *data_set, node_t *node,
xmlNode *rsc_entry, unsigned int mon_ops, GListPtr op_list)
{
GListPtr gIter = 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);
/* 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);
out->message(out, "resource-history", rsc, rsc_id, TRUE, failcount, last_failure, TRUE);
printed = TRUE;
}
/* Print the operation */
out->message(out, "op-history", xml_op, task, interval_ms_s,
rc, is_set(mon_ops, mon_op_print_timing));
}
/* 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) {
out->end_list(out);
}
}
/*!
* \internal
* \brief Print node operation/failure history
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] node_state Root of XML tree describing node status.
* \param[in] operations Whether to print operations or just failcounts.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_node_history(pcmk__output_t *out, 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) {
return;
}
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)) {
continue;
}
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;
out->message(out, "node", node, get_resource_display_options(mon_ops),
FALSE, NULL, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
}
out->message(out, "resource-history", rsc, rsc_id, FALSE,
failcount, last_failure, FALSE);
}
} else {
GListPtr op_list = get_operation_list(rsc_entry);
if (printed_header == FALSE) {
printed_header = TRUE;
out->message(out, "node", node, get_resource_display_options(mon_ops),
FALSE, NULL, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
}
if (op_list != NULL) {
print_rsc_history(out, data_set, node, rsc_entry, mon_ops, op_list);
}
}
}
if (printed_header) {
out->end_list(out);
}
}
/*!
* \internal
* \brief Determine whether extended information about an attribute should be added.
*
* \param[in] out The output functions structure.
* \param[in] node Node that ran this resource.
* \param[in] rsc_list The list of resources for this node.
* \param[in] attrname The attribute to find.
* \param[out] expected_score The expected value for this attribute.
*
* \return TRUE if extended information should be printed, FALSE otherwise
* \note Currently, extended information is only supported for ping/pingd
* resources, for which a message will be printed if connectivity is lost
* or degraded.
*/
static gboolean
add_extra_info(pcmk__output_t *out, node_t *node, GListPtr rsc_list,
const char *attrname, 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 (add_extra_info(out, node, rsc->children, attrname, expected_score)) {
return TRUE;
}
}
if (safe_str_neq(type, "ping") && safe_str_neq(type, "pingd")) {
return FALSE;
}
name = g_hash_table_lookup(rsc->parameters, "name");
if (name == NULL) {
name = "pingd";
}
/* 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);
}
/* pingd multiplier is the same as the default value. */
*expected_score = host_list_num * crm_parse_int(multiplier, "1");
return TRUE;
}
}
return FALSE;
}
/* structure for passing multiple user data to g_list_foreach() */
struct mon_attr_data {
pcmk__output_t *out;
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);
add_extra = add_extra_info(data->out, data->node, data->node->details->running_rsc,
name, &expected_score);
/* Print attribute name and value */
data->out->message(data->out, "node-attribute", name, value, add_extra,
expected_score);
}
/*!
* \internal
* \brief Print history for all nodes.
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] operations Whether to print operations or just failcounts.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_node_summary(pcmk__output_t *out, 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);
gboolean printed_header = FALSE;
if (xmlChildElementCount(cib_status) == 0) {
return;
}
/* 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)) {
continue;
}
if (printed_header == FALSE) {
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
if (operations) {
out->begin_list(out, NULL, NULL, "Operations");
} else {
out->begin_list(out, NULL, NULL, "Migration Summary");
}
printed_header = TRUE;
}
print_node_history(out, data_set, node_state, operations, mon_ops);
}
if (printed_header == TRUE) {
out->end_list(out);
}
}
/*!
* \internal
* \brief Print all tickets.
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
*/
static void
print_cluster_tickets(pcmk__output_t *out, pe_working_set_t * data_set)
{
GHashTableIter iter;
gpointer key, value;
if (g_hash_table_size(data_set->tickets) == 0) {
return;
}
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
/* Print section heading */
out->begin_list(out, NULL, NULL, "Tickets");
/* Print each ticket */
g_hash_table_iter_init(&iter, data_set->tickets);
while (g_hash_table_iter_next(&iter, &key, &value)) {
ticket_t *ticket = (ticket_t *) value;
out->message(out, "ticket", ticket);
}
/* Close section */
out->end_list(out);
}
/*!
* \internal
* \brief Print section for negative location constraints
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] prefix ID prefix to filter results by.
*/
static void
print_neg_locations(pcmk__output_t *out, pe_working_set_t *data_set, unsigned int mon_ops,
const char *prefix)
{
GListPtr gIter, gIter2;
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 (prefix != NULL && !g_str_has_prefix(location->id, prefix))
continue;
for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
pe_node_t *node = (pe_node_t *) gIter2->data;
if (node->weight < 0) {
if (printed_header == FALSE) {
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
printed_header = TRUE;
out->begin_list(out, NULL, NULL, "Negative Location Constraints");
}
out->message(out, "ban", node, location, is_set(mon_ops, mon_op_print_clone_detail));
}
}
}
if (printed_header) {
out->end_list(out);
}
}
/*!
* \internal
* \brief Print node attributes section
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_node_attributes(pcmk__output_t *out, pe_working_set_t *data_set, unsigned int mon_ops)
{
GListPtr gIter = NULL;
gboolean printed_header = FALSE;
/* Unpack all resource parameters (it would be more efficient to do this
* 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.out = out;
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;
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);
}
if (attr_list == NULL) {
continue;
}
if (printed_header == FALSE) {
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
printed_header = TRUE;
out->begin_list(out, NULL, NULL, "Node Attributes");
}
out->message(out, "node", data.node, get_resource_display_options(mon_ops),
FALSE, NULL, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
g_list_foreach(attr_list, print_node_attribute, &data);
g_list_free(attr_list);
out->end_list(out);
}
}
/* Print section footer */
if (printed_header) {
out->end_list(out);
}
}
-/*!
- * \internal
- * \brief Print a summary of cluster-wide information
- *
- * \param[in] out The output functions structure.
- * \param[in] data_set Cluster state to display.
- * \param[in] mon_ops Bitmask of mon_op_*.
- * \param[in] show Bitmask of mon_show_*.
- */
-static gboolean
-print_cluster_summary(pcmk__output_t *out, pe_working_set_t *data_set,
- unsigned int mon_ops, unsigned int show, mon_output_format_t fmt)
-{
- const char *stack_s = get_cluster_stack(data_set);
- gboolean header_printed = FALSE;
-
- if (is_set(show, mon_show_stack)) {
- if (header_printed == FALSE) {
- out->begin_list(out, NULL, NULL, "Cluster Summary");
- header_printed = TRUE;
- }
- out->message(out, "cluster-stack", stack_s);
- }
-
- /* Always print DC if none, even if not requested */
- if ((data_set->dc_node == NULL) || is_set(show, mon_show_dc)) {
- 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 = data_set->dc_node ? pe__node_display_name(data_set->dc_node, is_set(mon_ops, mon_op_print_clone_detail)) : NULL;
-
- if (header_printed == FALSE) {
- out->begin_list(out, NULL, NULL, "Cluster Summary");
- header_printed = TRUE;
- }
-
- out->message(out, "cluster-dc", data_set->dc_node, quorum, dc_version_s, dc_name);
- free(dc_name);
- }
-
- if (is_set(show, mon_show_times)) {
- 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);
-
- if (header_printed == FALSE) {
- out->begin_list(out, NULL, NULL, "Cluster Summary");
- header_printed = TRUE;
- }
-
- out->message(out, "cluster-times", last_written, user, client, origin);
- }
-
- if (is_set(show, mon_show_counts)) {
- if (header_printed == FALSE) {
- out->begin_list(out, NULL, NULL, "Cluster Summary");
- header_printed = TRUE;
- }
- out->message(out, "cluster-counts", g_list_length(data_set->nodes),
- data_set->ninstances, data_set->disabled_resources,
- data_set->blocked_resources);
- }
-
- if (is_set(show, mon_show_options)) {
- if (fmt == mon_output_html || fmt == mon_output_cgi) {
- /* Kind of a hack - close the list we may have opened earlier in this
- * function so we can put all the options into their own list. We
- * only want to do this on HTML output, though.
- */
- if (header_printed == TRUE) {
- out->end_list(out);
- }
-
- out->begin_list(out, NULL, NULL, "Config Options");
- } else if (header_printed == FALSE) {
- out->begin_list(out, NULL, NULL, "Cluster Summary");
- header_printed = TRUE;
- }
-
- out->message(out, "cluster-options", data_set);
- }
-
- if (header_printed) {
- out->end_list(out);
- }
-
- if (is_set(data_set->flags, pe_flag_maintenance_mode)) {
- out->message(out, "maint-mode");
- }
-
- return header_printed;
-}
-
/*!
* \internal
* \brief Print a section for failed actions
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
*/
static void
print_failed_actions(pcmk__output_t *out, pe_working_set_t *data_set)
{
xmlNode *xml_op = NULL;
if (xmlChildElementCount(data_set->failed) == 0) {
return;
}
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
/* Print section heading */
out->begin_list(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)) {
out->message(out, "failed-action", xml_op);
}
/* End section */
out->end_list(out);
}
/*!
* \internal
* \brief Print a section for failed stonith actions
*
* \note This function should not be called for XML output.
*
* \param[in] out The output functions structure.
* \param[in] history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_failed_stonith_actions(pcmk__output_t *out, 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;
}
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
/* Print section heading */
out->begin_list(out, NULL, NULL, "Failed Fencing Actions");
/* Print each failed stonith action */
for (hp = history; hp; hp = hp->next) {
if (hp->state == st_failed) {
out->message(out, "stonith-event", hp, is_set(mon_ops, mon_op_fence_full_history),
stonith__later_succeeded(hp, history));
out->increment_list(out);
}
}
/* End section */
out->end_list(out);
}
/*!
* \internal
* \brief Print pending stonith actions
*
* \note This function should not be called for XML output.
*
* \param[in] out The output functions structure.
* \param[in] history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_stonith_pending(pcmk__output_t *out, stonith_history_t *history, unsigned int mon_ops)
{
gboolean printed_header = FALSE;
/* 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 */
if (printed_header == FALSE) {
printed_header = TRUE;
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
out->begin_list(out, NULL, NULL, "Pending Fencing Actions");
}
for (hp = history; hp; hp = hp->next) {
if ((hp->state == st_failed) || (hp->state == st_done)) {
break;
}
out->message(out, "stonith-event", hp, is_set(mon_ops, mon_op_fence_full_history),
stonith__later_succeeded(hp, history));
out->increment_list(out);
}
/* End section */
if (printed_header == TRUE) {
out->end_list(out);
}
}
}
/*!
* \internal
* \brief Print fencing history, skipping all failed actions.
*
* \note This function should not be called for XML output.
*
* \param[in] out The output functions structure.
* \param[in] history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static void
print_stonith_history(pcmk__output_t *out, stonith_history_t *history, unsigned int mon_ops)
{
stonith_history_t *hp;
gboolean printed_header = FALSE;
if (history == NULL) {
return;
}
for (hp = history; hp; hp = hp->next) {
if (hp->state != st_failed) {
/* Print the header the first time we have an event to print out to
* prevent printing headers with empty sections underneath.
*/
if (printed_header == FALSE) {
printed_header = TRUE;
/* Add a blank line between this section and the one before it. */
out->info(out, "%s", "");
out->begin_list(out, NULL, NULL, "Fencing History");
}
out->message(out, "stonith-event", hp, is_set(mon_ops, mon_op_fence_full_history),
stonith__later_succeeded(hp, history));
out->increment_list(out);
}
}
if (printed_header == TRUE) {
out->end_list(out);
}
}
/*!
* \internal
* \brief Print fencing history, including failed actions.
*
* \note This function should be called for XML output. It may also be
* interesting for other output formats.
*
* \param[in] out The output functions structure.
* \param[in] history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
*/
static gboolean
print_stonith_history_full(pcmk__output_t *out, stonith_history_t *history, unsigned int mon_ops)
{
stonith_history_t *hp;
if (history == NULL) {
return FALSE;
}
/* Print section heading */
out->begin_list(out, NULL, NULL, "Fencing History");
for (hp = history; hp; hp = hp->next) {
out->message(out, "stonith-event", hp, is_set(mon_ops, mon_op_fence_full_history),
stonith__later_succeeded(hp, history));
out->increment_list(out);
}
/* End section */
out->end_list(out);
return TRUE;
}
/*!
* \internal
* \brief Top-level printing function for text/curses output.
*
* \param[in] out The output functions structure.
- * \param[in] output_format Is this text or curses output?
* \param[in] data_set Cluster state to display.
* \param[in] stonith_history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] show Bitmask of mon_show_*.
* \param[in] prefix ID prefix to filter results by.
*/
void
-print_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix)
+print_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix)
{
GListPtr gIter = NULL;
unsigned int print_opts = get_resource_display_options(mon_ops);
- 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;
- printed = print_cluster_summary(out, data_set, mon_ops, show, output_format);
+ gboolean show_stack = is_set(show, mon_show_stack);
+ gboolean show_dc = is_set(show, mon_show_dc);
+ gboolean show_times = is_set(show, mon_show_times);
+ gboolean show_counts = is_set(show, mon_show_counts);
+ gboolean show_options = is_set(show, mon_show_options);
+
+ out->message(out, "cluster-summary", data_set,
+ is_set(mon_ops, mon_op_print_clone_detail),
+ show_stack, show_dc, show_times, show_counts, show_options);
/* Gather node information (and print if in bad state or grouping by node) */
if (is_set(show, mon_show_nodes)) {
- if (printed) {
+ /* If any of these conditions are met, the cluster-summary message will
+ * have printed out something. In that case, we need to leave a blank
+ * line between the summary and the nodes.
+ */
+ if (show_stack || data_set->dc_node == NULL || show_dc || show_times ||
+ show_counts || show_options) {
out->info(out, "%s", "");
- printed = FALSE;
}
out->begin_list(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 = pe__node_display_name(node, is_set(mon_ops, mon_op_print_clone_detail));
/* 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 = pcmk__add_word(online_guest_nodes,
node_name);
} else if (pe__is_remote_node(node)) {
online_remote_nodes = pcmk__add_word(online_remote_nodes,
node_name);
} else {
online_nodes = pcmk__add_word(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 = pcmk__add_word(offline_remote_nodes,
node_name);
} else if (pe__is_guest_node(node)) {
/* ignore offline guest nodes */
} else {
offline_nodes = pcmk__add_word(offline_nodes, node_name);
}
free(node_name);
continue;
}
}
/* If we get here, node is in bad state, or we're grouping by node */
out->message(out, "node", node, get_resource_display_options(mon_ops), TRUE,
node_mode, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
free(node_name);
}
/* If we're not grouping by node, summarize nodes by status */
if (online_nodes) {
out->list_item(out, "Online", "[%s ]", online_nodes);
free(online_nodes);
}
if (offline_nodes) {
out->list_item(out, "OFFLINE", "[%s ]", offline_nodes);
free(offline_nodes);
}
if (online_remote_nodes) {
out->list_item(out, "RemoteOnline", "[%s ]", online_remote_nodes);
free(online_remote_nodes);
}
if (offline_remote_nodes) {
out->list_item(out, "RemoteOFFLINE", "[%s ]", offline_remote_nodes);
free(offline_remote_nodes);
}
if (online_guest_nodes) {
out->list_item(out, "GuestOnline", "[%s ]", online_guest_nodes);
free(online_guest_nodes);
}
out->end_list(out);
}
/* Print resources section, if needed */
if (is_set(show, mon_show_resources)) {
print_resources(out, data_set, print_opts, mon_ops,
is_set(mon_ops, mon_op_print_brief), TRUE);
}
/* print Node Attributes section if requested */
if (is_set(show, mon_show_attributes)) {
print_node_attributes(out, data_set, mon_ops);
}
/* If requested, print resource operations (which includes failcounts)
* or just failcounts
*/
if (is_set(show, mon_show_operations) || is_set(show, mon_show_failcounts)) {
print_node_summary(out, data_set, is_set(show, mon_show_operations), mon_ops);
}
/* If there were any failed actions, print them */
if (is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) {
print_failed_actions(out, data_set);
}
/* Print failed stonith actions */
if (is_set(show, mon_show_fence_failed) && is_set(mon_ops, mon_op_fence_history)) {
print_failed_stonith_actions(out, stonith_history, mon_ops);
}
/* Print tickets if requested */
if (is_set(show, mon_show_tickets)) {
print_cluster_tickets(out, data_set);
}
/* Print negative location constraints if requested */
if (is_set(show, mon_show_bans)) {
print_neg_locations(out, data_set, mon_ops, prefix);
}
/* Print stonith history */
if (is_set(mon_ops, mon_op_fence_history)) {
if (is_set(show, mon_show_fence_worked)) {
print_stonith_history(out, stonith_history, mon_ops);
} else if (is_set(show, mon_show_fence_pending)) {
print_stonith_pending(out, stonith_history, mon_ops);
}
}
}
/*!
* \internal
* \brief Top-level printing function for XML output.
*
* \param[in] out The output functions structure.
* \param[in] data_set Cluster state to display.
* \param[in] stonith_history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] show Bitmask of mon_show_*.
* \param[in] prefix ID prefix to filter results by.
*/
void
-print_xml_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix)
+print_xml_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix)
{
GListPtr gIter = NULL;
unsigned int print_opts = get_resource_display_options(mon_ops);
- print_cluster_summary(out, data_set, mon_ops, show, output_format);
+ out->message(out, "cluster-summary", data_set,
+ is_set(mon_ops, mon_op_print_clone_detail),
+ is_set(show, mon_show_stack),
+ is_set(show, mon_show_dc),
+ is_set(show, mon_show_times),
+ is_set(show, mon_show_counts),
+ is_set(show, mon_show_options));
/*** NODES ***/
if (is_set(show, mon_show_nodes)) {
out->begin_list(out, NULL, NULL, "nodes");
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
out->message(out, "node", node, get_resource_display_options(mon_ops), TRUE,
NULL, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
}
out->end_list(out);
}
/* Print resources section, if needed */
if (is_set(show, mon_show_resources)) {
print_resources(out, data_set, print_opts, mon_ops, FALSE, FALSE);
}
/* print Node Attributes section if requested */
if (is_set(show, mon_show_attributes)) {
print_node_attributes(out, data_set, mon_ops);
}
/* If requested, print resource operations (which includes failcounts)
* or just failcounts
*/
if (is_set(show, mon_show_operations) || is_set(show, mon_show_failcounts)) {
print_node_summary(out, data_set, is_set(show, mon_show_operations), mon_ops);
}
/* If there were any failed actions, print them */
if (is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) {
print_failed_actions(out, data_set);
}
/* Print stonith history */
if (is_set(show, mon_show_fencing_all) && is_set(mon_ops, mon_op_fence_history)) {
print_stonith_history_full(out, stonith_history, mon_ops);
}
/* Print tickets if requested */
if (is_set(show, mon_show_tickets)) {
print_cluster_tickets(out, data_set);
}
/* Print negative location constraints if requested */
if (is_set(show, mon_show_bans)) {
print_neg_locations(out, data_set, mon_ops, prefix);
}
}
/*!
* \internal
* \brief Top-level printing function for HTML output.
*
* \param[in] out The output functions structure.
- * \param[in] output_format Is this HTML or CGI output?
* \param[in] data_set Cluster state to display.
* \param[in] stonith_history List of stonith actions.
* \param[in] mon_ops Bitmask of mon_op_*.
* \param[in] show Bitmask of mon_show_*.
* \param[in] prefix ID prefix to filter results by.
*/
int
-print_html_status(pcmk__output_t *out, mon_output_format_t output_format,
- pe_working_set_t *data_set, stonith_history_t *stonith_history,
- unsigned int mon_ops, unsigned int show, char *prefix)
+print_html_status(pcmk__output_t *out, pe_working_set_t *data_set,
+ stonith_history_t *stonith_history, unsigned int mon_ops,
+ unsigned int show, char *prefix)
{
GListPtr gIter = NULL;
unsigned int print_opts = get_resource_display_options(mon_ops);
- print_cluster_summary(out, data_set, mon_ops, show, output_format);
+ out->message(out, "cluster-summary", data_set,
+ is_set(mon_ops, mon_op_print_clone_detail),
+ is_set(show, mon_show_stack),
+ is_set(show, mon_show_dc),
+ is_set(show, mon_show_times),
+ is_set(show, mon_show_counts),
+ is_set(show, mon_show_options));
/*** NODE LIST ***/
if (is_set(show, mon_show_nodes)) {
out->begin_list(out, NULL, NULL, "Node List");
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
out->message(out, "node", node, get_resource_display_options(mon_ops), TRUE,
NULL, is_set(mon_ops, mon_op_print_clone_detail),
is_set(mon_ops, mon_op_print_brief), is_set(mon_ops, mon_op_group_by_node));
}
out->end_list(out);
}
/* Print resources section, if needed */
if (is_set(show, mon_show_resources)) {
print_resources(out, data_set, print_opts, mon_ops,
is_set(mon_ops, mon_op_print_brief), TRUE);
}
/* print Node Attributes section if requested */
if (is_set(show, mon_show_attributes)) {
print_node_attributes(out, data_set, mon_ops);
}
/* If requested, print resource operations (which includes failcounts)
* or just failcounts
*/
if (is_set(show, mon_show_operations) || is_set(show, mon_show_failcounts)) {
print_node_summary(out, data_set, is_set(show, mon_show_operations), mon_ops);
}
/* If there were any failed actions, print them */
if (is_set(show, mon_show_failures) && xml_has_children(data_set->failed)) {
print_failed_actions(out, data_set);
}
/* Print failed stonith actions */
if (is_set(show, mon_show_fence_failed) && is_set(mon_ops, mon_op_fence_history)) {
print_failed_stonith_actions(out, stonith_history, mon_ops);
}
/* Print stonith history */
if (is_set(mon_ops, mon_op_fence_history)) {
if (is_set(show, mon_show_fence_worked)) {
print_stonith_history(out, stonith_history, mon_ops);
} else if (is_set(show, mon_show_fence_pending)) {
print_stonith_pending(out, stonith_history, mon_ops);
}
}
/* Print tickets if requested */
if (is_set(show, mon_show_tickets)) {
print_cluster_tickets(out, data_set);
}
/* Print negative location constraints if requested */
if (is_set(show, mon_show_bans)) {
print_neg_locations(out, data_set, mon_ops, prefix);
}
return 0;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jul 8, 5:54 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002400
Default Alt Text
(212 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment