diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
commit | e5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch) | |
tree | a6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/pengine/pe_output.c | |
parent | Initial commit. (diff) | |
download | pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip |
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/pengine/pe_output.c')
-rw-r--r-- | lib/pengine/pe_output.c | 3108 |
1 files changed, 3108 insertions, 0 deletions
diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c new file mode 100644 index 0000000..68cc867 --- /dev/null +++ b/lib/pengine/pe_output.c @@ -0,0 +1,3108 @@ +/* + * Copyright 2019-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> +#include <stdint.h> +#include <crm/common/xml_internal.h> +#include <crm/common/output.h> +#include <crm/cib/util.h> +#include <crm/msg_xml.h> +#include <crm/pengine/internal.h> + +const char * +pe__resource_description(const pe_resource_t *rsc, uint32_t show_opts) +{ + const char * desc = NULL; + // User-supplied description + if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description) + || pcmk__list_of_multiple(rsc->running_on)) { + desc = crm_element_value(rsc->xml, XML_ATTR_DESC); + } + return desc; +} + +/* Never display node attributes whose name starts with one of these prefixes */ +#define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX, \ + "shutdown", "terminate", "standby", "#", NULL } + +static int +compare_attribute(gconstpointer a, gconstpointer b) +{ + int rc; + + rc = strcmp((const char *)a, (const char *)b); + + return rc; +} + +/*! + * \internal + * \brief Determine whether extended information about an attribute should be added. + * + * \param[in] node Node that ran this resource + * \param[in,out] rsc_list List of resources for this node + * \param[in,out] data_set Cluster working set + * \param[in] attrname Attribute to find + * \param[out] expected_score 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 bool +add_extra_info(const pe_node_t *node, GList *rsc_list, pe_working_set_t *data_set, + const char *attrname, int *expected_score) +{ + GList *gIter = NULL; + + for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) { + pe_resource_t *rsc = (pe_resource_t *) gIter->data; + const char *type = g_hash_table_lookup(rsc->meta, "type"); + const char *name = NULL; + GHashTable *params = NULL; + + if (rsc->children != NULL) { + if (add_extra_info(node, rsc->children, data_set, attrname, + expected_score)) { + return true; + } + } + + if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) { + continue; + } + + params = pe_rsc_params(rsc, node, data_set); + name = g_hash_table_lookup(params, "name"); + + if (name == NULL) { + name = "pingd"; + } + + /* To identify the resource with the attribute name. */ + if (pcmk__str_eq(name, attrname, pcmk__str_casei)) { + int host_list_num = 0; + const char *hosts = g_hash_table_lookup(params, "host_list"); + const char *multiplier = g_hash_table_lookup(params, "multiplier"); + int multiplier_i; + + if (hosts) { + char **host_list = g_strsplit(hosts, " ", 0); + host_list_num = g_strv_length(host_list); + g_strfreev(host_list); + } + + if ((multiplier == NULL) + || (pcmk__scan_min_int(multiplier, &multiplier_i, + INT_MIN) != pcmk_rc_ok)) { + /* The ocf:pacemaker:ping resource agent defaults multiplier to + * 1. The agent currently does not handle invalid text, but it + * should, and this would be a reasonable choice ... + */ + multiplier_i = 1; + } + *expected_score = host_list_num * multiplier_i; + + return true; + } + } + return false; +} + +static GList * +filter_attr_list(GList *attr_list, char *name) +{ + int i; + const char *filt_str[] = FILTER_STR; + + CRM_CHECK(name != NULL, return attr_list); + + /* filtering automatic attributes */ + for (i = 0; filt_str[i] != NULL; i++) { + if (g_str_has_prefix(name, filt_str[i])) { + return attr_list; + } + } + + return g_list_insert_sorted(attr_list, name, compare_attribute); +} + +static GList * +get_operation_list(xmlNode *rsc_entry) { + GList *op_list = NULL; + xmlNode *rsc_op = NULL; + + for (rsc_op = pcmk__xe_first_child(rsc_entry); rsc_op != NULL; + rsc_op = pcmk__xe_next(rsc_op)) { + const char *task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK); + const char *interval_ms_s = crm_element_value(rsc_op, + XML_LRM_ATTR_INTERVAL_MS); + const char *op_rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC); + int op_rc_i; + + pcmk__scan_min_int(op_rc, &op_rc_i, 0); + + /* Display 0-interval monitors as "probe" */ + if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei) + && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { + task = "probe"; + } + + /* Ignore notifies and some probes */ + if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_casei) || (pcmk__str_eq(task, "probe", pcmk__str_casei) && (op_rc_i == 7))) { + continue; + } + + if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, pcmk__str_none)) { + op_list = g_list_append(op_list, rsc_op); + } + } + + op_list = g_list_sort(op_list, sort_op_by_callid); + return op_list; +} + +static void +add_dump_node(gpointer key, gpointer value, gpointer user_data) +{ + xmlNodePtr node = user_data; + pcmk_create_xml_text_node(node, (const char *) key, (const char *) value); +} + +static void +append_dump_text(gpointer key, gpointer value, gpointer user_data) +{ + char **dump_text = user_data; + char *new_text = crm_strdup_printf("%s %s=%s", + *dump_text, (char *)key, (char *)value); + + free(*dump_text); + *dump_text = new_text; +} + +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, bool 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 && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { + 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 *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 *epoch_str = pcmk__epoch2str(&epoch, 0); + + last_change_str = crm_strdup_printf(" %s=\"%s\"", + XML_RSC_OP_LAST_CHANGE, + pcmk__s(epoch_str, "")); + free(epoch_str); + } + + 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 rc=%d (%s)", call, task, + interval_str ? interval_str : "", + last_change_str ? last_change_str : "", + exec_str ? exec_str : "", + queue_str ? queue_str : "", + rc, services_ocf_exitcode_str(rc)); + + if (last_change_str) { + free(last_change_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(pe_resource_t *rsc, const char *rsc_id, bool 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 = NULL; + char *lastfail_s = NULL; + + if (failcount > 0) { + failcount_s = crm_strdup_printf(" %s=%d", PCMK__FAIL_COUNT_PREFIX, + failcount); + } else { + failcount_s = strdup(""); + } + if (last_failure > 0) { + buf = pcmk__epoch2str(&last_failure, 0); + lastfail_s = crm_strdup_printf(" %s='%s'", + PCMK__LAST_FAILURE_PREFIX, buf); + free(buf); + } + + 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; +} + +static const char * +get_node_feature_set(pe_node_t *node) { + const char *feature_set = NULL; + + if (node->details->online && !pe__is_guest_or_remote_node(node)) { + feature_set = g_hash_table_lookup(node->details->attrs, + CRM_ATTR_FEATURE_SET); + /* The feature set attribute is present since 3.15.1. If it is missing + * then the node must be running an earlier version. */ + if (feature_set == NULL) { + feature_set = "<3.15.1"; + } + } + return feature_set; +} + +static bool +is_mixed_version(pe_working_set_t *data_set) { + const char *feature_set = NULL; + for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { + pe_node_t *node = gIter->data; + const char *node_feature_set = get_node_feature_set(node); + if (node_feature_set != NULL) { + if (feature_set == NULL) { + feature_set = node_feature_set; + } else if (strcmp(feature_set, node_feature_set) != 0) { + return true; + } + } + } + return false; +} + +static char * +formatted_xml_buf(pe_resource_t *rsc, bool raw) +{ + if (raw) { + return dump_xml_formatted(rsc->orig_xml ? rsc->orig_xml : rsc->xml); + } else { + return dump_xml_formatted(rsc->xml); + } +} + +PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", + "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t") +static int +cluster_summary(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + enum pcmk_pacemakerd_state pcmkd_state = + (enum pcmk_pacemakerd_state) va_arg(args, int); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + + int rc = pcmk_rc_no_output; + const char *stack_s = get_cluster_stack(data_set); + + if (pcmk_is_set(section_opts, pcmk_section_stack)) { + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-stack", stack_s, pcmkd_state); + } + + if (pcmk_is_set(section_opts, pcmk_section_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, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL; + bool mixed_version = is_mixed_version(data_set); + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-dc", data_set->dc_node, quorum, + dc_version_s, dc_name, mixed_version); + free(dc_name); + } + + if (pcmk_is_set(section_opts, pcmk_section_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); + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-times", + data_set->localhost, last_written, user, client, origin); + } + + if (pcmk_is_set(section_opts, pcmk_section_counts)) { + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-counts", g_list_length(data_set->nodes), + data_set->ninstances, data_set->disabled_resources, + data_set->blocked_resources); + } + + if (pcmk_is_set(section_opts, pcmk_section_options)) { + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-options", data_set); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + + if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) { + if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) { + rc = pcmk_rc_ok; + } + } + + return rc; +} + +PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *", + "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t") +static int +cluster_summary_html(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + enum pcmk_pacemakerd_state pcmkd_state = + (enum pcmk_pacemakerd_state) va_arg(args, int); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + + int rc = pcmk_rc_no_output; + const char *stack_s = get_cluster_stack(data_set); + + if (pcmk_is_set(section_opts, pcmk_section_stack)) { + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-stack", stack_s, pcmkd_state); + } + + /* Always print DC if none, even if not requested */ + if (data_set->dc_node == NULL || pcmk_is_set(section_opts, pcmk_section_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, pcmk_is_set(show_opts, pcmk_show_node_id)) : NULL; + bool mixed_version = is_mixed_version(data_set); + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-dc", data_set->dc_node, quorum, + dc_version_s, dc_name, mixed_version); + free(dc_name); + } + + if (pcmk_is_set(section_opts, pcmk_section_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); + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-times", + data_set->localhost, last_written, user, client, origin); + } + + if (pcmk_is_set(section_opts, pcmk_section_counts)) { + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); + out->message(out, "cluster-counts", g_list_length(data_set->nodes), + data_set->ninstances, data_set->disabled_resources, + data_set->blocked_resources); + } + + if (pcmk_is_set(section_opts, pcmk_section_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. + */ + PCMK__OUTPUT_LIST_FOOTER(out, rc); + + out->begin_list(out, NULL, NULL, "Config Options"); + out->message(out, "cluster-options", data_set); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + + if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) { + if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) { + rc = pcmk_rc_ok; + } + } + + return rc; +} + +char * +pe__node_display_name(pe_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 and detail is requested */ + if (print_detail && pe__is_guest_node(node)) { + const pe_resource_t *container = node->details->remote_rsc->container; + const pe_node_t *host_node = pe__current_node(container); + + 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 && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) { + 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) { + crm_xml_add(xml_node, param_name, param_value); + } + }; + va_end(args); + + if (is_list) { + pcmk__output_xml_push_parent(out, xml_node); + } + return pcmk_rc_ok; +} + +static const char * +role_desc(enum rsc_role_e role) +{ + if (role == RSC_ROLE_PROMOTED) { +#ifdef PCMK__COMPAT_2_0 + return "as " RSC_ROLE_PROMOTED_LEGACY_S " "; +#else + return "in " RSC_ROLE_PROMOTED_S " role "; +#endif + } + return ""; +} + +PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t") +static int +ban_html(pcmk__output_t *out, va_list args) { + pe_node_t *pe_node = va_arg(args, pe_node_t *); + pe__location_t *location = va_arg(args, pe__location_t *); + uint32_t show_opts = va_arg(args, uint32_t); + + char *node_name = pe__node_display_name(pe_node, + pcmk_is_set(show_opts, pcmk_show_node_id)); + char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s", + location->id, location->rsc_lh->id, + role_desc(location->role_filter), node_name); + + pcmk__output_create_html_node(out, "li", NULL, NULL, buf); + + free(node_name); + free(buf); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t") +static int +ban_text(pcmk__output_t *out, va_list args) { + pe_node_t *pe_node = va_arg(args, pe_node_t *); + pe__location_t *location = va_arg(args, pe__location_t *); + uint32_t show_opts = va_arg(args, uint32_t); + + char *node_name = pe__node_display_name(pe_node, + pcmk_is_set(show_opts, pcmk_show_node_id)); + out->list_item(out, NULL, "%s\tprevents %s from running %son %s", + location->id, location->rsc_lh->id, + role_desc(location->role_filter), node_name); + + free(node_name); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t") +static int +ban_xml(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 *); + uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); + + const char *promoted_only = pcmk__btoa(location->role_filter == RSC_ROLE_PROMOTED); + char *weight_s = pcmk__itoa(pe_node->weight); + + pcmk__output_create_xml_node(out, "ban", + "id", location->id, + "resource", location->rsc_lh->id, + "node", pe_node->details->uname, + "weight", weight_s, + "promoted-only", promoted_only, + /* This is a deprecated alias for + * promoted_only. Removing it will break + * backward compatibility of the API schema, + * which will require an API schema major + * version bump. + */ + "master_only", promoted_only, + NULL); + + free(weight_s); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ban-list", "pe_working_set_t *", "const char *", "GList *", + "uint32_t", "bool") +static int +ban_list(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + const char *prefix = va_arg(args, const char *); + GList *only_rsc = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + GList *gIter, *gIter2; + int rc = pcmk_rc_no_output; + + /* Print each ban */ + for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) { + pe__location_t *location = gIter->data; + const pe_resource_t *rsc = location->rsc_lh; + + if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) { + continue; + } + + if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, + pcmk__str_star_matches) + && !pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)), + only_rsc, pcmk__str_star_matches)) { + 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) { + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints"); + out->message(out, "ban", node, location, show_opts); + } + } + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") +static int +cluster_counts_html(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); + + xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL); + + 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") +static int +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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int") +static int +cluster_counts_xml(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); + + xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured", NULL); + xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured", NULL); + + char *s = pcmk__itoa(nnodes); + crm_xml_add(nodes_node, "number", s); + free(s); + + s = pcmk__itoa(nresources); + crm_xml_add(resources_node, "number", s); + free(s); + + s = pcmk__itoa(ndisabled); + crm_xml_add(resources_node, "disabled", s); + free(s); + + s = pcmk__itoa(nblocked); + crm_xml_add(resources_node, "blocked", s); + free(s); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", + "char *", "int") +static int +cluster_dc_html(pcmk__output_t *out, va_list args) { + pe_node_t *dc = va_arg(args, pe_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 *); + bool mixed_version = va_arg(args, int); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + + pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: "); + + if (dc) { + char *buf = crm_strdup_printf("%s (version %s) -", dc_name, + dc_version_s ? dc_version_s : "unknown"); + pcmk_create_html_node(node, "span", NULL, NULL, buf); + free(buf); + + if (mixed_version) { + pcmk_create_html_node(node, "span", NULL, "warning", + " MIXED-VERSION"); + } + pcmk_create_html_node(node, "span", NULL, NULL, " partition"); + if (crm_is_true(quorum)) { + pcmk_create_html_node(node, "span", NULL, NULL, " with"); + } else { + 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", + "char *", "int") +static int +cluster_dc_text(pcmk__output_t *out, va_list args) { + pe_node_t *dc = va_arg(args, pe_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 *); + bool mixed_version = va_arg(args, int); + + if (dc) { + out->list_item(out, "Current DC", + "%s (version %s) - %spartition %s quorum", + dc_name, dc_version_s ? dc_version_s : "unknown", + mixed_version ? "MIXED-VERSION " : "", + crm_is_true(quorum) ? "with" : "WITHOUT"); + } else { + out->list_item(out, "Current DC", "NONE"); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *", + "char *", "int") +static int +cluster_dc_xml(pcmk__output_t *out, va_list args) { + pe_node_t *dc = va_arg(args, pe_node_t *); + const char *quorum = va_arg(args, const char *); + const char *dc_version_s = va_arg(args, const char *); + char *dc_name G_GNUC_UNUSED = va_arg(args, char *); + bool mixed_version = va_arg(args, int); + + if (dc) { + pcmk__output_create_xml_node(out, "current_dc", + "present", "true", + "version", dc_version_s ? dc_version_s : "", + "name", dc->details->uname, + "id", dc->details->id, + "with_quorum", pcmk__btoa(crm_is_true(quorum)), + "mixed_version", pcmk__btoa(mixed_version), + NULL); + } else { + pcmk__output_create_xml_node(out, "current_dc", + "present", "false", + NULL); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int") +static int +cluster_maint_mode_text(pcmk__output_t *out, va_list args) { + unsigned long long flags = va_arg(args, unsigned long long); + + if (pcmk_is_set(flags, pe_flag_maintenance_mode)) { + pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); + pcmk__formatted_printf(out, " The cluster will not attempt to start, stop or recover services\n"); + return pcmk_rc_ok; + } else if (pcmk_is_set(flags, pe_flag_stop_everything)) { + pcmk__formatted_printf(out, "\n *** Resource management is DISABLED ***\n"); + pcmk__formatted_printf(out, " The cluster will keep all resources stopped\n"); + return pcmk_rc_ok; + } else { + return pcmk_rc_no_output; + } +} + +PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") +static int +cluster_options_html(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + out->list_item(out, NULL, "STONITH of failed nodes %s", + pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled"); + + out->list_item(out, NULL, "Cluster is %s", + pcmk_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_demote: + out->list_item(out, NULL, "No quorum policy: Demote promotable " + "resources and stop all other 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 (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + + 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 if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) { + xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + + pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: "); + pcmk_create_html_node(node, "span", NULL, "bold", "STOPPED"); + pcmk_create_html_node(node, "span", NULL, NULL, + " (the cluster will keep all resources stopped)"); + } else { + out->list_item(out, NULL, "Resource management: enabled"); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") +static int +cluster_options_log(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) { + return out->info(out, "Resource management is DISABLED. The cluster will not attempt to start, stop or recover services."); + } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) { + return out->info(out, "Resource management is DISABLED. The cluster has stopped all resources."); + } else { + return pcmk_rc_no_output; + } +} + +PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") +static int +cluster_options_text(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + out->list_item(out, NULL, "STONITH of failed nodes %s", + pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled"); + + out->list_item(out, NULL, "Cluster is %s", + pcmk_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_demote: + out->list_item(out, NULL, "No quorum policy: Demote promotable " + "resources and stop all other 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *") +static int +cluster_options_xml(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + + const char *no_quorum_policy = NULL; + char *stonith_timeout_str = pcmk__itoa(data_set->stonith_timeout); + char *priority_fencing_delay_str = pcmk__itoa(data_set->priority_fencing_delay * 1000); + + switch (data_set->no_quorum_policy) { + case no_quorum_freeze: + no_quorum_policy = "freeze"; + break; + + case no_quorum_stop: + no_quorum_policy = "stop"; + break; + + case no_quorum_demote: + no_quorum_policy = "demote"; + break; + + case no_quorum_ignore: + no_quorum_policy = "ignore"; + break; + + case no_quorum_suicide: + no_quorum_policy = "suicide"; + break; + } + + pcmk__output_create_xml_node(out, "cluster_options", + "stonith-enabled", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)), + "symmetric-cluster", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)), + "no-quorum-policy", no_quorum_policy, + "maintenance-mode", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)), + "stop-all-resources", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stop_everything)), + "stonith-timeout-ms", stonith_timeout_str, + "priority-fencing-delay-ms", priority_fencing_delay_str, + NULL); + free(stonith_timeout_str); + free(priority_fencing_delay_str); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") +static int +cluster_stack_html(pcmk__output_t *out, va_list args) { + const char *stack_s = va_arg(args, const char *); + enum pcmk_pacemakerd_state pcmkd_state = + (enum pcmk_pacemakerd_state) va_arg(args, int); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL); + + pcmk_create_html_node(node, "span", NULL, "bold", "Stack: "); + pcmk_create_html_node(node, "span", NULL, NULL, stack_s); + + if (pcmkd_state != pcmk_pacemakerd_state_invalid) { + pcmk_create_html_node(node, "span", NULL, NULL, " ("); + pcmk_create_html_node(node, "span", NULL, NULL, + pcmk__pcmkd_state_enum2friendly(pcmkd_state)); + pcmk_create_html_node(node, "span", NULL, NULL, ")"); + } + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") +static int +cluster_stack_text(pcmk__output_t *out, va_list args) { + const char *stack_s = va_arg(args, const char *); + enum pcmk_pacemakerd_state pcmkd_state = + (enum pcmk_pacemakerd_state) va_arg(args, int); + + if (pcmkd_state != pcmk_pacemakerd_state_invalid) { + out->list_item(out, "Stack", "%s (%s)", + stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state)); + } else { + out->list_item(out, "Stack", "%s", stack_s); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state") +static int +cluster_stack_xml(pcmk__output_t *out, va_list args) { + const char *stack_s = va_arg(args, const char *); + enum pcmk_pacemakerd_state pcmkd_state = + (enum pcmk_pacemakerd_state) va_arg(args, int); + + const char *state_s = NULL; + + if (pcmkd_state != pcmk_pacemakerd_state_invalid) { + state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state); + } + + pcmk__output_create_xml_node(out, "stack", + "type", stack_s, + "pacemakerd-state", state_s, + NULL); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", + "const char *", "const char *", "const char *") +static int +cluster_times_html(pcmk__output_t *out, va_list args) { + const char *our_nodename = va_arg(args, const char *); + 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 *); + + xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL); + xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL); + + char *time_s = pcmk__epoch2str(NULL, 0); + + pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: "); + pcmk_create_html_node(updated_node, "span", NULL, NULL, time_s); + + if (our_nodename != NULL) { + pcmk_create_html_node(updated_node, "span", NULL, NULL, " on "); + pcmk_create_html_node(updated_node, "span", NULL, NULL, our_nodename); + } + + free(time_s); + time_s = last_changed_string(last_written, user, client, origin); + + pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: "); + pcmk_create_html_node(changed_node, "span", NULL, NULL, time_s); + + free(time_s); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", + "const char *", "const char *", "const char *") +static int +cluster_times_xml(pcmk__output_t *out, va_list args) { + const char *our_nodename = va_arg(args, const char *); + 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 *time_s = pcmk__epoch2str(NULL, 0); + + pcmk__output_create_xml_node(out, "last_update", + "time", time_s, + "origin", our_nodename, + NULL); + + pcmk__output_create_xml_node(out, "last_change", + "time", last_written ? last_written : "", + "user", user ? user : "", + "client", client ? client : "", + "origin", origin ? origin : "", + NULL); + + free(time_s); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *", + "const char *", "const char *", "const char *") +static int +cluster_times_text(pcmk__output_t *out, va_list args) { + const char *our_nodename = va_arg(args, const char *); + 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 *time_s = pcmk__epoch2str(NULL, 0); + + out->list_item(out, "Last updated", "%s%s%s", + time_s, (our_nodename != NULL)? " on " : "", + pcmk__s(our_nodename, "")); + + free(time_s); + time_s = last_changed_string(last_written, user, client, origin); + + out->list_item(out, "Last change", " %s", time_s); + + free(time_s); + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Display a failed action in less-technical natural language + * + * \param[in,out] out Output object to use for display + * \param[in] xml_op XML containing failed action + * \param[in] op_key Operation key of failed action + * \param[in] node_name Where failed action occurred + * \param[in] rc OCF exit code of failed action + * \param[in] status Execution status of failed action + * \param[in] exit_reason Exit reason given for failed action + * \param[in] exec_time String containing execution time in milliseconds + */ +static void +failed_action_friendly(pcmk__output_t *out, const xmlNode *xml_op, + const char *op_key, const char *node_name, int rc, + int status, const char *exit_reason, + const char *exec_time) +{ + char *rsc_id = NULL; + char *task = NULL; + guint interval_ms = 0; + time_t last_change_epoch = 0; + GString *str = NULL; + + if (pcmk__str_empty(op_key) + || !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) { + rsc_id = strdup("unknown resource"); + task = strdup("unknown action"); + interval_ms = 0; + } + CRM_ASSERT((rsc_id != NULL) && (task != NULL)); + + str = g_string_sized_new(256); // Should be sufficient for most messages + + pcmk__g_strcat(str, rsc_id, " ", NULL); + + if (interval_ms != 0) { + pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ", + NULL); + } + pcmk__g_strcat(str, crm_action_str(task, interval_ms), " on ", node_name, + NULL); + + if (status == PCMK_EXEC_DONE) { + pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'", + NULL); + if (!pcmk__str_empty(exit_reason)) { + pcmk__g_strcat(str, " (", exit_reason, ")", NULL); + } + + } else { + pcmk__g_strcat(str, " could not be executed (", + pcmk_exec_status_str(status), NULL); + if (!pcmk__str_empty(exit_reason)) { + pcmk__g_strcat(str, ": ", exit_reason, NULL); + } + g_string_append_c(str, ')'); + } + + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change_epoch) == pcmk_ok) { + char *s = pcmk__epoch2str(&last_change_epoch, 0); + + pcmk__g_strcat(str, " at ", s, NULL); + free(s); + } + if (!pcmk__str_empty(exec_time)) { + int exec_time_ms = 0; + + if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok) + && (exec_time_ms > 0)) { + + pcmk__g_strcat(str, " after ", + pcmk__readable_interval(exec_time_ms), NULL); + } + } + + out->list_item(out, NULL, "%s", str->str); + g_string_free(str, TRUE); + free(rsc_id); + free(task); +} + +/*! + * \internal + * \brief Display a failed action with technical details + * + * \param[in,out] out Output object to use for display + * \param[in] xml_op XML containing failed action + * \param[in] op_key Operation key of failed action + * \param[in] node_name Where failed action occurred + * \param[in] rc OCF exit code of failed action + * \param[in] status Execution status of failed action + * \param[in] exit_reason Exit reason given for failed action + * \param[in] exec_time String containing execution time in milliseconds + */ +static void +failed_action_technical(pcmk__output_t *out, const xmlNode *xml_op, + const char *op_key, const char *node_name, int rc, + int status, const char *exit_reason, + const char *exec_time) +{ + const char *call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID); + const char *queue_time = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); + const char *exit_status = services_ocf_exitcode_str(rc); + const char *lrm_status = pcmk_exec_status_str(status); + time_t last_change_epoch = 0; + GString *str = NULL; + + if (pcmk__str_empty(op_key)) { + op_key = "unknown operation"; + } + if (pcmk__str_empty(exit_status)) { + exit_status = "unknown exit status"; + } + if (pcmk__str_empty(call_id)) { + call_id = "unknown"; + } + + str = g_string_sized_new(256); + + g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'", + op_key, node_name, exit_status, rc, call_id, + lrm_status); + + if (!pcmk__str_empty(exit_reason)) { + pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL); + } + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change_epoch) == pcmk_ok) { + char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0); + + pcmk__g_strcat(str, + ", " XML_RSC_OP_LAST_CHANGE "=" + "'", last_change_str, "'", NULL); + free(last_change_str); + } + if (!pcmk__str_empty(queue_time)) { + pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL); + } + if (!pcmk__str_empty(exec_time)) { + pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL); + } + + out->list_item(out, NULL, "%s", str->str); + g_string_free(str, TRUE); +} + +PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t") +static int +failed_action_default(pcmk__output_t *out, va_list args) +{ + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + uint32_t show_opts = va_arg(args, uint32_t); + + const char *op_key = pe__xe_history_key(xml_op); + const char *node_name = crm_element_value(xml_op, XML_ATTR_UNAME); + const char *exit_reason = crm_element_value(xml_op, + XML_LRM_ATTR_EXIT_REASON); + const char *exec_time = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); + + int rc; + int status; + + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0); + + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), + &status, 0); + + if (pcmk__str_empty(node_name)) { + node_name = "unknown node"; + } + + if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) { + failed_action_technical(out, xml_op, op_key, node_name, rc, status, + exit_reason, exec_time); + } else { + failed_action_friendly(out, xml_op, op_key, node_name, rc, status, + exit_reason, exec_time); + } + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t") +static int +failed_action_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); + + const char *op_key = pe__xe_history_key(xml_op); + const char *op_key_name = "op_key"; + int rc; + int status; + const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON); + + time_t epoch = 0; + char *rc_s = NULL; + char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none"); + xmlNodePtr node = NULL; + + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0); + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), + &status, 0); + + rc_s = pcmk__itoa(rc); + if (crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY) == NULL) { + op_key_name = "id"; + } + node = pcmk__output_create_xml_node(out, "failure", + op_key_name, op_key, + "node", crm_element_value(xml_op, XML_ATTR_UNAME), + "exitstatus", services_ocf_exitcode_str(rc), + "exitreason", pcmk__s(reason_s, ""), + "exitcode", rc_s, + "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + "status", pcmk_exec_status_str(status), + NULL); + free(rc_s); + + if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &epoch) == pcmk_ok) && (epoch > 0)) { + guint interval_ms = 0; + char *interval_ms_s = NULL; + char *rc_change = pcmk__epoch2str(&epoch, + crm_time_log_date + |crm_time_log_timeofday + |crm_time_log_with_timezone); + + crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms); + interval_ms_s = crm_strdup_printf("%u", interval_ms); + + pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE, rc_change, + "queued", crm_element_value(xml_op, XML_RSC_OP_T_QUEUE), + "exec", crm_element_value(xml_op, XML_RSC_OP_T_EXEC), + "interval", interval_ms_s, + "task", crm_element_value(xml_op, XML_LRM_ATTR_TASK), + NULL); + + free(interval_ms_s); + free(rc_change); + } + + free(reason_s); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("failed-action-list", "pe_working_set_t *", "GList *", + "GList *", "uint32_t", "bool") +static int +failed_action_list(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + xmlNode *xml_op = NULL; + int rc = pcmk_rc_no_output; + + if (xmlChildElementCount(data_set->failed) == 0) { + return rc; + } + + for (xml_op = pcmk__xml_first_child(data_set->failed); xml_op != NULL; + xml_op = pcmk__xml_next(xml_op)) { + char *rsc = NULL; + + if (!pcmk__str_in_list(crm_element_value(xml_op, XML_ATTR_UNAME), only_node, + pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + if (pcmk_xe_mask_probe_failure(xml_op)) { + continue; + } + + if (!parse_op_key(pe__xe_history_key(xml_op), &rsc, NULL, NULL)) { + continue; + } + + if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) { + free(rsc); + continue; + } + + free(rsc); + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions"); + out->message(out, "failed-action", xml_op, show_opts); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +static void +status_node(pe_node_t *node, xmlNodePtr parent, uint32_t show_opts) +{ + int health = pe__node_health(node); + + // Cluster membership + if (node->details->online) { + pcmk_create_html_node(parent, "span", NULL, "online", " online"); + } else { + pcmk_create_html_node(parent, "span", NULL, "offline", " OFFLINE"); + } + + // Standby mode + if (node->details->standby_onfail && (node->details->running_rsc != NULL)) { + pcmk_create_html_node(parent, "span", NULL, "standby", + " (in standby due to on-fail," + " with active resources)"); + } else if (node->details->standby_onfail) { + pcmk_create_html_node(parent, "span", NULL, "standby", + " (in standby due to on-fail)"); + } else if (node->details->standby && (node->details->running_rsc != NULL)) { + pcmk_create_html_node(parent, "span", NULL, "standby", + " (in standby, with active resources)"); + } else if (node->details->standby) { + pcmk_create_html_node(parent, "span", NULL, "standby", " (in standby)"); + } + + // Maintenance mode + if (node->details->maintenance) { + pcmk_create_html_node(parent, "span", NULL, "maint", + " (in maintenance mode)"); + } + + // Node health + if (health < 0) { + pcmk_create_html_node(parent, "span", NULL, "health_red", + " (health is RED)"); + } else if (health == 0) { + pcmk_create_html_node(parent, "span", NULL, "health_yellow", + " (health is YELLOW)"); + } + + // Feature set + if (pcmk_is_set(show_opts, pcmk_show_feature_set)) { + const char *feature_set = get_node_feature_set(node); + if (feature_set != NULL) { + char *buf = crm_strdup_printf(", feature set %s", feature_set); + pcmk_create_html_node(parent, "span", NULL, NULL, buf); + free(buf); + } + } +} + +PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", + "GList *", "GList *") +static int +node_html(pcmk__output_t *out, va_list args) { + pe_node_t *node = va_arg(args, pe_node_t *); + uint32_t show_opts = va_arg(args, uint32_t); + bool full = va_arg(args, int); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); + + if (full) { + xmlNodePtr item_node; + + if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) { + GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); + + out->begin_list(out, NULL, NULL, "%s:", node_name); + item_node = pcmk__output_xml_create_parent(out, "li", NULL); + pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:"); + status_node(node, item_node, show_opts); + + if (rscs != NULL) { + uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs; + out->begin_list(out, NULL, NULL, "Resources"); + pe__rscs_brief_output(out, rscs, new_show_opts); + out->end_list(out); + } + + pcmk__output_xml_pop_parent(out); + out->end_list(out); + + } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + GList *lpc2 = NULL; + int rc = pcmk_rc_no_output; + + out->begin_list(out, NULL, NULL, "%s:", node_name); + item_node = pcmk__output_xml_create_parent(out, "li", NULL); + pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:"); + status_node(node, item_node, show_opts); + + for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc2->data; + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources"); + + show_opts |= pcmk_show_rsc_only; + out->message(out, crm_map_element_name(rsc->xml), show_opts, + rsc, only_node, only_rsc); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + pcmk__output_xml_pop_parent(out); + out->end_list(out); + + } else { + char *buf = crm_strdup_printf("%s:", node_name); + + item_node = pcmk__output_create_xml_node(out, "li", NULL); + pcmk_create_html_node(item_node, "span", NULL, "bold", buf); + status_node(node, item_node, show_opts); + + free(buf); + } + } else { + out->begin_list(out, NULL, NULL, "%s:", node_name); + } + + free(node_name); + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Get a human-friendly textual description of a node's status + * + * \param[in] node Node to check + * + * \return String representation of node's status + */ +static const char * +node_text_status(const pe_node_t *node) +{ + if (node->details->unclean) { + if (node->details->online) { + return "UNCLEAN (online)"; + + } else if (node->details->pending) { + return "UNCLEAN (pending)"; + + } else { + return "UNCLEAN (offline)"; + } + + } else if (node->details->pending) { + return "pending"; + + } else if (node->details->standby_onfail && node->details->online) { + return "standby (on-fail)"; + + } else if (node->details->standby) { + if (node->details->online) { + if (node->details->running_rsc) { + return "standby (with active resources)"; + } else { + return "standby"; + } + } else { + return "OFFLINE (standby)"; + } + + } else if (node->details->maintenance) { + if (node->details->online) { + return "maintenance"; + } else { + return "OFFLINE (maintenance)"; + } + + } else if (node->details->online) { + return "online"; + } + + return "OFFLINE"; +} + +PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *") +static int +node_text(pcmk__output_t *out, va_list args) { + pe_node_t *node = va_arg(args, pe_node_t *); + uint32_t show_opts = va_arg(args, uint32_t); + bool full = va_arg(args, int); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + if (full) { + char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); + GString *str = g_string_sized_new(64); + int health = pe__node_health(node); + + // Create a summary line with node type, name, and status + if (pe__is_guest_node(node)) { + g_string_append(str, "GuestNode"); + } else if (pe__is_remote_node(node)) { + g_string_append(str, "RemoteNode"); + } else { + g_string_append(str, "Node"); + } + pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL); + + if (health < 0) { + g_string_append(str, " (health is RED)"); + } else if (health == 0) { + g_string_append(str, " (health is YELLOW)"); + } + if (pcmk_is_set(show_opts, pcmk_show_feature_set)) { + const char *feature_set = get_node_feature_set(node); + if (feature_set != NULL) { + pcmk__g_strcat(str, ", feature set ", feature_set, NULL); + } + } + + /* If we're grouping by node, print its resources */ + if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + if (pcmk_is_set(show_opts, pcmk_show_brief)) { + GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc); + + if (rscs != NULL) { + uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs; + out->begin_list(out, NULL, NULL, "%s", str->str); + out->begin_list(out, NULL, NULL, "Resources"); + + pe__rscs_brief_output(out, rscs, new_show_opts); + + out->end_list(out); + out->end_list(out); + + g_list_free(rscs); + } + + } else { + GList *gIter2 = NULL; + + out->begin_list(out, NULL, NULL, "%s", str->str); + out->begin_list(out, NULL, NULL, "Resources"); + + for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) { + pe_resource_t *rsc = (pe_resource_t *) gIter2->data; + + show_opts |= pcmk_show_rsc_only; + out->message(out, crm_map_element_name(rsc->xml), show_opts, + rsc, only_node, only_rsc); + } + + out->end_list(out); + out->end_list(out); + } + } else { + out->list_item(out, NULL, "%s", str->str); + } + + g_string_free(str, TRUE); + free(node_name); + } else { + char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); + out->begin_list(out, NULL, NULL, "Node: %s", node_name); + free(node_name); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *") +static int +node_xml(pcmk__output_t *out, va_list args) { + pe_node_t *node = va_arg(args, pe_node_t *); + uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t); + bool full = va_arg(args, int); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + if (full) { + const char *node_type = "unknown"; + char *length_s = pcmk__itoa(g_list_length(node->details->running_rsc)); + int health = pe__node_health(node); + const char *health_s = NULL; + const char *feature_set; + + 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; + } + + if (health < 0) { + health_s = "red"; + } else if (health == 0) { + health_s = "yellow"; + } else { + health_s = "green"; + } + + feature_set = get_node_feature_set(node); + + pe__name_and_nvpairs_xml(out, true, "node", 15, + "name", node->details->uname, + "id", node->details->id, + "online", pcmk__btoa(node->details->online), + "standby", pcmk__btoa(node->details->standby), + "standby_onfail", pcmk__btoa(node->details->standby_onfail), + "maintenance", pcmk__btoa(node->details->maintenance), + "pending", pcmk__btoa(node->details->pending), + "unclean", pcmk__btoa(node->details->unclean), + "health", health_s, + "feature_set", feature_set, + "shutdown", pcmk__btoa(node->details->shutdown), + "expected_up", pcmk__btoa(node->details->expected_up), + "is_dc", pcmk__btoa(node->details->is_dc), + "resources_running", length_s, + "type", node_type); + + if (pe__is_guest_node(node)) { + xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out); + crm_xml_add(xml_node, "id_as_resource", node->details->remote_rsc->container->id); + } + + if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + GList *lpc = NULL; + + for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) { + pe_resource_t *rsc = (pe_resource_t *) lpc->data; + + show_opts |= pcmk_show_rsc_only; + out->message(out, crm_map_element_name(rsc->xml), show_opts, + rsc, only_node, only_rsc); + } + } + + free(length_s); + + out->end_list(out); + } else { + pcmk__output_xml_create_parent(out, "node", + "name", node->details->uname, + NULL); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") +static int +node_attribute_text(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + bool add_extra = va_arg(args, int); + int expected_score = va_arg(args, int); + + if (add_extra) { + int v; + + if (value == NULL) { + v = 0; + } else { + pcmk__scan_min_int(value, &v, INT_MIN); + } + 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") +static int +node_attribute_html(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + bool add_extra = va_arg(args, int); + int expected_score = va_arg(args, int); + + if (add_extra) { + int v; + char *s = crm_strdup_printf("%s: %s", name, value); + xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL); + + if (value == NULL) { + v = 0; + } else { + pcmk__scan_min_int(value, &v, INT_MIN); + } + + 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr") +static int +node_and_op(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + + pe_resource_t *rsc = NULL; + gchar *node_str = NULL; + char *last_change_str = NULL; + + const char *op_rsc = crm_element_value(xml_op, "resource"); + int status; + time_t last_change = 0; + + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), + &status, PCMK_EXEC_UNKNOWN); + + rsc = pe_find_resource(data_set->resources, op_rsc); + + if (rsc) { + const pe_node_t *node = pe__current_node(rsc); + const char *target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); + uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending; + + if (node == NULL) { + node = rsc->pending_node; + } + + node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node, + show_opts, target_role, false); + } else { + node_str = crm_strdup_printf("Unknown resource %s", op_rsc); + } + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change) == pcmk_ok) { + last_change_str = crm_strdup_printf(", %s='%s', exec=%sms", + XML_RSC_OP_LAST_CHANGE, + pcmk__trim(ctime(&last_change)), + crm_element_value(xml_op, XML_RSC_OP_T_EXEC)); + } + + out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s", + node_str, pe__xe_history_key(xml_op), + crm_element_value(xml_op, XML_ATTR_UNAME), + crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + crm_element_value(xml_op, XML_LRM_ATTR_RC), + last_change_str ? last_change_str : "", + pcmk_exec_status_str(status)); + + g_free(node_str); + free(last_change_str); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr") +static int +node_and_op_xml(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + + pe_resource_t *rsc = NULL; + const char *op_rsc = crm_element_value(xml_op, "resource"); + int status; + time_t last_change = 0; + xmlNode *node = NULL; + + pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS), + &status, PCMK_EXEC_UNKNOWN); + node = pcmk__output_create_xml_node(out, "operation", + "op", pe__xe_history_key(xml_op), + "node", crm_element_value(xml_op, XML_ATTR_UNAME), + "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + "rc", crm_element_value(xml_op, XML_LRM_ATTR_RC), + "status", pcmk_exec_status_str(status), + NULL); + + rsc = pe_find_resource(data_set->resources, op_rsc); + + if (rsc) { + const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS); + const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE); + char *agent_tuple = NULL; + + agent_tuple = crm_strdup_printf("%s:%s:%s", class, + pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider) ? crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER) : "", + kind); + + pcmk__xe_set_props(node, "rsc", rsc_printable_id(rsc), + "agent", agent_tuple, + NULL); + free(agent_tuple); + } + + if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, + &last_change) == pcmk_ok) { + pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE, + pcmk__trim(ctime(&last_change)), + XML_RSC_OP_T_EXEC, crm_element_value(xml_op, XML_RSC_OP_T_EXEC), + NULL); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int") +static int +node_attribute_xml(pcmk__output_t *out, va_list args) { + const char *name = va_arg(args, const char *); + const char *value = va_arg(args, const char *); + bool add_extra = va_arg(args, int); + int expected_score = va_arg(args, int); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute", + "name", name, + "value", value, + NULL); + + if (add_extra) { + char *buf = pcmk__itoa(expected_score); + crm_xml_add(node, "expected", buf); + free(buf); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-attribute-list", "pe_working_set_t *", "uint32_t", + "bool", "GList *", "GList *") +static int +node_attribute_list(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + + int rc = pcmk_rc_no_output; + + /* Display each node's attributes */ + for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) { + pe_node_t *node = gIter->data; + + GList *attr_list = NULL; + GHashTableIter iter; + gpointer key; + + if (!node || !node->details || !node->details->online) { + continue; + } + + g_hash_table_iter_init(&iter, node->details->attrs); + while (g_hash_table_iter_next (&iter, &key, NULL)) { + attr_list = filter_attr_list(attr_list, key); + } + + if (attr_list == NULL) { + continue; + } + + if (!pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) { + g_list_free(attr_list); + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes"); + + out->message(out, "node", node, show_opts, false, only_node, only_rsc); + + for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) { + const char *name = aIter->data; + const char *value = NULL; + int expected_score = 0; + bool add_extra = false; + + value = pe_node_attribute_raw(node, name); + + add_extra = add_extra_info(node, node->details->running_rsc, + data_set, name, &expected_score); + + /* Print attribute name and value */ + out->message(out, "node-attribute", name, value, add_extra, + expected_score); + } + + g_list_free(attr_list); + out->end_list(out); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("node-capacity", "const pe_node_t *", "const char *") +static int +node_capacity(pcmk__output_t *out, va_list args) +{ + const pe_node_t *node = va_arg(args, pe_node_t *); + const char *comment = va_arg(args, const char *); + + char *dump_text = crm_strdup_printf("%s: %s capacity:", + comment, pe__node_name(node)); + + g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text); + out->list_item(out, NULL, "%s", dump_text); + free(dump_text); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-capacity", "const pe_node_t *", "const char *") +static int +node_capacity_xml(pcmk__output_t *out, va_list args) +{ + const pe_node_t *node = va_arg(args, pe_node_t *); + const char *comment = va_arg(args, const char *); + + xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "capacity", + "node", node->details->uname, + "comment", comment, + NULL); + g_hash_table_foreach(node->details->utilization, add_dump_node, xml_node); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-history-list", "pe_working_set_t *", "pe_node_t *", "xmlNodePtr", + "GList *", "GList *", "uint32_t", "uint32_t") +static int +node_history_list(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + xmlNode *node_state = va_arg(args, xmlNode *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + + xmlNode *lrm_rsc = NULL; + xmlNode *rsc_entry = NULL; + int rc = pcmk_rc_no_output; + + 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 = first_named_child(lrm_rsc, XML_LRM_TAG_RESOURCE); + rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) { + const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID); + pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id); + const pe_resource_t *parent = pe__const_top_resource(rsc, false); + + /* We can't use is_filtered here to filter group resources. For is_filtered, + * we have to decide whether to check the parent or not. If we check the + * parent, all elements of a group will always be printed because that's how + * is_filtered works for groups. If we do not check the parent, sometimes + * this will filter everything out. + * + * For other resource types, is_filtered is okay. + */ + if (parent->variant == pe_group) { + if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc, + pcmk__str_star_matches) + && !pcmk__str_in_list(rsc_printable_id(parent), only_rsc, + pcmk__str_star_matches)) { + continue; + } + } else { + if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) { + continue; + } + } + + if (!pcmk_is_set(section_opts, pcmk_section_operations)) { + time_t last_failure = 0; + int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default, + NULL); + + if (failcount <= 0) { + continue; + } + + if (rc == pcmk_rc_no_output) { + rc = pcmk_rc_ok; + out->message(out, "node", node, show_opts, false, only_node, + only_rsc); + } + + out->message(out, "resource-history", rsc, rsc_id, false, + failcount, last_failure, false); + } else { + GList *op_list = get_operation_list(rsc_entry); + pe_resource_t *rsc = pe_find_resource(data_set->resources, + crm_element_value(rsc_entry, XML_ATTR_ID)); + + if (op_list == NULL) { + continue; + } + + if (rc == pcmk_rc_no_output) { + rc = pcmk_rc_ok; + out->message(out, "node", node, show_opts, false, only_node, + only_rsc); + } + + out->message(out, "resource-operation-list", data_set, rsc, node, + op_list, show_opts); + } + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") +static int +node_list_html(pcmk__output_t *out, va_list args) { + GList *nodes = va_arg(args, GList *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer G_GNUC_UNUSED = va_arg(args, int); + + int rc = pcmk_rc_no_output; + + for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { + pe_node_t *node = (pe_node_t *) gIter->data; + + if (!pcmk__str_in_list(node->details->uname, only_node, + pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List"); + + out->message(out, "node", node, show_opts, true, only_node, only_rsc); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") +static int +node_list_text(pcmk__output_t *out, va_list args) { + GList *nodes = va_arg(args, GList *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + /* space-separated lists of node names */ + GString *online_nodes = NULL; + GString *online_remote_nodes = NULL; + GString *online_guest_nodes = NULL; + GString *offline_nodes = NULL; + GString *offline_remote_nodes = NULL; + + int rc = pcmk_rc_no_output; + + for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { + pe_node_t *node = (pe_node_t *) gIter->data; + char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id)); + + if (!pcmk__str_in_list(node->details->uname, only_node, + pcmk__str_star_matches|pcmk__str_casei)) { + free(node_name); + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List"); + + // Determine whether to display node individually or in a list + if (node->details->unclean || node->details->pending + || (node->details->standby_onfail && node->details->online) + || node->details->standby || node->details->maintenance + || pcmk_is_set(show_opts, pcmk_show_rscs_by_node) + || pcmk_is_set(show_opts, pcmk_show_feature_set) + || (pe__node_health(node) <= 0)) { + // Display node individually + + } else if (node->details->online) { + // Display online node in a list + if (pe__is_guest_node(node)) { + pcmk__add_word(&online_guest_nodes, 1024, node_name); + + } else if (pe__is_remote_node(node)) { + pcmk__add_word(&online_remote_nodes, 1024, node_name); + + } else { + pcmk__add_word(&online_nodes, 1024, node_name); + } + free(node_name); + continue; + + } else { + // Display offline node in a list + if (pe__is_remote_node(node)) { + pcmk__add_word(&offline_remote_nodes, 1024, node_name); + + } else if (pe__is_guest_node(node)) { + /* ignore offline guest nodes */ + + } else { + pcmk__add_word(&offline_nodes, 1024, 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, show_opts, true, only_node, only_rsc); + free(node_name); + } + + /* If we're not grouping by node, summarize nodes by status */ + if (online_nodes != NULL) { + out->list_item(out, "Online", "[ %s ]", + (const char *) online_nodes->str); + g_string_free(online_nodes, TRUE); + } + if (offline_nodes != NULL) { + out->list_item(out, "OFFLINE", "[ %s ]", + (const char *) offline_nodes->str); + g_string_free(offline_nodes, TRUE); + } + if (online_remote_nodes) { + out->list_item(out, "RemoteOnline", "[ %s ]", + (const char *) online_remote_nodes->str); + g_string_free(online_remote_nodes, TRUE); + } + if (offline_remote_nodes) { + out->list_item(out, "RemoteOFFLINE", "[ %s ]", + (const char *) offline_remote_nodes->str); + g_string_free(offline_remote_nodes, TRUE); + } + if (online_guest_nodes != NULL) { + out->list_item(out, "GuestOnline", "[ %s ]", + (const char *) online_guest_nodes->str); + g_string_free(online_guest_nodes, TRUE); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool") +static int +node_list_xml(pcmk__output_t *out, va_list args) { + GList *nodes = va_arg(args, GList *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer G_GNUC_UNUSED = va_arg(args, int); + + out->begin_list(out, NULL, NULL, "nodes"); + for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) { + pe_node_t *node = (pe_node_t *) gIter->data; + + if (!pcmk__str_in_list(node->details->uname, only_node, + pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + out->message(out, "node", node, show_opts, true, only_node, only_rsc); + } + out->end_list(out); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-summary", "pe_working_set_t *", "GList *", "GList *", + "uint32_t", "uint32_t", "bool") +static int +node_summary(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + uint32_t section_opts = va_arg(args, uint32_t); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_spacer = va_arg(args, int); + + xmlNode *node_state = NULL; + xmlNode *cib_status = pcmk_find_cib_element(data_set->input, + XML_CIB_TAG_STATUS); + int rc = pcmk_rc_no_output; + + if (xmlChildElementCount(cib_status) == 0) { + return rc; + } + + for (node_state = first_named_child(cib_status, XML_CIB_TAG_STATE); + node_state != NULL; node_state = crm_next_same_xml(node_state)) { + pe_node_t *node = pe_find_node_id(data_set->nodes, ID(node_state)); + + if (!node || !node->details || !node->details->online) { + continue; + } + + if (!pcmk__str_in_list(node->details->uname, only_node, + pcmk__str_star_matches|pcmk__str_casei)) { + continue; + } + + PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, + pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary"); + + out->message(out, "node-history-list", data_set, node, node_state, + only_node, only_rsc, section_opts, show_opts); + } + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("node-weight", "const pe_resource_t *", "const char *", + "const char *", "const char *") +static int +node_weight(pcmk__output_t *out, va_list args) +{ + const pe_resource_t *rsc = va_arg(args, const pe_resource_t *); + const char *prefix = va_arg(args, const char *); + const char *uname = va_arg(args, const char *); + const char *score = va_arg(args, const char *); + + if (rsc) { + out->list_item(out, NULL, "%s: %s allocation score on %s: %s", + prefix, rsc->id, uname, score); + } else { + out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("node-weight", "const pe_resource_t *", "const char *", + "const char *", "const char *") +static int +node_weight_xml(pcmk__output_t *out, va_list args) +{ + const pe_resource_t *rsc = va_arg(args, const pe_resource_t *); + const char *prefix = va_arg(args, const char *); + const char *uname = va_arg(args, const char *); + const char *score = va_arg(args, const char *); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "node_weight", + "function", prefix, + "node", uname, + "score", score, + NULL); + + if (rsc) { + crm_xml_add(node, "id", rsc->id); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t") +static int +op_history_text(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + const char *task = va_arg(args, const char *); + const char *interval_ms_s = va_arg(args, const char *); + int rc = va_arg(args, int); + uint32_t show_opts = va_arg(args, uint32_t); + + char *buf = op_history_string(xml_op, task, interval_ms_s, rc, + pcmk_is_set(show_opts, pcmk_show_timing)); + + out->list_item(out, NULL, "%s", buf); + + free(buf); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t") +static int +op_history_xml(pcmk__output_t *out, va_list args) { + xmlNodePtr xml_op = va_arg(args, xmlNodePtr); + const char *task = va_arg(args, const char *); + const char *interval_ms_s = va_arg(args, const char *); + int rc = va_arg(args, int); + uint32_t show_opts = va_arg(args, uint32_t); + + char *rc_s = pcmk__itoa(rc); + xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history", + "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID), + "task", task, + "rc", rc_s, + "rc_text", services_ocf_exitcode_str(rc), + NULL); + free(rc_s); + + if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) { + char *s = crm_strdup_printf("%sms", interval_ms_s); + crm_xml_add(node, "interval", s); + free(s); + } + + if (pcmk_is_set(show_opts, pcmk_show_timing)) { + 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 *s = pcmk__epoch2str(&epoch, 0); + crm_xml_add(node, XML_RSC_OP_LAST_CHANGE, s); + free(s); + } + + value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC); + if (value) { + char *s = crm_strdup_printf("%sms", value); + crm_xml_add(node, XML_RSC_OP_T_EXEC, s); + free(s); + } + value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE); + if (value) { + char *s = crm_strdup_printf("%sms", value); + crm_xml_add(node, XML_RSC_OP_T_QUEUE, s); + free(s); + } + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *") +static int +promotion_score(pcmk__output_t *out, va_list args) +{ + pe_resource_t *child_rsc = va_arg(args, pe_resource_t *); + pe_node_t *chosen = va_arg(args, pe_node_t *); + const char *score = va_arg(args, const char *); + + out->list_item(out, NULL, "%s promotion score on %s: %s", + child_rsc->id, + chosen? chosen->details->uname : "none", + score); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *") +static int +promotion_score_xml(pcmk__output_t *out, va_list args) +{ + pe_resource_t *child_rsc = va_arg(args, pe_resource_t *); + pe_node_t *chosen = va_arg(args, pe_node_t *); + const char *score = va_arg(args, const char *); + + xmlNodePtr node = pcmk__output_create_xml_node(out, "promotion_score", + "id", child_rsc->id, + "score", score, + NULL); + + if (chosen) { + crm_xml_add(node, "node", chosen->details->uname); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool") +static int +resource_config(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + bool raw = va_arg(args, int); + + char *rsc_xml = formatted_xml_buf(rsc, raw); + + out->output_xml(out, "xml", rsc_xml); + + free(rsc_xml); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool") +static int +resource_config_text(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + bool raw = va_arg(args, int); + + char *rsc_xml = formatted_xml_buf(rsc, raw); + + pcmk__formatted_printf(out, "Resource XML:\n"); + out->output_xml(out, "xml", rsc_xml); + + free(rsc_xml); + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool") +static int +resource_history_text(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + const char *rsc_id = va_arg(args, const char *); + bool all = va_arg(args, int); + int failcount = va_arg(args, int); + time_t last_failure = va_arg(args, time_t); + bool as_header = va_arg(args, int); + + 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 pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool") +static int +resource_history_xml(pcmk__output_t *out, va_list args) { + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + const char *rsc_id = va_arg(args, const char *); + bool all = va_arg(args, int); + int failcount = va_arg(args, int); + time_t last_failure = va_arg(args, time_t); + bool as_header = va_arg(args, int); + + xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history", + "id", rsc_id, + NULL); + + if (rsc == NULL) { + pcmk__xe_set_bool_attr(node, "orphan", true); + } else if (all || failcount || last_failure > 0) { + char *migration_s = pcmk__itoa(rsc->migration_threshold); + + pcmk__xe_set_props(node, "orphan", "false", + "migration-threshold", migration_s, + NULL); + free(migration_s); + + if (failcount > 0) { + char *s = pcmk__itoa(failcount); + + crm_xml_add(node, PCMK__FAIL_COUNT_PREFIX, s); + free(s); + } + + if (last_failure > 0) { + char *s = pcmk__epoch2str(&last_failure, 0); + + crm_xml_add(node, PCMK__LAST_FAILURE_PREFIX, s); + free(s); + } + } + + if (!as_header) { + pcmk__output_xml_pop_parent(out); + } + + return pcmk_rc_ok; +} + +static void +print_resource_header(pcmk__output_t *out, uint32_t show_opts) +{ + if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + /* Active resources have already been printed by node */ + out->begin_list(out, NULL, NULL, "Inactive Resources"); + } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { + out->begin_list(out, NULL, NULL, "Full List of Resources"); + } else { + out->begin_list(out, NULL, NULL, "Active Resources"); + } +} + + +PCMK__OUTPUT_ARGS("resource-list", "pe_working_set_t *", "uint32_t", "bool", + "GList *", "GList *", "bool") +static int +resource_list(pcmk__output_t *out, va_list args) +{ + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + uint32_t show_opts = va_arg(args, uint32_t); + bool print_summary = va_arg(args, int); + GList *only_node = va_arg(args, GList *); + GList *only_rsc = va_arg(args, GList *); + bool print_spacer = va_arg(args, int); + + GList *rsc_iter; + int rc = pcmk_rc_no_output; + bool printed_header = false; + + /* If we already showed active resources by node, and + * we're not showing inactive resources, we have nothing to do + */ + if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) && + !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { + return rc; + } + + /* If we haven't already printed resources grouped by node, + * and brief output was requested, print resource summary */ + if (pcmk_is_set(show_opts, pcmk_show_brief) && !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + GList *rscs = pe__filter_rsc_list(data_set->resources, only_rsc); + + PCMK__OUTPUT_SPACER_IF(out, print_spacer); + print_resource_header(out, show_opts); + printed_header = true; + + rc = pe__rscs_brief_output(out, rscs, show_opts); + g_list_free(rscs); + } + + /* 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; + int x; + + /* 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 (pcmk_is_set(rsc->flags, pe_rsc_orphan) && !is_active) { + continue; + + /* Skip active resources if we already displayed them by node */ + } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + if (is_active) { + continue; + } + + /* Skip primitives already counted in a brief summary */ + } else if (pcmk_is_set(show_opts, pcmk_show_brief) && (rsc->variant == pe_native)) { + continue; + + /* Skip resources that aren't at least partially active, + * unless we're displaying inactive resources + */ + } else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { + continue; + + } else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) { + continue; + } + + if (!printed_header) { + PCMK__OUTPUT_SPACER_IF(out, print_spacer); + print_resource_header(out, show_opts); + printed_header = true; + } + + /* Print this resource */ + x = out->message(out, crm_map_element_name(rsc->xml), show_opts, rsc, + only_node, only_rsc); + if (x == pcmk_rc_ok) { + rc = pcmk_rc_ok; + } + } + + if (print_summary && rc != pcmk_rc_ok) { + if (!printed_header) { + PCMK__OUTPUT_SPACER_IF(out, print_spacer); + print_resource_header(out, show_opts); + printed_header = true; + } + + if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) { + out->list_item(out, NULL, "No inactive resources"); + } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) { + out->list_item(out, NULL, "No resources"); + } else { + out->list_item(out, NULL, "No active resources"); + } + } + + if (printed_header) { + out->end_list(out); + } + + return rc; +} + +PCMK__OUTPUT_ARGS("resource-operation-list", "pe_working_set_t *", "pe_resource_t *", + "pe_node_t *", "GList *", "uint32_t") +static int +resource_operation_list(pcmk__output_t *out, va_list args) +{ + pe_working_set_t *data_set G_GNUC_UNUSED = va_arg(args, pe_working_set_t *); + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + GList *op_list = va_arg(args, GList *); + uint32_t show_opts = va_arg(args, uint32_t); + + GList *gIter = NULL; + int rc = pcmk_rc_no_output; + + /* 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 op_rc_i; + + pcmk__scan_min_int(op_rc, &op_rc_i, 0); + + /* Display 0-interval monitors as "probe" */ + if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei) + && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) { + task = "probe"; + } + + /* If this is the first printed operation, print heading for resource */ + if (rc == pcmk_rc_no_output) { + time_t last_failure = 0; + int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default, + NULL); + + out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true, + failcount, last_failure, true); + rc = pcmk_rc_ok; + } + + /* Print the operation */ + out->message(out, "op-history", xml_op, task, interval_ms_s, + op_rc_i, show_opts); + } + + /* Free the list we created (no need to free the individual items) */ + g_list_free(op_list); + + PCMK__OUTPUT_LIST_FOOTER(out, rc); + return rc; +} + +PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *") +static int +resource_util(pcmk__output_t *out, va_list args) +{ + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + const char *fn = va_arg(args, const char *); + + char *dump_text = crm_strdup_printf("%s: %s utilization on %s:", + fn, rsc->id, pe__node_name(node)); + + g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text); + out->list_item(out, NULL, "%s", dump_text); + free(dump_text); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *") +static int +resource_util_xml(pcmk__output_t *out, va_list args) +{ + pe_resource_t *rsc = va_arg(args, pe_resource_t *); + pe_node_t *node = va_arg(args, pe_node_t *); + const char *fn = va_arg(args, const char *); + + xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "utilization", + "resource", rsc->id, + "node", node->details->uname, + "function", fn, + NULL); + g_hash_table_foreach(rsc->utilization, add_dump_node, xml_node); + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") +static int +ticket_html(pcmk__output_t *out, va_list args) { + pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); + + if (ticket->last_granted > -1) { + char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0); + + out->list_item(out, NULL, "%s:\t%s%s %s=\"%s\"", ticket->id, + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : "", + "last-granted", pcmk__s(epoch_str, "")); + free(epoch_str); + } else { + out->list_item(out, NULL, "%s:\t%s%s", ticket->id, + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : ""); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") +static int +ticket_text(pcmk__output_t *out, va_list args) { + pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); + + if (ticket->last_granted > -1) { + char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0); + + out->list_item(out, ticket->id, "%s%s %s=\"%s\"", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : "", + "last-granted", pcmk__s(epoch_str, "")); + free(epoch_str); + } else { + out->list_item(out, ticket->id, "%s%s", + ticket->granted ? "granted" : "revoked", + ticket->standby ? " [standby]" : ""); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *") +static int +ticket_xml(pcmk__output_t *out, va_list args) { + pe_ticket_t *ticket = va_arg(args, pe_ticket_t *); + + xmlNodePtr node = NULL; + + node = pcmk__output_create_xml_node(out, "ticket", + "id", ticket->id, + "status", ticket->granted ? "granted" : "revoked", + "standby", pcmk__btoa(ticket->standby), + NULL); + + if (ticket->last_granted > -1) { + char *buf = pcmk__epoch2str(&ticket->last_granted, 0); + + crm_xml_add(node, "last-granted", buf); + free(buf); + } + + return pcmk_rc_ok; +} + +PCMK__OUTPUT_ARGS("ticket-list", "pe_working_set_t *", "bool") +static int +ticket_list(pcmk__output_t *out, va_list args) { + pe_working_set_t *data_set = va_arg(args, pe_working_set_t *); + bool print_spacer = va_arg(args, int); + + GHashTableIter iter; + gpointer key, value; + + if (g_hash_table_size(data_set->tickets) == 0) { + return pcmk_rc_no_output; + } + + PCMK__OUTPUT_SPACER_IF(out, print_spacer); + + /* 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)) { + pe_ticket_t *ticket = (pe_ticket_t *) value; + out->message(out, "ticket", ticket); + } + + /* Close section */ + out->end_list(out); + return pcmk_rc_ok; +} + +static pcmk__message_entry_t fmt_functions[] = { + { "ban", "default", ban_text }, + { "ban", "html", ban_html }, + { "ban", "xml", ban_xml }, + { "ban-list", "default", ban_list }, + { "bundle", "default", pe__bundle_text }, + { "bundle", "xml", pe__bundle_xml }, + { "bundle", "html", pe__bundle_html }, + { "clone", "default", pe__clone_default }, + { "clone", "xml", pe__clone_xml }, + { "cluster-counts", "default", cluster_counts_text }, + { "cluster-counts", "html", cluster_counts_html }, + { "cluster-counts", "xml", cluster_counts_xml }, + { "cluster-dc", "default", cluster_dc_text }, + { "cluster-dc", "html", cluster_dc_html }, + { "cluster-dc", "xml", cluster_dc_xml }, + { "cluster-options", "default", cluster_options_text }, + { "cluster-options", "html", cluster_options_html }, + { "cluster-options", "log", cluster_options_log }, + { "cluster-options", "xml", cluster_options_xml }, + { "cluster-summary", "default", cluster_summary }, + { "cluster-summary", "html", cluster_summary_html }, + { "cluster-stack", "default", cluster_stack_text }, + { "cluster-stack", "html", cluster_stack_html }, + { "cluster-stack", "xml", cluster_stack_xml }, + { "cluster-times", "default", cluster_times_text }, + { "cluster-times", "html", cluster_times_html }, + { "cluster-times", "xml", cluster_times_xml }, + { "failed-action", "default", failed_action_default }, + { "failed-action", "xml", failed_action_xml }, + { "failed-action-list", "default", failed_action_list }, + { "group", "default", pe__group_default}, + { "group", "xml", pe__group_xml }, + { "maint-mode", "text", cluster_maint_mode_text }, + { "node", "default", node_text }, + { "node", "html", node_html }, + { "node", "xml", node_xml }, + { "node-and-op", "default", node_and_op }, + { "node-and-op", "xml", node_and_op_xml }, + { "node-capacity", "default", node_capacity }, + { "node-capacity", "xml", node_capacity_xml }, + { "node-history-list", "default", node_history_list }, + { "node-list", "default", node_list_text }, + { "node-list", "html", node_list_html }, + { "node-list", "xml", node_list_xml }, + { "node-weight", "default", node_weight }, + { "node-weight", "xml", node_weight_xml }, + { "node-attribute", "default", node_attribute_text }, + { "node-attribute", "html", node_attribute_html }, + { "node-attribute", "xml", node_attribute_xml }, + { "node-attribute-list", "default", node_attribute_list }, + { "node-summary", "default", node_summary }, + { "op-history", "default", op_history_text }, + { "op-history", "xml", op_history_xml }, + { "primitive", "default", pe__resource_text }, + { "primitive", "xml", pe__resource_xml }, + { "primitive", "html", pe__resource_html }, + { "promotion-score", "default", promotion_score }, + { "promotion-score", "xml", promotion_score_xml }, + { "resource-config", "default", resource_config }, + { "resource-config", "text", resource_config_text }, + { "resource-history", "default", resource_history_text }, + { "resource-history", "xml", resource_history_xml }, + { "resource-list", "default", resource_list }, + { "resource-operation-list", "default", resource_operation_list }, + { "resource-util", "default", resource_util }, + { "resource-util", "xml", resource_util_xml }, + { "ticket", "default", ticket_text }, + { "ticket", "html", ticket_html }, + { "ticket", "xml", ticket_xml }, + { "ticket-list", "default", ticket_list }, + + { NULL, NULL, NULL } +}; + +void +pe__register_messages(pcmk__output_t *out) { + pcmk__register_messages(out, fmt_functions); +} |