root/lib/pengine/pe_output.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pe__resource_description
  2. compare_attribute
  3. add_extra_info
  4. filter_attr_list
  5. get_operation_list
  6. add_dump_node
  7. append_dump_text
  8. get_cluster_stack
  9. last_changed_string
  10. op_history_string
  11. resource_history_string
  12. get_node_feature_set
  13. is_mixed_version
  14. formatted_xml_buf
  15. PCMK__OUTPUT_ARGS
  16. PCMK__OUTPUT_ARGS
  17. pe__node_display_name
  18. pe__name_and_nvpairs_xml
  19. role_desc
  20. PCMK__OUTPUT_ARGS
  21. PCMK__OUTPUT_ARGS
  22. PCMK__OUTPUT_ARGS
  23. PCMK__OUTPUT_ARGS
  24. PCMK__OUTPUT_ARGS
  25. PCMK__OUTPUT_ARGS
  26. PCMK__OUTPUT_ARGS
  27. PCMK__OUTPUT_ARGS
  28. PCMK__OUTPUT_ARGS
  29. PCMK__OUTPUT_ARGS
  30. PCMK__OUTPUT_ARGS
  31. PCMK__OUTPUT_ARGS
  32. PCMK__OUTPUT_ARGS
  33. PCMK__OUTPUT_ARGS
  34. PCMK__OUTPUT_ARGS
  35. PCMK__OUTPUT_ARGS
  36. PCMK__OUTPUT_ARGS
  37. PCMK__OUTPUT_ARGS
  38. PCMK__OUTPUT_ARGS
  39. PCMK__OUTPUT_ARGS
  40. PCMK__OUTPUT_ARGS
  41. failed_action_friendly
  42. failed_action_technical
  43. PCMK__OUTPUT_ARGS
  44. PCMK__OUTPUT_ARGS
  45. PCMK__OUTPUT_ARGS
  46. status_node
  47. PCMK__OUTPUT_ARGS
  48. node_text_status
  49. PCMK__OUTPUT_ARGS
  50. PCMK__OUTPUT_ARGS
  51. PCMK__OUTPUT_ARGS
  52. PCMK__OUTPUT_ARGS
  53. PCMK__OUTPUT_ARGS
  54. PCMK__OUTPUT_ARGS
  55. PCMK__OUTPUT_ARGS
  56. PCMK__OUTPUT_ARGS
  57. PCMK__OUTPUT_ARGS
  58. PCMK__OUTPUT_ARGS
  59. PCMK__OUTPUT_ARGS
  60. PCMK__OUTPUT_ARGS
  61. PCMK__OUTPUT_ARGS
  62. PCMK__OUTPUT_ARGS
  63. PCMK__OUTPUT_ARGS
  64. PCMK__OUTPUT_ARGS
  65. PCMK__OUTPUT_ARGS
  66. PCMK__OUTPUT_ARGS
  67. PCMK__OUTPUT_ARGS
  68. PCMK__OUTPUT_ARGS
  69. PCMK__OUTPUT_ARGS
  70. PCMK__OUTPUT_ARGS
  71. PCMK__OUTPUT_ARGS
  72. PCMK__OUTPUT_ARGS
  73. PCMK__OUTPUT_ARGS
  74. print_resource_header
  75. PCMK__OUTPUT_ARGS
  76. PCMK__OUTPUT_ARGS
  77. PCMK__OUTPUT_ARGS
  78. PCMK__OUTPUT_ARGS
  79. PCMK__OUTPUT_ARGS
  80. PCMK__OUTPUT_ARGS
  81. PCMK__OUTPUT_ARGS
  82. PCMK__OUTPUT_ARGS
  83. pe__register_messages

   1 /*
   2  * Copyright 2019-2023 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <stdint.h>
  12 #include <crm/common/xml_internal.h>
  13 #include <crm/common/output.h>
  14 #include <crm/cib/util.h>
  15 #include <crm/msg_xml.h>
  16 #include <crm/pengine/internal.h>
  17 
  18 const char *
  19 pe__resource_description(const pe_resource_t *rsc, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
  20 {
  21     const char * desc = NULL;
  22     // User-supplied description
  23     if (pcmk_any_flags_set(show_opts, pcmk_show_rsc_only|pcmk_show_description)
  24         || pcmk__list_of_multiple(rsc->running_on)) {
  25         desc = crm_element_value(rsc->xml, XML_ATTR_DESC);
  26     }
  27     return desc;
  28 }
  29 
  30 /* Never display node attributes whose name starts with one of these prefixes */
  31 #define FILTER_STR { PCMK__FAIL_COUNT_PREFIX, PCMK__LAST_FAILURE_PREFIX,   \
  32                      "shutdown", "terminate", "standby", "#", NULL }
  33 
  34 static int
  35 compare_attribute(gconstpointer a, gconstpointer b)
     /* [previous][next][first][last][top][bottom][index][help] */
  36 {
  37     int rc;
  38 
  39     rc = strcmp((const char *)a, (const char *)b);
  40 
  41     return rc;
  42 }
  43 
  44 /*!
  45  * \internal
  46  * \brief Determine whether extended information about an attribute should be added.
  47  *
  48  * \param[in]     node            Node that ran this resource
  49  * \param[in,out] rsc_list        List of resources for this node
  50  * \param[in,out] data_set        Cluster working set
  51  * \param[in]     attrname        Attribute to find
  52  * \param[out]    expected_score  Expected value for this attribute
  53  *
  54  * \return true if extended information should be printed, false otherwise
  55  * \note Currently, extended information is only supported for ping/pingd
  56  *       resources, for which a message will be printed if connectivity is lost
  57  *       or degraded.
  58  */
  59 static bool
  60 add_extra_info(const pe_node_t *node, GList *rsc_list, pe_working_set_t *data_set,
     /* [previous][next][first][last][top][bottom][index][help] */
  61                const char *attrname, int *expected_score)
  62 {
  63     GList *gIter = NULL;
  64 
  65     for (gIter = rsc_list; gIter != NULL; gIter = gIter->next) {
  66         pe_resource_t *rsc = (pe_resource_t *) gIter->data;
  67         const char *type = g_hash_table_lookup(rsc->meta, "type");
  68         const char *name = NULL;
  69         GHashTable *params = NULL;
  70 
  71         if (rsc->children != NULL) {
  72             if (add_extra_info(node, rsc->children, data_set, attrname,
  73                                expected_score)) {
  74                 return true;
  75             }
  76         }
  77 
  78         if (!pcmk__strcase_any_of(type, "ping", "pingd", NULL)) {
  79             continue;
  80         }
  81 
  82         params = pe_rsc_params(rsc, node, data_set);
  83         name = g_hash_table_lookup(params, "name");
  84 
  85         if (name == NULL) {
  86             name = "pingd";
  87         }
  88 
  89         /* To identify the resource with the attribute name. */
  90         if (pcmk__str_eq(name, attrname, pcmk__str_casei)) {
  91             int host_list_num = 0;
  92             const char *hosts = g_hash_table_lookup(params, "host_list");
  93             const char *multiplier = g_hash_table_lookup(params, "multiplier");
  94             int multiplier_i;
  95 
  96             if (hosts) {
  97                 char **host_list = g_strsplit(hosts, " ", 0);
  98                 host_list_num = g_strv_length(host_list);
  99                 g_strfreev(host_list);
 100             }
 101 
 102             if ((multiplier == NULL)
 103                 || (pcmk__scan_min_int(multiplier, &multiplier_i,
 104                                        INT_MIN) != pcmk_rc_ok)) {
 105                 /* The ocf:pacemaker:ping resource agent defaults multiplier to
 106                  * 1. The agent currently does not handle invalid text, but it
 107                  * should, and this would be a reasonable choice ...
 108                  */
 109                 multiplier_i = 1;
 110             }
 111             *expected_score = host_list_num * multiplier_i;
 112 
 113             return true;
 114         }
 115     }
 116     return false;
 117 }
 118 
 119 static GList *
 120 filter_attr_list(GList *attr_list, char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 121 {
 122     int i;
 123     const char *filt_str[] = FILTER_STR;
 124 
 125     CRM_CHECK(name != NULL, return attr_list);
 126 
 127     /* filtering automatic attributes */
 128     for (i = 0; filt_str[i] != NULL; i++) {
 129         if (g_str_has_prefix(name, filt_str[i])) {
 130             return attr_list;
 131         }
 132     }
 133 
 134     return g_list_insert_sorted(attr_list, name, compare_attribute);
 135 }
 136 
 137 static GList *
 138 get_operation_list(xmlNode *rsc_entry) {
     /* [previous][next][first][last][top][bottom][index][help] */
 139     GList *op_list = NULL;
 140     xmlNode *rsc_op = NULL;
 141 
 142     for (rsc_op = pcmk__xe_first_child(rsc_entry); rsc_op != NULL;
 143          rsc_op = pcmk__xe_next(rsc_op)) {
 144         const char *task = crm_element_value(rsc_op, XML_LRM_ATTR_TASK);
 145         const char *interval_ms_s = crm_element_value(rsc_op,
 146                                                       XML_LRM_ATTR_INTERVAL_MS);
 147         const char *op_rc = crm_element_value(rsc_op, XML_LRM_ATTR_RC);
 148         int op_rc_i;
 149 
 150         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
 151 
 152         /* Display 0-interval monitors as "probe" */
 153         if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)
 154             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
 155             task = "probe";
 156         }
 157 
 158         /* Ignore notifies and some probes */
 159         if (pcmk__str_eq(task, CRMD_ACTION_NOTIFY, pcmk__str_casei) || (pcmk__str_eq(task, "probe", pcmk__str_casei) && (op_rc_i == 7))) {
 160             continue;
 161         }
 162 
 163         if (pcmk__str_eq((const char *)rsc_op->name, XML_LRM_TAG_RSC_OP, pcmk__str_none)) {
 164             op_list = g_list_append(op_list, rsc_op);
 165         }
 166     }
 167 
 168     op_list = g_list_sort(op_list, sort_op_by_callid);
 169     return op_list;
 170 }
 171 
 172 static void
 173 add_dump_node(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 174 {
 175     xmlNodePtr node = user_data;
 176     pcmk_create_xml_text_node(node, (const char *) key, (const char *) value);
 177 }
 178 
 179 static void
 180 append_dump_text(gpointer key, gpointer value, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 181 {
 182     char **dump_text = user_data;
 183     char *new_text = crm_strdup_printf("%s %s=%s",
 184                                        *dump_text, (char *)key, (char *)value);
 185 
 186     free(*dump_text);
 187     *dump_text = new_text;
 188 }
 189 
 190 static const char *
 191 get_cluster_stack(pe_working_set_t *data_set)
     /* [previous][next][first][last][top][bottom][index][help] */
 192 {
 193     xmlNode *stack = get_xpath_object("//nvpair[@name='cluster-infrastructure']",
 194                                       data_set->input, LOG_DEBUG);
 195     return stack? crm_element_value(stack, XML_NVPAIR_ATTR_VALUE) : "unknown";
 196 }
 197 
 198 static char *
 199 last_changed_string(const char *last_written, const char *user,
     /* [previous][next][first][last][top][bottom][index][help] */
 200                     const char *client, const char *origin) {
 201     if (last_written != NULL || user != NULL || client != NULL || origin != NULL) {
 202         return crm_strdup_printf("%s%s%s%s%s%s%s",
 203                                  last_written ? last_written : "",
 204                                  user ? " by " : "",
 205                                  user ? user : "",
 206                                  client ? " via " : "",
 207                                  client ? client : "",
 208                                  origin ? " on " : "",
 209                                  origin ? origin : "");
 210     } else {
 211         return strdup("");
 212     }
 213 }
 214 
 215 static char *
 216 op_history_string(xmlNode *xml_op, const char *task, const char *interval_ms_s,
     /* [previous][next][first][last][top][bottom][index][help] */
 217                   int rc, bool print_timing) {
 218     const char *call = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
 219     char *interval_str = NULL;
 220     char *buf = NULL;
 221 
 222     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
 223         char *pair = pcmk__format_nvpair("interval", interval_ms_s, "ms");
 224         interval_str = crm_strdup_printf(" %s", pair);
 225         free(pair);
 226     }
 227 
 228     if (print_timing) {
 229         char *last_change_str = NULL;
 230         char *exec_str = NULL;
 231         char *queue_str = NULL;
 232 
 233         const char *value = NULL;
 234 
 235         time_t epoch = 0;
 236 
 237         if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE, &epoch) == pcmk_ok)
 238             && (epoch > 0)) {
 239             char *epoch_str = pcmk__epoch2str(&epoch, 0);
 240 
 241             last_change_str = crm_strdup_printf(" %s=\"%s\"",
 242                                                 XML_RSC_OP_LAST_CHANGE,
 243                                                 pcmk__s(epoch_str, ""));
 244             free(epoch_str);
 245         }
 246 
 247         value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
 248         if (value) {
 249             char *pair = pcmk__format_nvpair(XML_RSC_OP_T_EXEC, value, "ms");
 250             exec_str = crm_strdup_printf(" %s", pair);
 251             free(pair);
 252         }
 253 
 254         value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
 255         if (value) {
 256             char *pair = pcmk__format_nvpair(XML_RSC_OP_T_QUEUE, value, "ms");
 257             queue_str = crm_strdup_printf(" %s", pair);
 258             free(pair);
 259         }
 260 
 261         buf = crm_strdup_printf("(%s) %s:%s%s%s%s rc=%d (%s)", call, task,
 262                                 interval_str ? interval_str : "",
 263                                 last_change_str ? last_change_str : "",
 264                                 exec_str ? exec_str : "",
 265                                 queue_str ? queue_str : "",
 266                                 rc, services_ocf_exitcode_str(rc));
 267 
 268         if (last_change_str) {
 269             free(last_change_str);
 270         }
 271 
 272         if (exec_str) {
 273             free(exec_str);
 274         }
 275 
 276         if (queue_str) {
 277             free(queue_str);
 278         }
 279     } else {
 280         buf = crm_strdup_printf("(%s) %s%s%s", call, task,
 281                                 interval_str ? ":" : "",
 282                                 interval_str ? interval_str : "");
 283     }
 284 
 285     if (interval_str) {
 286         free(interval_str);
 287     }
 288 
 289     return buf;
 290 }
 291 
 292 static char *
 293 resource_history_string(pe_resource_t *rsc, const char *rsc_id, bool all,
     /* [previous][next][first][last][top][bottom][index][help] */
 294                         int failcount, time_t last_failure) {
 295     char *buf = NULL;
 296 
 297     if (rsc == NULL) {
 298         buf = crm_strdup_printf("%s: orphan", rsc_id);
 299     } else if (all || failcount || last_failure > 0) {
 300         char *failcount_s = NULL;
 301         char *lastfail_s = NULL;
 302 
 303         if (failcount > 0) {
 304             failcount_s = crm_strdup_printf(" %s=%d", PCMK__FAIL_COUNT_PREFIX,
 305                                             failcount);
 306         } else {
 307             failcount_s = strdup("");
 308         }
 309         if (last_failure > 0) {
 310             buf = pcmk__epoch2str(&last_failure, 0);
 311             lastfail_s = crm_strdup_printf(" %s='%s'",
 312                                            PCMK__LAST_FAILURE_PREFIX, buf);
 313             free(buf);
 314         }
 315 
 316         buf = crm_strdup_printf("%s: migration-threshold=%d%s%s",
 317                                 rsc_id, rsc->migration_threshold, failcount_s,
 318                                 lastfail_s? lastfail_s : "");
 319         free(failcount_s);
 320         free(lastfail_s);
 321     } else {
 322         buf = crm_strdup_printf("%s:", rsc_id);
 323     }
 324 
 325     return buf;
 326 }
 327 
 328 static const char *
 329 get_node_feature_set(pe_node_t *node) {
     /* [previous][next][first][last][top][bottom][index][help] */
 330     const char *feature_set = NULL;
 331 
 332     if (node->details->online && !pe__is_guest_or_remote_node(node)) {
 333         feature_set = g_hash_table_lookup(node->details->attrs,
 334                                           CRM_ATTR_FEATURE_SET);
 335         /* The feature set attribute is present since 3.15.1. If it is missing
 336          * then the node must be running an earlier version. */
 337         if (feature_set == NULL) {
 338             feature_set = "<3.15.1";
 339         }
 340     }
 341     return feature_set;
 342 }
 343 
 344 static bool
 345 is_mixed_version(pe_working_set_t *data_set) {
     /* [previous][next][first][last][top][bottom][index][help] */
 346     const char *feature_set = NULL;
 347     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
 348         pe_node_t *node = gIter->data;
 349         const char *node_feature_set = get_node_feature_set(node);
 350         if (node_feature_set != NULL) {
 351             if (feature_set == NULL) {
 352                 feature_set = node_feature_set;
 353             } else if (strcmp(feature_set, node_feature_set) != 0) {
 354                 return true;
 355             }
 356         }
 357     }
 358     return false;
 359 }
 360 
 361 static char *
 362 formatted_xml_buf(pe_resource_t *rsc, bool raw)
     /* [previous][next][first][last][top][bottom][index][help] */
 363 {
 364     if (raw) {
 365         return dump_xml_formatted(rsc->orig_xml ? rsc->orig_xml : rsc->xml);
 366     } else {
 367         return dump_xml_formatted(rsc->xml);
 368     }
 369 }
 370 
 371 PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
 372                   "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 373 static int
 374 cluster_summary(pcmk__output_t *out, va_list args) {
 375     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 376     enum pcmk_pacemakerd_state pcmkd_state =
 377         (enum pcmk_pacemakerd_state) va_arg(args, int);
 378     uint32_t section_opts = va_arg(args, uint32_t);
 379     uint32_t show_opts = va_arg(args, uint32_t);
 380 
 381     int rc = pcmk_rc_no_output;
 382     const char *stack_s = get_cluster_stack(data_set);
 383 
 384     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
 385         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 386         out->message(out, "cluster-stack", stack_s, pcmkd_state);
 387     }
 388 
 389     if (pcmk_is_set(section_opts, pcmk_section_dc)) {
 390         xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
 391                                                data_set->input, LOG_DEBUG);
 392         const char *dc_version_s = dc_version?
 393                                    crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
 394                                    : NULL;
 395         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
 396         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;
 397         bool mixed_version = is_mixed_version(data_set);
 398 
 399         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 400         out->message(out, "cluster-dc", data_set->dc_node, quorum,
 401                      dc_version_s, dc_name, mixed_version);
 402         free(dc_name);
 403     }
 404 
 405     if (pcmk_is_set(section_opts, pcmk_section_times)) {
 406         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
 407         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
 408         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
 409         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 410 
 411         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 412         out->message(out, "cluster-times",
 413                      data_set->localhost, last_written, user, client, origin);
 414     }
 415 
 416     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
 417         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 418         out->message(out, "cluster-counts", g_list_length(data_set->nodes),
 419                      data_set->ninstances, data_set->disabled_resources,
 420                      data_set->blocked_resources);
 421     }
 422 
 423     if (pcmk_is_set(section_opts, pcmk_section_options)) {
 424         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 425         out->message(out, "cluster-options", data_set);
 426     }
 427 
 428     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 429 
 430     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
 431         if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) {
 432             rc = pcmk_rc_ok;
 433         }
 434     }
 435 
 436     return rc;
 437 }
 438 
 439 PCMK__OUTPUT_ARGS("cluster-summary", "pe_working_set_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
 440                   "enum pcmk_pacemakerd_state", "uint32_t", "uint32_t")
 441 static int
 442 cluster_summary_html(pcmk__output_t *out, va_list args) {
 443     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 444     enum pcmk_pacemakerd_state pcmkd_state =
 445         (enum pcmk_pacemakerd_state) va_arg(args, int);
 446     uint32_t section_opts = va_arg(args, uint32_t);
 447     uint32_t show_opts = va_arg(args, uint32_t);
 448 
 449     int rc = pcmk_rc_no_output;
 450     const char *stack_s = get_cluster_stack(data_set);
 451 
 452     if (pcmk_is_set(section_opts, pcmk_section_stack)) {
 453         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 454         out->message(out, "cluster-stack", stack_s, pcmkd_state);
 455     }
 456 
 457     /* Always print DC if none, even if not requested */
 458     if (data_set->dc_node == NULL || pcmk_is_set(section_opts, pcmk_section_dc)) {
 459         xmlNode *dc_version = get_xpath_object("//nvpair[@name='dc-version']",
 460                                                data_set->input, LOG_DEBUG);
 461         const char *dc_version_s = dc_version?
 462                                    crm_element_value(dc_version, XML_NVPAIR_ATTR_VALUE)
 463                                    : NULL;
 464         const char *quorum = crm_element_value(data_set->input, XML_ATTR_HAVE_QUORUM);
 465         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;
 466         bool mixed_version = is_mixed_version(data_set);
 467 
 468         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 469         out->message(out, "cluster-dc", data_set->dc_node, quorum,
 470                      dc_version_s, dc_name, mixed_version);
 471         free(dc_name);
 472     }
 473 
 474     if (pcmk_is_set(section_opts, pcmk_section_times)) {
 475         const char *last_written = crm_element_value(data_set->input, XML_CIB_ATTR_WRITTEN);
 476         const char *user = crm_element_value(data_set->input, XML_ATTR_UPDATE_USER);
 477         const char *client = crm_element_value(data_set->input, XML_ATTR_UPDATE_CLIENT);
 478         const char *origin = crm_element_value(data_set->input, XML_ATTR_UPDATE_ORIG);
 479 
 480         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 481         out->message(out, "cluster-times",
 482                      data_set->localhost, last_written, user, client, origin);
 483     }
 484 
 485     if (pcmk_is_set(section_opts, pcmk_section_counts)) {
 486         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary");
 487         out->message(out, "cluster-counts", g_list_length(data_set->nodes),
 488                      data_set->ninstances, data_set->disabled_resources,
 489                      data_set->blocked_resources);
 490     }
 491 
 492     if (pcmk_is_set(section_opts, pcmk_section_options)) {
 493         /* Kind of a hack - close the list we may have opened earlier in this
 494          * function so we can put all the options into their own list.  We
 495          * only want to do this on HTML output, though.
 496          */
 497         PCMK__OUTPUT_LIST_FOOTER(out, rc);
 498 
 499         out->begin_list(out, NULL, NULL, "Config Options");
 500         out->message(out, "cluster-options", data_set);
 501     }
 502 
 503     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 504 
 505     if (pcmk_is_set(section_opts, pcmk_section_maint_mode)) {
 506         if (out->message(out, "maint-mode", data_set->flags) == pcmk_rc_ok) {
 507             rc = pcmk_rc_ok;
 508         }
 509     }
 510 
 511     return rc;
 512 }
 513 
 514 char *
 515 pe__node_display_name(pe_node_t *node, bool print_detail)
     /* [previous][next][first][last][top][bottom][index][help] */
 516 {
 517     char *node_name;
 518     const char *node_host = NULL;
 519     const char *node_id = NULL;
 520     int name_len;
 521 
 522     CRM_ASSERT((node != NULL) && (node->details != NULL) && (node->details->uname != NULL));
 523 
 524     /* Host is displayed only if this is a guest node and detail is requested */
 525     if (print_detail && pe__is_guest_node(node)) {
 526         const pe_resource_t *container = node->details->remote_rsc->container;
 527         const pe_node_t *host_node = pe__current_node(container);
 528 
 529         if (host_node && host_node->details) {
 530             node_host = host_node->details->uname;
 531         }
 532         if (node_host == NULL) {
 533             node_host = ""; /* so we at least get "uname@" to indicate guest */
 534         }
 535     }
 536 
 537     /* Node ID is displayed if different from uname and detail is requested */
 538     if (print_detail && !pcmk__str_eq(node->details->uname, node->details->id, pcmk__str_casei)) {
 539         node_id = node->details->id;
 540     }
 541 
 542     /* Determine name length */
 543     name_len = strlen(node->details->uname) + 1;
 544     if (node_host) {
 545         name_len += strlen(node_host) + 1; /* "@node_host" */
 546     }
 547     if (node_id) {
 548         name_len += strlen(node_id) + 3; /* + " (node_id)" */
 549     }
 550 
 551     /* Allocate and populate display name */
 552     node_name = malloc(name_len);
 553     CRM_ASSERT(node_name != NULL);
 554     strcpy(node_name, node->details->uname);
 555     if (node_host) {
 556         strcat(node_name, "@");
 557         strcat(node_name, node_host);
 558     }
 559     if (node_id) {
 560         strcat(node_name, " (");
 561         strcat(node_name, node_id);
 562         strcat(node_name, ")");
 563     }
 564     return node_name;
 565 }
 566 
 567 int
 568 pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
     /* [previous][next][first][last][top][bottom][index][help] */
 569                          , size_t pairs_count, ...)
 570 {
 571     xmlNodePtr xml_node = NULL;
 572     va_list args;
 573 
 574     CRM_ASSERT(tag_name != NULL);
 575 
 576     xml_node = pcmk__output_xml_peek_parent(out);
 577     CRM_ASSERT(xml_node != NULL);
 578     xml_node = is_list
 579         ? create_xml_node(xml_node, tag_name)
 580         : xmlNewChild(xml_node, NULL, (pcmkXmlStr) tag_name, NULL);
 581 
 582     va_start(args, pairs_count);
 583     while(pairs_count--) {
 584         const char *param_name = va_arg(args, const char *);
 585         const char *param_value = va_arg(args, const char *);
 586         if (param_name && param_value) {
 587             crm_xml_add(xml_node, param_name, param_value);
 588         }
 589     };
 590     va_end(args);
 591 
 592     if (is_list) {
 593         pcmk__output_xml_push_parent(out, xml_node);
 594     }
 595     return pcmk_rc_ok;
 596 }
 597 
 598 static const char *
 599 role_desc(enum rsc_role_e role)
     /* [previous][next][first][last][top][bottom][index][help] */
 600 {
 601     if (role == RSC_ROLE_PROMOTED) {
 602 #ifdef PCMK__COMPAT_2_0
 603         return "as " RSC_ROLE_PROMOTED_LEGACY_S " ";
 604 #else
 605         return "in " RSC_ROLE_PROMOTED_S " role ";
 606 #endif
 607     }
 608     return "";
 609 }
 610 
 611 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 612 static int
 613 ban_html(pcmk__output_t *out, va_list args) {
 614     pe_node_t *pe_node = va_arg(args, pe_node_t *);
 615     pe__location_t *location = va_arg(args, pe__location_t *);
 616     uint32_t show_opts = va_arg(args, uint32_t);
 617 
 618     char *node_name = pe__node_display_name(pe_node,
 619                                             pcmk_is_set(show_opts, pcmk_show_node_id));
 620     char *buf = crm_strdup_printf("%s\tprevents %s from running %son %s",
 621                                   location->id, location->rsc_lh->id,
 622                                   role_desc(location->role_filter), node_name);
 623 
 624     pcmk__output_create_html_node(out, "li", NULL, NULL, buf);
 625 
 626     free(node_name);
 627     free(buf);
 628     return pcmk_rc_ok;
 629 }
 630 
 631 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 632 static int
 633 ban_text(pcmk__output_t *out, va_list args) {
 634     pe_node_t *pe_node = va_arg(args, pe_node_t *);
 635     pe__location_t *location = va_arg(args, pe__location_t *);
 636     uint32_t show_opts = va_arg(args, uint32_t);
 637 
 638     char *node_name = pe__node_display_name(pe_node,
 639                                             pcmk_is_set(show_opts, pcmk_show_node_id));
 640     out->list_item(out, NULL, "%s\tprevents %s from running %son %s",
 641                    location->id, location->rsc_lh->id,
 642                    role_desc(location->role_filter), node_name);
 643 
 644     free(node_name);
 645     return pcmk_rc_ok;
 646 }
 647 
 648 PCMK__OUTPUT_ARGS("ban", "pe_node_t *", "pe__location_t *", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
 649 static int
 650 ban_xml(pcmk__output_t *out, va_list args) {
 651     pe_node_t *pe_node = va_arg(args, pe_node_t *);
 652     pe__location_t *location = va_arg(args, pe__location_t *);
 653     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
 654 
 655     const char *promoted_only = pcmk__btoa(location->role_filter == RSC_ROLE_PROMOTED);
 656     char *weight_s = pcmk__itoa(pe_node->weight);
 657 
 658     pcmk__output_create_xml_node(out, "ban",
 659                                  "id", location->id,
 660                                  "resource", location->rsc_lh->id,
 661                                  "node", pe_node->details->uname,
 662                                  "weight", weight_s,
 663                                  "promoted-only", promoted_only,
 664                                  /* This is a deprecated alias for
 665                                   * promoted_only. Removing it will break
 666                                   * backward compatibility of the API schema,
 667                                   * which will require an API schema major
 668                                   * version bump.
 669                                   */
 670                                  "master_only", promoted_only,
 671                                  NULL);
 672 
 673     free(weight_s);
 674     return pcmk_rc_ok;
 675 }
 676 
 677 PCMK__OUTPUT_ARGS("ban-list", "pe_working_set_t *", "const char *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
 678                   "uint32_t", "bool")
 679 static int
 680 ban_list(pcmk__output_t *out, va_list args) {
 681     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 682     const char *prefix = va_arg(args, const char *);
 683     GList *only_rsc = va_arg(args, GList *);
 684     uint32_t show_opts = va_arg(args, uint32_t);
 685     bool print_spacer = va_arg(args, int);
 686 
 687     GList *gIter, *gIter2;
 688     int rc = pcmk_rc_no_output;
 689 
 690     /* Print each ban */
 691     for (gIter = data_set->placement_constraints; gIter != NULL; gIter = gIter->next) {
 692         pe__location_t *location = gIter->data;
 693         const pe_resource_t *rsc = location->rsc_lh;
 694 
 695         if (prefix != NULL && !g_str_has_prefix(location->id, prefix)) {
 696             continue;
 697         }
 698 
 699         if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
 700                                pcmk__str_star_matches)
 701             && !pcmk__str_in_list(rsc_printable_id(pe__const_top_resource(rsc, false)),
 702                                   only_rsc, pcmk__str_star_matches)) {
 703             continue;
 704         }
 705 
 706         for (gIter2 = location->node_list_rh; gIter2 != NULL; gIter2 = gIter2->next) {
 707             pe_node_t *node = (pe_node_t *) gIter2->data;
 708 
 709             if (node->weight < 0) {
 710                 PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Negative Location Constraints");
 711                 out->message(out, "ban", node, location, show_opts);
 712             }
 713         }
 714     }
 715 
 716     PCMK__OUTPUT_LIST_FOOTER(out, rc);
 717     return rc;
 718 }
 719 
 720 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 721 static int
 722 cluster_counts_html(pcmk__output_t *out, va_list args) {
 723     unsigned int nnodes = va_arg(args, unsigned int);
 724     int nresources = va_arg(args, int);
 725     int ndisabled = va_arg(args, int);
 726     int nblocked = va_arg(args, int);
 727 
 728     xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "li", NULL);
 729     xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "li", NULL);
 730 
 731     char *nnodes_str = crm_strdup_printf("%d node%s configured",
 732                                          nnodes, pcmk__plural_s(nnodes));
 733 
 734     pcmk_create_html_node(nodes_node, "span", NULL, NULL, nnodes_str);
 735     free(nnodes_str);
 736 
 737     if (ndisabled && nblocked) {
 738         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
 739                                     nresources, pcmk__plural_s(nresources),
 740                                     ndisabled);
 741         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
 742         free(s);
 743 
 744         pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
 745 
 746         s = crm_strdup_printf(", %d ", nblocked);
 747         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
 748         free(s);
 749 
 750         pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
 751         pcmk_create_html_node(resources_node, "span", NULL, NULL,
 752                               " from further action due to failure)");
 753     } else if (ndisabled && !nblocked) {
 754         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
 755                                     nresources, pcmk__plural_s(nresources),
 756                                     ndisabled);
 757         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
 758         free(s);
 759 
 760         pcmk_create_html_node(resources_node, "span", NULL, "bold", "DISABLED");
 761         pcmk_create_html_node(resources_node, "span", NULL, NULL, ")");
 762     } else if (!ndisabled && nblocked) {
 763         char *s = crm_strdup_printf("%d resource instance%s configured (%d ",
 764                                     nresources, pcmk__plural_s(nresources),
 765                                     nblocked);
 766         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
 767         free(s);
 768 
 769         pcmk_create_html_node(resources_node, "span", NULL, "bold", "BLOCKED");
 770         pcmk_create_html_node(resources_node, "span", NULL, NULL,
 771                               " from further action due to failure)");
 772     } else {
 773         char *s = crm_strdup_printf("%d resource instance%s configured",
 774                                     nresources, pcmk__plural_s(nresources));
 775         pcmk_create_html_node(resources_node, "span", NULL, NULL, s);
 776         free(s);
 777     }
 778 
 779     return pcmk_rc_ok;
 780 }
 781 
 782 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 783 static int
 784 cluster_counts_text(pcmk__output_t *out, va_list args) {
 785     unsigned int nnodes = va_arg(args, unsigned int);
 786     int nresources = va_arg(args, int);
 787     int ndisabled = va_arg(args, int);
 788     int nblocked = va_arg(args, int);
 789 
 790     out->list_item(out, NULL, "%d node%s configured",
 791                    nnodes, pcmk__plural_s(nnodes));
 792 
 793     if (ndisabled && nblocked) {
 794         out->list_item(out, NULL, "%d resource instance%s configured "
 795                                   "(%d DISABLED, %d BLOCKED from "
 796                                   "further action due to failure)",
 797                        nresources, pcmk__plural_s(nresources), ndisabled,
 798                        nblocked);
 799     } else if (ndisabled && !nblocked) {
 800         out->list_item(out, NULL, "%d resource instance%s configured "
 801                                   "(%d DISABLED)",
 802                        nresources, pcmk__plural_s(nresources), ndisabled);
 803     } else if (!ndisabled && nblocked) {
 804         out->list_item(out, NULL, "%d resource instance%s configured "
 805                                   "(%d BLOCKED from further action "
 806                                   "due to failure)",
 807                        nresources, pcmk__plural_s(nresources), nblocked);
 808     } else {
 809         out->list_item(out, NULL, "%d resource instance%s configured",
 810                        nresources, pcmk__plural_s(nresources));
 811     }
 812 
 813     return pcmk_rc_ok;
 814 }
 815 
 816 PCMK__OUTPUT_ARGS("cluster-counts", "unsigned int", "int", "int", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
 817 static int
 818 cluster_counts_xml(pcmk__output_t *out, va_list args) {
 819     unsigned int nnodes = va_arg(args, unsigned int);
 820     int nresources = va_arg(args, int);
 821     int ndisabled = va_arg(args, int);
 822     int nblocked = va_arg(args, int);
 823 
 824     xmlNodePtr nodes_node = pcmk__output_create_xml_node(out, "nodes_configured", NULL);
 825     xmlNodePtr resources_node = pcmk__output_create_xml_node(out, "resources_configured", NULL);
 826 
 827     char *s = pcmk__itoa(nnodes);
 828     crm_xml_add(nodes_node, "number", s);
 829     free(s);
 830 
 831     s = pcmk__itoa(nresources);
 832     crm_xml_add(resources_node, "number", s);
 833     free(s);
 834 
 835     s = pcmk__itoa(ndisabled);
 836     crm_xml_add(resources_node, "disabled", s);
 837     free(s);
 838 
 839     s = pcmk__itoa(nblocked);
 840     crm_xml_add(resources_node, "blocked", s);
 841     free(s);
 842 
 843     return pcmk_rc_ok;
 844 }
 845 
 846 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 847                   "char *", "int")
 848 static int
 849 cluster_dc_html(pcmk__output_t *out, va_list args) {
 850     pe_node_t *dc = va_arg(args, pe_node_t *);
 851     const char *quorum = va_arg(args, const char *);
 852     const char *dc_version_s = va_arg(args, const char *);
 853     char *dc_name = va_arg(args, char *);
 854     bool mixed_version = va_arg(args, int);
 855 
 856     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 857 
 858     pcmk_create_html_node(node, "span", NULL, "bold", "Current DC: ");
 859 
 860     if (dc) {
 861         char *buf = crm_strdup_printf("%s (version %s) -", dc_name,
 862                                       dc_version_s ? dc_version_s : "unknown");
 863         pcmk_create_html_node(node, "span", NULL, NULL, buf);
 864         free(buf);
 865 
 866         if (mixed_version) {
 867             pcmk_create_html_node(node, "span", NULL, "warning",
 868                                   " MIXED-VERSION");
 869         }
 870         pcmk_create_html_node(node, "span", NULL, NULL, " partition");
 871         if (crm_is_true(quorum)) {
 872             pcmk_create_html_node(node, "span", NULL, NULL, " with");
 873         } else {
 874             pcmk_create_html_node(node, "span", NULL, "warning", " WITHOUT");
 875         }
 876         pcmk_create_html_node(node, "span", NULL, NULL, " quorum");
 877     } else {
 878         pcmk_create_html_node(node, "span", NULL, "warning", "NONE");
 879     }
 880 
 881     return pcmk_rc_ok;
 882 }
 883 
 884 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 885                   "char *", "int")
 886 static int
 887 cluster_dc_text(pcmk__output_t *out, va_list args) {
 888     pe_node_t *dc = va_arg(args, pe_node_t *);
 889     const char *quorum = va_arg(args, const char *);
 890     const char *dc_version_s = va_arg(args, const char *);
 891     char *dc_name = va_arg(args, char *);
 892     bool mixed_version = va_arg(args, int);
 893 
 894     if (dc) {
 895         out->list_item(out, "Current DC",
 896                        "%s (version %s) - %spartition %s quorum",
 897                        dc_name, dc_version_s ? dc_version_s : "unknown",
 898                        mixed_version ? "MIXED-VERSION " : "",
 899                        crm_is_true(quorum) ? "with" : "WITHOUT");
 900     } else {
 901         out->list_item(out, "Current DC", "NONE");
 902     }
 903 
 904     return pcmk_rc_ok;
 905 }
 906 
 907 PCMK__OUTPUT_ARGS("cluster-dc", "pe_node_t *", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
 908                   "char *", "int")
 909 static int
 910 cluster_dc_xml(pcmk__output_t *out, va_list args) {
 911     pe_node_t *dc = va_arg(args, pe_node_t *);
 912     const char *quorum = va_arg(args, const char *);
 913     const char *dc_version_s = va_arg(args, const char *);
 914     char *dc_name G_GNUC_UNUSED = va_arg(args, char *);
 915     bool mixed_version = va_arg(args, int);
 916 
 917     if (dc) {
 918         pcmk__output_create_xml_node(out, "current_dc",
 919                                      "present", "true",
 920                                      "version", dc_version_s ? dc_version_s : "",
 921                                      "name", dc->details->uname,
 922                                      "id", dc->details->id,
 923                                      "with_quorum", pcmk__btoa(crm_is_true(quorum)),
 924                                      "mixed_version", pcmk__btoa(mixed_version),
 925                                      NULL);
 926     } else {
 927         pcmk__output_create_xml_node(out, "current_dc",
 928                                      "present", "false",
 929                                      NULL);
 930     }
 931 
 932     return pcmk_rc_ok;
 933 }
 934 
 935 PCMK__OUTPUT_ARGS("maint-mode", "unsigned long long int")
     /* [previous][next][first][last][top][bottom][index][help] */
 936 static int
 937 cluster_maint_mode_text(pcmk__output_t *out, va_list args) {
 938     unsigned long long flags = va_arg(args, unsigned long long);
 939 
 940     if (pcmk_is_set(flags, pe_flag_maintenance_mode)) {
 941         pcmk__formatted_printf(out, "\n              *** Resource management is DISABLED ***\n");
 942         pcmk__formatted_printf(out, "  The cluster will not attempt to start, stop or recover services\n");
 943         return pcmk_rc_ok;
 944     } else if (pcmk_is_set(flags, pe_flag_stop_everything)) {
 945         pcmk__formatted_printf(out, "\n    *** Resource management is DISABLED ***\n");
 946         pcmk__formatted_printf(out, "  The cluster will keep all resources stopped\n");
 947         return pcmk_rc_ok;
 948     } else {
 949         return pcmk_rc_no_output;
 950     }
 951 }
 952 
 953 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
 954 static int
 955 cluster_options_html(pcmk__output_t *out, va_list args) {
 956     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
 957 
 958     out->list_item(out, NULL, "STONITH of failed nodes %s",
 959                    pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled");
 960 
 961     out->list_item(out, NULL, "Cluster is %s",
 962                    pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric");
 963 
 964     switch (data_set->no_quorum_policy) {
 965         case no_quorum_freeze:
 966             out->list_item(out, NULL, "No quorum policy: Freeze resources");
 967             break;
 968 
 969         case no_quorum_stop:
 970             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
 971             break;
 972 
 973         case no_quorum_demote:
 974             out->list_item(out, NULL, "No quorum policy: Demote promotable "
 975                            "resources and stop all other resources");
 976             break;
 977 
 978         case no_quorum_ignore:
 979             out->list_item(out, NULL, "No quorum policy: Ignore");
 980             break;
 981 
 982         case no_quorum_suicide:
 983             out->list_item(out, NULL, "No quorum policy: Suicide");
 984             break;
 985     }
 986 
 987     if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
 988         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 989 
 990         pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
 991         pcmk_create_html_node(node, "span", NULL, "bold", "DISABLED");
 992         pcmk_create_html_node(node, "span", NULL, NULL,
 993                               " (the cluster will not attempt to start, stop, or recover services)");
 994     } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) {
 995         xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
 996 
 997         pcmk_create_html_node(node, "span", NULL, NULL, "Resource management: ");
 998         pcmk_create_html_node(node, "span", NULL, "bold", "STOPPED");
 999         pcmk_create_html_node(node, "span", NULL, NULL,
1000                               " (the cluster will keep all resources stopped)");
1001     } else {
1002         out->list_item(out, NULL, "Resource management: enabled");
1003     }
1004 
1005     return pcmk_rc_ok;
1006 }
1007 
1008 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1009 static int
1010 cluster_options_log(pcmk__output_t *out, va_list args) {
1011     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
1012 
1013     if (pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)) {
1014         return out->info(out, "Resource management is DISABLED.  The cluster will not attempt to start, stop or recover services.");
1015     } else if (pcmk_is_set(data_set->flags, pe_flag_stop_everything)) {
1016         return out->info(out, "Resource management is DISABLED.  The cluster has stopped all resources.");
1017     } else {
1018         return pcmk_rc_no_output;
1019     }
1020 }
1021 
1022 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1023 static int
1024 cluster_options_text(pcmk__output_t *out, va_list args) {
1025     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
1026 
1027     out->list_item(out, NULL, "STONITH of failed nodes %s",
1028                    pcmk_is_set(data_set->flags, pe_flag_stonith_enabled) ? "enabled" : "disabled");
1029 
1030     out->list_item(out, NULL, "Cluster is %s",
1031                    pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster) ? "symmetric" : "asymmetric");
1032 
1033     switch (data_set->no_quorum_policy) {
1034         case no_quorum_freeze:
1035             out->list_item(out, NULL, "No quorum policy: Freeze resources");
1036             break;
1037 
1038         case no_quorum_stop:
1039             out->list_item(out, NULL, "No quorum policy: Stop ALL resources");
1040             break;
1041 
1042         case no_quorum_demote:
1043             out->list_item(out, NULL, "No quorum policy: Demote promotable "
1044                            "resources and stop all other resources");
1045             break;
1046 
1047         case no_quorum_ignore:
1048             out->list_item(out, NULL, "No quorum policy: Ignore");
1049             break;
1050 
1051         case no_quorum_suicide:
1052             out->list_item(out, NULL, "No quorum policy: Suicide");
1053             break;
1054     }
1055 
1056     return pcmk_rc_ok;
1057 }
1058 
1059 PCMK__OUTPUT_ARGS("cluster-options", "pe_working_set_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
1060 static int
1061 cluster_options_xml(pcmk__output_t *out, va_list args) {
1062     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
1063 
1064     const char *no_quorum_policy = NULL;
1065     char *stonith_timeout_str = pcmk__itoa(data_set->stonith_timeout);
1066     char *priority_fencing_delay_str = pcmk__itoa(data_set->priority_fencing_delay * 1000);
1067 
1068     switch (data_set->no_quorum_policy) {
1069         case no_quorum_freeze:
1070             no_quorum_policy = "freeze";
1071             break;
1072 
1073         case no_quorum_stop:
1074             no_quorum_policy = "stop";
1075             break;
1076 
1077         case no_quorum_demote:
1078             no_quorum_policy = "demote";
1079             break;
1080 
1081         case no_quorum_ignore:
1082             no_quorum_policy = "ignore";
1083             break;
1084 
1085         case no_quorum_suicide:
1086             no_quorum_policy = "suicide";
1087             break;
1088     }
1089 
1090     pcmk__output_create_xml_node(out, "cluster_options",
1091                                  "stonith-enabled", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stonith_enabled)),
1092                                  "symmetric-cluster", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_symmetric_cluster)),
1093                                  "no-quorum-policy", no_quorum_policy,
1094                                  "maintenance-mode", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_maintenance_mode)),
1095                                  "stop-all-resources", pcmk__btoa(pcmk_is_set(data_set->flags, pe_flag_stop_everything)),
1096                                  "stonith-timeout-ms", stonith_timeout_str,
1097                                  "priority-fencing-delay-ms", priority_fencing_delay_str,
1098                                  NULL);
1099     free(stonith_timeout_str);
1100     free(priority_fencing_delay_str);
1101 
1102     return pcmk_rc_ok;
1103 }
1104 
1105 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1106 static int
1107 cluster_stack_html(pcmk__output_t *out, va_list args) {
1108     const char *stack_s = va_arg(args, const char *);
1109     enum pcmk_pacemakerd_state pcmkd_state =
1110         (enum pcmk_pacemakerd_state) va_arg(args, int);
1111 
1112     xmlNodePtr node = pcmk__output_create_xml_node(out, "li", NULL);
1113 
1114     pcmk_create_html_node(node, "span", NULL, "bold", "Stack: ");
1115     pcmk_create_html_node(node, "span", NULL, NULL, stack_s);
1116 
1117     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1118         pcmk_create_html_node(node, "span", NULL, NULL, " (");
1119         pcmk_create_html_node(node, "span", NULL, NULL,
1120                               pcmk__pcmkd_state_enum2friendly(pcmkd_state));
1121         pcmk_create_html_node(node, "span", NULL, NULL, ")");
1122     }
1123     return pcmk_rc_ok;
1124 }
1125 
1126 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1127 static int
1128 cluster_stack_text(pcmk__output_t *out, va_list args) {
1129     const char *stack_s = va_arg(args, const char *);
1130     enum pcmk_pacemakerd_state pcmkd_state =
1131         (enum pcmk_pacemakerd_state) va_arg(args, int);
1132 
1133     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1134         out->list_item(out, "Stack", "%s (%s)",
1135                        stack_s, pcmk__pcmkd_state_enum2friendly(pcmkd_state));
1136     } else {
1137         out->list_item(out, "Stack", "%s", stack_s);
1138     }
1139 
1140     return pcmk_rc_ok;
1141 }
1142 
1143 PCMK__OUTPUT_ARGS("cluster-stack", "const char *", "enum pcmk_pacemakerd_state")
     /* [previous][next][first][last][top][bottom][index][help] */
1144 static int
1145 cluster_stack_xml(pcmk__output_t *out, va_list args) {
1146     const char *stack_s = va_arg(args, const char *);
1147     enum pcmk_pacemakerd_state pcmkd_state =
1148         (enum pcmk_pacemakerd_state) va_arg(args, int);
1149 
1150     const char *state_s = NULL;
1151 
1152     if (pcmkd_state != pcmk_pacemakerd_state_invalid) {
1153         state_s = pcmk_pacemakerd_api_daemon_state_enum2text(pcmkd_state);
1154     }
1155 
1156     pcmk__output_create_xml_node(out, "stack",
1157                                  "type", stack_s,
1158                                  "pacemakerd-state", state_s,
1159                                  NULL);
1160 
1161     return pcmk_rc_ok;
1162 }
1163 
1164 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1165                   "const char *", "const char *", "const char *")
1166 static int
1167 cluster_times_html(pcmk__output_t *out, va_list args) {
1168     const char *our_nodename = va_arg(args, const char *);
1169     const char *last_written = va_arg(args, const char *);
1170     const char *user = va_arg(args, const char *);
1171     const char *client = va_arg(args, const char *);
1172     const char *origin = va_arg(args, const char *);
1173 
1174     xmlNodePtr updated_node = pcmk__output_create_xml_node(out, "li", NULL);
1175     xmlNodePtr changed_node = pcmk__output_create_xml_node(out, "li", NULL);
1176 
1177     char *time_s = pcmk__epoch2str(NULL, 0);
1178 
1179     pcmk_create_html_node(updated_node, "span", NULL, "bold", "Last updated: ");
1180     pcmk_create_html_node(updated_node, "span", NULL, NULL, time_s);
1181 
1182     if (our_nodename != NULL) {
1183         pcmk_create_html_node(updated_node, "span", NULL, NULL, " on ");
1184         pcmk_create_html_node(updated_node, "span", NULL, NULL, our_nodename);
1185     }
1186 
1187     free(time_s);
1188     time_s = last_changed_string(last_written, user, client, origin);
1189 
1190     pcmk_create_html_node(changed_node, "span", NULL, "bold", "Last change: ");
1191     pcmk_create_html_node(changed_node, "span", NULL, NULL, time_s);
1192 
1193     free(time_s);
1194     return pcmk_rc_ok;
1195 }
1196 
1197 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1198                   "const char *", "const char *", "const char *")
1199 static int
1200 cluster_times_xml(pcmk__output_t *out, va_list args) {
1201     const char *our_nodename = va_arg(args, const char *);
1202     const char *last_written = va_arg(args, const char *);
1203     const char *user = va_arg(args, const char *);
1204     const char *client = va_arg(args, const char *);
1205     const char *origin = va_arg(args, const char *);
1206 
1207     char *time_s = pcmk__epoch2str(NULL, 0);
1208 
1209     pcmk__output_create_xml_node(out, "last_update",
1210                                  "time", time_s,
1211                                  "origin", our_nodename,
1212                                  NULL);
1213 
1214     pcmk__output_create_xml_node(out, "last_change",
1215                                  "time", last_written ? last_written : "",
1216                                  "user", user ? user : "",
1217                                  "client", client ? client : "",
1218                                  "origin", origin ? origin : "",
1219                                  NULL);
1220 
1221     free(time_s);
1222     return pcmk_rc_ok;
1223 }
1224 
1225 PCMK__OUTPUT_ARGS("cluster-times", "const char *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
1226                   "const char *", "const char *", "const char *")
1227 static int
1228 cluster_times_text(pcmk__output_t *out, va_list args) {
1229     const char *our_nodename = va_arg(args, const char *);
1230     const char *last_written = va_arg(args, const char *);
1231     const char *user = va_arg(args, const char *);
1232     const char *client = va_arg(args, const char *);
1233     const char *origin = va_arg(args, const char *);
1234 
1235     char *time_s = pcmk__epoch2str(NULL, 0);
1236 
1237     out->list_item(out, "Last updated", "%s%s%s",
1238                    time_s, (our_nodename != NULL)? " on " : "",
1239                    pcmk__s(our_nodename, ""));
1240 
1241     free(time_s);
1242     time_s = last_changed_string(last_written, user, client, origin);
1243 
1244     out->list_item(out, "Last change", " %s", time_s);
1245 
1246     free(time_s);
1247     return pcmk_rc_ok;
1248 }
1249 
1250 /*!
1251  * \internal
1252  * \brief Display a failed action in less-technical natural language
1253  *
1254  * \param[in,out] out          Output object to use for display
1255  * \param[in]     xml_op       XML containing failed action
1256  * \param[in]     op_key       Operation key of failed action
1257  * \param[in]     node_name    Where failed action occurred
1258  * \param[in]     rc           OCF exit code of failed action
1259  * \param[in]     status       Execution status of failed action
1260  * \param[in]     exit_reason  Exit reason given for failed action
1261  * \param[in]     exec_time    String containing execution time in milliseconds
1262  */
1263 static void
1264 failed_action_friendly(pcmk__output_t *out, const xmlNode *xml_op,
     /* [previous][next][first][last][top][bottom][index][help] */
1265                        const char *op_key, const char *node_name, int rc,
1266                        int status, const char *exit_reason,
1267                        const char *exec_time)
1268 {
1269     char *rsc_id = NULL;
1270     char *task = NULL;
1271     guint interval_ms = 0;
1272     time_t last_change_epoch = 0;
1273     GString *str = NULL;
1274 
1275     if (pcmk__str_empty(op_key)
1276         || !parse_op_key(op_key, &rsc_id, &task, &interval_ms)) {
1277         rsc_id = strdup("unknown resource");
1278         task = strdup("unknown action");
1279         interval_ms = 0;
1280     }
1281     CRM_ASSERT((rsc_id != NULL) && (task != NULL));
1282 
1283     str = g_string_sized_new(256); // Should be sufficient for most messages
1284 
1285     pcmk__g_strcat(str, rsc_id, " ", NULL);
1286 
1287     if (interval_ms != 0) {
1288         pcmk__g_strcat(str, pcmk__readable_interval(interval_ms), "-interval ",
1289                        NULL);
1290     }
1291     pcmk__g_strcat(str, crm_action_str(task, interval_ms), " on ", node_name,
1292                    NULL);
1293 
1294     if (status == PCMK_EXEC_DONE) {
1295         pcmk__g_strcat(str, " returned '", services_ocf_exitcode_str(rc), "'",
1296                        NULL);
1297         if (!pcmk__str_empty(exit_reason)) {
1298             pcmk__g_strcat(str, " (", exit_reason, ")", NULL);
1299         }
1300 
1301     } else {
1302         pcmk__g_strcat(str, " could not be executed (",
1303                        pcmk_exec_status_str(status), NULL);
1304         if (!pcmk__str_empty(exit_reason)) {
1305             pcmk__g_strcat(str, ": ", exit_reason, NULL);
1306         }
1307         g_string_append_c(str, ')');
1308     }
1309 
1310 
1311     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
1312                                 &last_change_epoch) == pcmk_ok) {
1313         char *s = pcmk__epoch2str(&last_change_epoch, 0);
1314 
1315         pcmk__g_strcat(str, " at ", s, NULL);
1316         free(s);
1317     }
1318     if (!pcmk__str_empty(exec_time)) {
1319         int exec_time_ms = 0;
1320 
1321         if ((pcmk__scan_min_int(exec_time, &exec_time_ms, 0) == pcmk_rc_ok)
1322             && (exec_time_ms > 0)) {
1323 
1324             pcmk__g_strcat(str, " after ",
1325                            pcmk__readable_interval(exec_time_ms), NULL);
1326         }
1327     }
1328 
1329     out->list_item(out, NULL, "%s", str->str);
1330     g_string_free(str, TRUE);
1331     free(rsc_id);
1332     free(task);
1333 }
1334 
1335 /*!
1336  * \internal
1337  * \brief Display a failed action with technical details
1338  *
1339  * \param[in,out] out          Output object to use for display
1340  * \param[in]     xml_op       XML containing failed action
1341  * \param[in]     op_key       Operation key of failed action
1342  * \param[in]     node_name    Where failed action occurred
1343  * \param[in]     rc           OCF exit code of failed action
1344  * \param[in]     status       Execution status of failed action
1345  * \param[in]     exit_reason  Exit reason given for failed action
1346  * \param[in]     exec_time    String containing execution time in milliseconds
1347  */
1348 static void
1349 failed_action_technical(pcmk__output_t *out, const xmlNode *xml_op,
     /* [previous][next][first][last][top][bottom][index][help] */
1350                         const char *op_key, const char *node_name, int rc,
1351                         int status, const char *exit_reason,
1352                         const char *exec_time)
1353 {
1354     const char *call_id = crm_element_value(xml_op, XML_LRM_ATTR_CALLID);
1355     const char *queue_time = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
1356     const char *exit_status = services_ocf_exitcode_str(rc);
1357     const char *lrm_status = pcmk_exec_status_str(status);
1358     time_t last_change_epoch = 0;
1359     GString *str = NULL;
1360 
1361     if (pcmk__str_empty(op_key)) {
1362         op_key = "unknown operation";
1363     }
1364     if (pcmk__str_empty(exit_status)) {
1365         exit_status = "unknown exit status";
1366     }
1367     if (pcmk__str_empty(call_id)) {
1368         call_id = "unknown";
1369     }
1370 
1371     str = g_string_sized_new(256);
1372 
1373     g_string_append_printf(str, "%s on %s '%s' (%d): call=%s, status='%s'",
1374                            op_key, node_name, exit_status, rc, call_id,
1375                            lrm_status);
1376 
1377     if (!pcmk__str_empty(exit_reason)) {
1378         pcmk__g_strcat(str, ", exitreason='", exit_reason, "'", NULL);
1379     }
1380 
1381     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
1382                                 &last_change_epoch) == pcmk_ok) {
1383         char *last_change_str = pcmk__epoch2str(&last_change_epoch, 0);
1384 
1385         pcmk__g_strcat(str,
1386                        ", " XML_RSC_OP_LAST_CHANGE "="
1387                        "'", last_change_str, "'", NULL);
1388         free(last_change_str);
1389     }
1390     if (!pcmk__str_empty(queue_time)) {
1391         pcmk__g_strcat(str, ", queued=", queue_time, "ms", NULL);
1392     }
1393     if (!pcmk__str_empty(exec_time)) {
1394         pcmk__g_strcat(str, ", exec=", exec_time, "ms", NULL);
1395     }
1396 
1397     out->list_item(out, NULL, "%s", str->str);
1398     g_string_free(str, TRUE);
1399 }
1400 
1401 PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
1402 static int
1403 failed_action_default(pcmk__output_t *out, va_list args)
1404 {
1405     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
1406     uint32_t show_opts = va_arg(args, uint32_t);
1407 
1408     const char *op_key = pe__xe_history_key(xml_op);
1409     const char *node_name = crm_element_value(xml_op, XML_ATTR_UNAME);
1410     const char *exit_reason = crm_element_value(xml_op,
1411                                                 XML_LRM_ATTR_EXIT_REASON);
1412     const char *exec_time = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
1413 
1414     int rc;
1415     int status;
1416 
1417     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
1418 
1419     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
1420                        &status, 0);
1421 
1422     if (pcmk__str_empty(node_name)) {
1423         node_name = "unknown node";
1424     }
1425 
1426     if (pcmk_is_set(show_opts, pcmk_show_failed_detail)) {
1427         failed_action_technical(out, xml_op, op_key, node_name, rc, status,
1428                                 exit_reason, exec_time);
1429     } else {
1430         failed_action_friendly(out, xml_op, op_key, node_name, rc, status,
1431                                exit_reason, exec_time);
1432     }
1433     return pcmk_rc_ok;
1434 }
1435 
1436 PCMK__OUTPUT_ARGS("failed-action", "xmlNodePtr", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
1437 static int
1438 failed_action_xml(pcmk__output_t *out, va_list args) {
1439     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
1440     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
1441 
1442     const char *op_key = pe__xe_history_key(xml_op);
1443     const char *op_key_name = "op_key";
1444     int rc;
1445     int status;
1446     const char *exit_reason = crm_element_value(xml_op, XML_LRM_ATTR_EXIT_REASON);
1447 
1448     time_t epoch = 0;
1449     char *rc_s = NULL;
1450     char *reason_s = crm_xml_escape(exit_reason ? exit_reason : "none");
1451     xmlNodePtr node = NULL;
1452 
1453     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_RC), &rc, 0);
1454     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
1455                        &status, 0);
1456 
1457     rc_s = pcmk__itoa(rc);
1458     if (crm_element_value(xml_op, XML_LRM_ATTR_TASK_KEY) == NULL) {
1459         op_key_name = "id";
1460     }
1461     node = pcmk__output_create_xml_node(out, "failure",
1462                                         op_key_name, op_key,
1463                                         "node", crm_element_value(xml_op, XML_ATTR_UNAME),
1464                                         "exitstatus", services_ocf_exitcode_str(rc),
1465                                         "exitreason", pcmk__s(reason_s, ""),
1466                                         "exitcode", rc_s,
1467                                         "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
1468                                         "status", pcmk_exec_status_str(status),
1469                                         NULL);
1470     free(rc_s);
1471 
1472     if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
1473                                  &epoch) == pcmk_ok) && (epoch > 0)) {
1474         guint interval_ms = 0;
1475         char *interval_ms_s = NULL;
1476         char *rc_change = pcmk__epoch2str(&epoch,
1477                                           crm_time_log_date
1478                                           |crm_time_log_timeofday
1479                                           |crm_time_log_with_timezone);
1480 
1481         crm_element_value_ms(xml_op, XML_LRM_ATTR_INTERVAL_MS, &interval_ms);
1482         interval_ms_s = crm_strdup_printf("%u", interval_ms);
1483 
1484         pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE, rc_change,
1485                            "queued", crm_element_value(xml_op, XML_RSC_OP_T_QUEUE),
1486                            "exec", crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
1487                            "interval", interval_ms_s,
1488                            "task", crm_element_value(xml_op, XML_LRM_ATTR_TASK),
1489                            NULL);
1490 
1491         free(interval_ms_s);
1492         free(rc_change);
1493     }
1494 
1495     free(reason_s);
1496     return pcmk_rc_ok;
1497 }
1498 
1499 PCMK__OUTPUT_ARGS("failed-action-list", "pe_working_set_t *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
1500                   "GList *", "uint32_t", "bool")
1501 static int
1502 failed_action_list(pcmk__output_t *out, va_list args) {
1503     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
1504     GList *only_node = va_arg(args, GList *);
1505     GList *only_rsc = va_arg(args, GList *);
1506     uint32_t show_opts = va_arg(args, uint32_t);
1507     bool print_spacer = va_arg(args, int);
1508 
1509     xmlNode *xml_op = NULL;
1510     int rc = pcmk_rc_no_output;
1511 
1512     if (xmlChildElementCount(data_set->failed) == 0) {
1513         return rc;
1514     }
1515 
1516     for (xml_op = pcmk__xml_first_child(data_set->failed); xml_op != NULL;
1517          xml_op = pcmk__xml_next(xml_op)) {
1518         char *rsc = NULL;
1519 
1520         if (!pcmk__str_in_list(crm_element_value(xml_op, XML_ATTR_UNAME), only_node,
1521                                pcmk__str_star_matches|pcmk__str_casei)) {
1522             continue;
1523         }
1524 
1525         if (pcmk_xe_mask_probe_failure(xml_op)) {
1526             continue;
1527         }
1528 
1529         if (!parse_op_key(pe__xe_history_key(xml_op), &rsc, NULL, NULL)) {
1530             continue;
1531         }
1532 
1533         if (!pcmk__str_in_list(rsc, only_rsc, pcmk__str_star_matches)) {
1534             free(rsc);
1535             continue;
1536         }
1537 
1538         free(rsc);
1539 
1540         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Failed Resource Actions");
1541         out->message(out, "failed-action", xml_op, show_opts);
1542     }
1543 
1544     PCMK__OUTPUT_LIST_FOOTER(out, rc);
1545     return rc;
1546 }
1547 
1548 static void
1549 status_node(pe_node_t *node, xmlNodePtr parent, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
1550 {
1551     int health = pe__node_health(node);
1552 
1553     // Cluster membership
1554     if (node->details->online) {
1555         pcmk_create_html_node(parent, "span", NULL, "online", " online");
1556     } else {
1557         pcmk_create_html_node(parent, "span", NULL, "offline", " OFFLINE");
1558     }
1559 
1560     // Standby mode
1561     if (node->details->standby_onfail && (node->details->running_rsc != NULL)) {
1562         pcmk_create_html_node(parent, "span", NULL, "standby",
1563                               " (in standby due to on-fail,"
1564                               " with active resources)");
1565     } else if (node->details->standby_onfail) {
1566         pcmk_create_html_node(parent, "span", NULL, "standby",
1567                               " (in standby due to on-fail)");
1568     } else if (node->details->standby && (node->details->running_rsc != NULL)) {
1569         pcmk_create_html_node(parent, "span", NULL, "standby",
1570                               " (in standby, with active resources)");
1571     } else if (node->details->standby) {
1572         pcmk_create_html_node(parent, "span", NULL, "standby", " (in standby)");
1573     }
1574 
1575     // Maintenance mode
1576     if (node->details->maintenance) {
1577         pcmk_create_html_node(parent, "span", NULL, "maint",
1578                               " (in maintenance mode)");
1579     }
1580 
1581     // Node health
1582     if (health < 0) {
1583         pcmk_create_html_node(parent, "span", NULL, "health_red",
1584                               " (health is RED)");
1585     } else if (health == 0) {
1586         pcmk_create_html_node(parent, "span", NULL, "health_yellow",
1587                               " (health is YELLOW)");
1588     }
1589 
1590     // Feature set
1591     if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
1592         const char *feature_set = get_node_feature_set(node);
1593         if (feature_set != NULL) {
1594             char *buf = crm_strdup_printf(", feature set %s", feature_set);
1595             pcmk_create_html_node(parent, "span", NULL, NULL, buf);
1596             free(buf);
1597         }
1598     }
1599 }
1600 
1601 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool",
     /* [previous][next][first][last][top][bottom][index][help] */
1602                   "GList *", "GList *")
1603 static int
1604 node_html(pcmk__output_t *out, va_list args) {
1605     pe_node_t *node = va_arg(args, pe_node_t *);
1606     uint32_t show_opts = va_arg(args, uint32_t);
1607     bool full = va_arg(args, int);
1608     GList *only_node = va_arg(args, GList *);
1609     GList *only_rsc = va_arg(args, GList *);
1610 
1611     char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1612 
1613     if (full) {
1614         xmlNodePtr item_node;
1615 
1616         if (pcmk_all_flags_set(show_opts, pcmk_show_brief | pcmk_show_rscs_by_node)) {
1617             GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
1618 
1619             out->begin_list(out, NULL, NULL, "%s:", node_name);
1620             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
1621             pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
1622             status_node(node, item_node, show_opts);
1623 
1624             if (rscs != NULL) {
1625                 uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
1626                 out->begin_list(out, NULL, NULL, "Resources");
1627                 pe__rscs_brief_output(out, rscs, new_show_opts);
1628                 out->end_list(out);
1629             }
1630 
1631             pcmk__output_xml_pop_parent(out);
1632             out->end_list(out);
1633 
1634         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
1635             GList *lpc2 = NULL;
1636             int rc = pcmk_rc_no_output;
1637 
1638             out->begin_list(out, NULL, NULL, "%s:", node_name);
1639             item_node = pcmk__output_xml_create_parent(out, "li", NULL);
1640             pcmk_create_html_node(item_node, "span", NULL, NULL, "Status:");
1641             status_node(node, item_node, show_opts);
1642 
1643             for (lpc2 = node->details->running_rsc; lpc2 != NULL; lpc2 = lpc2->next) {
1644                 pe_resource_t *rsc = (pe_resource_t *) lpc2->data;
1645                 PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Resources");
1646 
1647                 show_opts |= pcmk_show_rsc_only;
1648                 out->message(out, crm_map_element_name(rsc->xml), show_opts,
1649                              rsc, only_node, only_rsc);
1650             }
1651 
1652             PCMK__OUTPUT_LIST_FOOTER(out, rc);
1653             pcmk__output_xml_pop_parent(out);
1654             out->end_list(out);
1655 
1656         } else {
1657             char *buf = crm_strdup_printf("%s:", node_name);
1658 
1659             item_node = pcmk__output_create_xml_node(out, "li", NULL);
1660             pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
1661             status_node(node, item_node, show_opts);
1662 
1663             free(buf);
1664         }
1665     } else {
1666         out->begin_list(out, NULL, NULL, "%s:", node_name);
1667     }
1668 
1669     free(node_name);
1670     return pcmk_rc_ok;
1671 }
1672 
1673 /*!
1674  * \internal
1675  * \brief Get a human-friendly textual description of a node's status
1676  *
1677  * \param[in] node  Node to check
1678  *
1679  * \return String representation of node's status
1680  */
1681 static const char *
1682 node_text_status(const pe_node_t *node)
     /* [previous][next][first][last][top][bottom][index][help] */
1683 {
1684     if (node->details->unclean) {
1685         if (node->details->online) {
1686             return "UNCLEAN (online)";
1687 
1688         } else if (node->details->pending) {
1689             return "UNCLEAN (pending)";
1690 
1691         } else {
1692             return "UNCLEAN (offline)";
1693         }
1694 
1695     } else if (node->details->pending) {
1696         return "pending";
1697 
1698     } else if (node->details->standby_onfail && node->details->online) {
1699         return "standby (on-fail)";
1700 
1701     } else if (node->details->standby) {
1702         if (node->details->online) {
1703             if (node->details->running_rsc) {
1704                 return "standby (with active resources)";
1705             } else {
1706                 return "standby";
1707             }
1708         } else {
1709             return "OFFLINE (standby)";
1710         }
1711 
1712     } else if (node->details->maintenance) {
1713         if (node->details->online) {
1714             return "maintenance";
1715         } else {
1716             return "OFFLINE (maintenance)";
1717         }
1718 
1719     } else if (node->details->online) {
1720         return "online";
1721     }
1722 
1723     return "OFFLINE";
1724 }
1725 
1726 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *")
     /* [previous][next][first][last][top][bottom][index][help] */
1727 static int
1728 node_text(pcmk__output_t *out, va_list args) {
1729     pe_node_t *node = va_arg(args, pe_node_t *);
1730     uint32_t show_opts = va_arg(args, uint32_t);
1731     bool full = va_arg(args, int);
1732     GList *only_node = va_arg(args, GList *);
1733     GList *only_rsc = va_arg(args, GList *);
1734 
1735     if (full) {
1736         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1737         GString *str = g_string_sized_new(64);
1738         int health = pe__node_health(node);
1739 
1740         // Create a summary line with node type, name, and status
1741         if (pe__is_guest_node(node)) {
1742             g_string_append(str, "GuestNode");
1743         } else if (pe__is_remote_node(node)) {
1744             g_string_append(str, "RemoteNode");
1745         } else {
1746             g_string_append(str, "Node");
1747         }
1748         pcmk__g_strcat(str, " ", node_name, ": ", node_text_status(node), NULL);
1749 
1750         if (health < 0) {
1751             g_string_append(str, " (health is RED)");
1752         } else if (health == 0) {
1753             g_string_append(str, " (health is YELLOW)");
1754         }
1755         if (pcmk_is_set(show_opts, pcmk_show_feature_set)) {
1756             const char *feature_set = get_node_feature_set(node);
1757             if (feature_set != NULL) {
1758                 pcmk__g_strcat(str, ", feature set ", feature_set, NULL);
1759             }
1760         }
1761 
1762         /* If we're grouping by node, print its resources */
1763         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
1764             if (pcmk_is_set(show_opts, pcmk_show_brief)) {
1765                 GList *rscs = pe__filter_rsc_list(node->details->running_rsc, only_rsc);
1766 
1767                 if (rscs != NULL) {
1768                     uint32_t new_show_opts = (show_opts | pcmk_show_rsc_only) & ~pcmk_show_inactive_rscs;
1769                     out->begin_list(out, NULL, NULL, "%s", str->str);
1770                     out->begin_list(out, NULL, NULL, "Resources");
1771 
1772                     pe__rscs_brief_output(out, rscs, new_show_opts);
1773 
1774                     out->end_list(out);
1775                     out->end_list(out);
1776 
1777                     g_list_free(rscs);
1778                 }
1779 
1780             } else {
1781                 GList *gIter2 = NULL;
1782 
1783                 out->begin_list(out, NULL, NULL, "%s", str->str);
1784                 out->begin_list(out, NULL, NULL, "Resources");
1785 
1786                 for (gIter2 = node->details->running_rsc; gIter2 != NULL; gIter2 = gIter2->next) {
1787                     pe_resource_t *rsc = (pe_resource_t *) gIter2->data;
1788 
1789                     show_opts |= pcmk_show_rsc_only;
1790                     out->message(out, crm_map_element_name(rsc->xml), show_opts,
1791                                  rsc, only_node, only_rsc);
1792                 }
1793 
1794                 out->end_list(out);
1795                 out->end_list(out);
1796             }
1797         } else {
1798             out->list_item(out, NULL, "%s", str->str);
1799         }
1800 
1801         g_string_free(str, TRUE);
1802         free(node_name);
1803     } else {
1804         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
1805         out->begin_list(out, NULL, NULL, "Node: %s", node_name);
1806         free(node_name);
1807     }
1808 
1809     return pcmk_rc_ok;
1810 }
1811 
1812 PCMK__OUTPUT_ARGS("node", "pe_node_t *", "uint32_t", "bool", "GList *", "GList *")
     /* [previous][next][first][last][top][bottom][index][help] */
1813 static int
1814 node_xml(pcmk__output_t *out, va_list args) {
1815     pe_node_t *node = va_arg(args, pe_node_t *);
1816     uint32_t show_opts G_GNUC_UNUSED = va_arg(args, uint32_t);
1817     bool full = va_arg(args, int);
1818     GList *only_node = va_arg(args, GList *);
1819     GList *only_rsc = va_arg(args, GList *);
1820 
1821     if (full) {
1822         const char *node_type = "unknown";
1823         char *length_s = pcmk__itoa(g_list_length(node->details->running_rsc));
1824         int health = pe__node_health(node);
1825         const char *health_s = NULL;
1826         const char *feature_set;
1827 
1828         switch (node->details->type) {
1829             case node_member:
1830                 node_type = "member";
1831                 break;
1832             case node_remote:
1833                 node_type = "remote";
1834                 break;
1835             case node_ping:
1836                 node_type = "ping";
1837                 break;
1838         }
1839 
1840         if (health < 0) {
1841             health_s = "red";
1842         } else if (health == 0) {
1843             health_s = "yellow";
1844         } else {
1845             health_s = "green";
1846         }
1847 
1848         feature_set = get_node_feature_set(node);
1849 
1850         pe__name_and_nvpairs_xml(out, true, "node", 15,
1851                                  "name", node->details->uname,
1852                                  "id", node->details->id,
1853                                  "online", pcmk__btoa(node->details->online),
1854                                  "standby", pcmk__btoa(node->details->standby),
1855                                  "standby_onfail", pcmk__btoa(node->details->standby_onfail),
1856                                  "maintenance", pcmk__btoa(node->details->maintenance),
1857                                  "pending", pcmk__btoa(node->details->pending),
1858                                  "unclean", pcmk__btoa(node->details->unclean),
1859                                  "health", health_s,
1860                                  "feature_set", feature_set,
1861                                  "shutdown", pcmk__btoa(node->details->shutdown),
1862                                  "expected_up", pcmk__btoa(node->details->expected_up),
1863                                  "is_dc", pcmk__btoa(node->details->is_dc),
1864                                  "resources_running", length_s,
1865                                  "type", node_type);
1866 
1867         if (pe__is_guest_node(node)) {
1868             xmlNodePtr xml_node = pcmk__output_xml_peek_parent(out);
1869             crm_xml_add(xml_node, "id_as_resource", node->details->remote_rsc->container->id);
1870         }
1871 
1872         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
1873             GList *lpc = NULL;
1874 
1875             for (lpc = node->details->running_rsc; lpc != NULL; lpc = lpc->next) {
1876                 pe_resource_t *rsc = (pe_resource_t *) lpc->data;
1877 
1878                 show_opts |= pcmk_show_rsc_only;
1879                 out->message(out, crm_map_element_name(rsc->xml), show_opts,
1880                              rsc, only_node, only_rsc);
1881             }
1882         }
1883 
1884         free(length_s);
1885 
1886         out->end_list(out);
1887     } else {
1888         pcmk__output_xml_create_parent(out, "node",
1889                                        "name", node->details->uname,
1890                                        NULL);
1891     }
1892 
1893     return pcmk_rc_ok;
1894 }
1895 
1896 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
1897 static int
1898 node_attribute_text(pcmk__output_t *out, va_list args) {
1899     const char *name = va_arg(args, const char *);
1900     const char *value = va_arg(args, const char *);
1901     bool add_extra = va_arg(args, int);
1902     int expected_score = va_arg(args, int);
1903 
1904     if (add_extra) {
1905         int v;
1906 
1907         if (value == NULL) {
1908             v = 0;
1909         } else {
1910             pcmk__scan_min_int(value, &v, INT_MIN);
1911         }
1912         if (v <= 0) {
1913             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is lost", name, value);
1914         } else if (v < expected_score) {
1915             out->list_item(out, NULL, "%-32s\t: %-10s\t: Connectivity is degraded (Expected=%d)", name, value, expected_score);
1916         } else {
1917             out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
1918         }
1919     } else {
1920         out->list_item(out, NULL, "%-32s\t: %-10s", name, value);
1921     }
1922 
1923     return pcmk_rc_ok;
1924 }
1925 
1926 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
1927 static int
1928 node_attribute_html(pcmk__output_t *out, va_list args) {
1929     const char *name = va_arg(args, const char *);
1930     const char *value = va_arg(args, const char *);
1931     bool add_extra = va_arg(args, int);
1932     int expected_score = va_arg(args, int);
1933 
1934     if (add_extra) {
1935         int v;
1936         char *s = crm_strdup_printf("%s: %s", name, value);
1937         xmlNodePtr item_node = pcmk__output_create_xml_node(out, "li", NULL);
1938 
1939         if (value == NULL) {
1940             v = 0;
1941         } else {
1942             pcmk__scan_min_int(value, &v, INT_MIN);
1943         }
1944 
1945         pcmk_create_html_node(item_node, "span", NULL, NULL, s);
1946         free(s);
1947 
1948         if (v <= 0) {
1949             pcmk_create_html_node(item_node, "span", NULL, "bold", "(connectivity is lost)");
1950         } else if (v < expected_score) {
1951             char *buf = crm_strdup_printf("(connectivity is degraded -- expected %d", expected_score);
1952             pcmk_create_html_node(item_node, "span", NULL, "bold", buf);
1953             free(buf);
1954         }
1955     } else {
1956         out->list_item(out, NULL, "%s: %s", name, value);
1957     }
1958 
1959     return pcmk_rc_ok;
1960 }
1961 
1962 PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr")
     /* [previous][next][first][last][top][bottom][index][help] */
1963 static int
1964 node_and_op(pcmk__output_t *out, va_list args) {
1965     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
1966     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
1967 
1968     pe_resource_t *rsc = NULL;
1969     gchar *node_str = NULL;
1970     char *last_change_str = NULL;
1971 
1972     const char *op_rsc = crm_element_value(xml_op, "resource");
1973     int status;
1974     time_t last_change = 0;
1975 
1976     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
1977                        &status, PCMK_EXEC_UNKNOWN);
1978 
1979     rsc = pe_find_resource(data_set->resources, op_rsc);
1980 
1981     if (rsc) {
1982         const pe_node_t *node = pe__current_node(rsc);
1983         const char *target_role = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE);
1984         uint32_t show_opts = pcmk_show_rsc_only | pcmk_show_pending;
1985 
1986         if (node == NULL) {
1987             node = rsc->pending_node;
1988         }
1989 
1990         node_str = pcmk__native_output_string(rsc, rsc_printable_id(rsc), node,
1991                                               show_opts, target_role, false);
1992     } else {
1993         node_str = crm_strdup_printf("Unknown resource %s", op_rsc);
1994     }
1995 
1996     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
1997                                 &last_change) == pcmk_ok) {
1998         last_change_str = crm_strdup_printf(", %s='%s', exec=%sms",
1999                                             XML_RSC_OP_LAST_CHANGE,
2000                                             pcmk__trim(ctime(&last_change)),
2001                                             crm_element_value(xml_op, XML_RSC_OP_T_EXEC));
2002     }
2003 
2004     out->list_item(out, NULL, "%s: %s (node=%s, call=%s, rc=%s%s): %s",
2005                    node_str, pe__xe_history_key(xml_op),
2006                    crm_element_value(xml_op, XML_ATTR_UNAME),
2007                    crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
2008                    crm_element_value(xml_op, XML_LRM_ATTR_RC),
2009                    last_change_str ? last_change_str : "",
2010                    pcmk_exec_status_str(status));
2011 
2012     g_free(node_str);
2013     free(last_change_str);
2014     return pcmk_rc_ok;
2015 }
2016 
2017 PCMK__OUTPUT_ARGS("node-and-op", "pe_working_set_t *", "xmlNodePtr")
     /* [previous][next][first][last][top][bottom][index][help] */
2018 static int
2019 node_and_op_xml(pcmk__output_t *out, va_list args) {
2020     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
2021     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2022 
2023     pe_resource_t *rsc = NULL;
2024     const char *op_rsc = crm_element_value(xml_op, "resource");
2025     int status;
2026     time_t last_change = 0;
2027     xmlNode *node = NULL;
2028 
2029     pcmk__scan_min_int(crm_element_value(xml_op, XML_LRM_ATTR_OPSTATUS),
2030                        &status, PCMK_EXEC_UNKNOWN);
2031     node = pcmk__output_create_xml_node(out, "operation",
2032                                         "op", pe__xe_history_key(xml_op),
2033                                         "node", crm_element_value(xml_op, XML_ATTR_UNAME),
2034                                         "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
2035                                         "rc", crm_element_value(xml_op, XML_LRM_ATTR_RC),
2036                                         "status", pcmk_exec_status_str(status),
2037                                         NULL);
2038 
2039     rsc = pe_find_resource(data_set->resources, op_rsc);
2040 
2041     if (rsc) {
2042         const char *class = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
2043         const char *kind = crm_element_value(rsc->xml, XML_ATTR_TYPE);
2044         char *agent_tuple = NULL;
2045 
2046         agent_tuple = crm_strdup_printf("%s:%s:%s", class,
2047                                         pcmk_is_set(pcmk_get_ra_caps(class), pcmk_ra_cap_provider) ? crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER) : "",
2048                                         kind);
2049 
2050         pcmk__xe_set_props(node, "rsc", rsc_printable_id(rsc),
2051                            "agent", agent_tuple,
2052                            NULL);
2053         free(agent_tuple);
2054     }
2055 
2056     if (crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
2057                                 &last_change) == pcmk_ok) {
2058         pcmk__xe_set_props(node, XML_RSC_OP_LAST_CHANGE,
2059                            pcmk__trim(ctime(&last_change)),
2060                            XML_RSC_OP_T_EXEC, crm_element_value(xml_op, XML_RSC_OP_T_EXEC),
2061                            NULL);
2062     }
2063 
2064     return pcmk_rc_ok;
2065 }
2066 
2067 PCMK__OUTPUT_ARGS("node-attribute", "const char *", "const char *", "bool", "int")
     /* [previous][next][first][last][top][bottom][index][help] */
2068 static int
2069 node_attribute_xml(pcmk__output_t *out, va_list args) {
2070     const char *name = va_arg(args, const char *);
2071     const char *value = va_arg(args, const char *);
2072     bool add_extra = va_arg(args, int);
2073     int expected_score = va_arg(args, int);
2074 
2075     xmlNodePtr node = pcmk__output_create_xml_node(out, "attribute",
2076                                                    "name", name,
2077                                                    "value", value,
2078                                                    NULL);
2079 
2080     if (add_extra) {
2081         char *buf = pcmk__itoa(expected_score);
2082         crm_xml_add(node, "expected", buf);
2083         free(buf);
2084     }
2085 
2086     return pcmk_rc_ok;
2087 }
2088 
2089 PCMK__OUTPUT_ARGS("node-attribute-list", "pe_working_set_t *", "uint32_t",
     /* [previous][next][first][last][top][bottom][index][help] */
2090                   "bool", "GList *", "GList *")
2091 static int
2092 node_attribute_list(pcmk__output_t *out, va_list args) {
2093     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
2094     uint32_t show_opts = va_arg(args, uint32_t);
2095     bool print_spacer = va_arg(args, int);
2096     GList *only_node = va_arg(args, GList *);
2097     GList *only_rsc = va_arg(args, GList *);
2098 
2099     int rc = pcmk_rc_no_output;
2100 
2101     /* Display each node's attributes */
2102     for (GList *gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
2103         pe_node_t *node = gIter->data;
2104 
2105         GList *attr_list = NULL;
2106         GHashTableIter iter;
2107         gpointer key;
2108 
2109         if (!node || !node->details || !node->details->online) {
2110             continue;
2111         }
2112 
2113         g_hash_table_iter_init(&iter, node->details->attrs);
2114         while (g_hash_table_iter_next (&iter, &key, NULL)) {
2115             attr_list = filter_attr_list(attr_list, key);
2116         }
2117 
2118         if (attr_list == NULL) {
2119             continue;
2120         }
2121 
2122         if (!pcmk__str_in_list(node->details->uname, only_node, pcmk__str_star_matches|pcmk__str_casei)) {
2123             g_list_free(attr_list);
2124             continue;
2125         }
2126 
2127         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node Attributes");
2128 
2129         out->message(out, "node", node, show_opts, false, only_node, only_rsc);
2130 
2131         for (GList *aIter = attr_list; aIter != NULL; aIter = aIter->next) {
2132             const char *name = aIter->data;
2133             const char *value = NULL;
2134             int expected_score = 0;
2135             bool add_extra = false;
2136 
2137             value = pe_node_attribute_raw(node, name);
2138 
2139             add_extra = add_extra_info(node, node->details->running_rsc,
2140                                        data_set, name, &expected_score);
2141 
2142             /* Print attribute name and value */
2143             out->message(out, "node-attribute", name, value, add_extra,
2144                          expected_score);
2145         }
2146 
2147         g_list_free(attr_list);
2148         out->end_list(out);
2149     }
2150 
2151     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2152     return rc;
2153 }
2154 
2155 PCMK__OUTPUT_ARGS("node-capacity", "const pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2156 static int
2157 node_capacity(pcmk__output_t *out, va_list args)
2158 {
2159     const pe_node_t *node = va_arg(args, pe_node_t *);
2160     const char *comment = va_arg(args, const char *);
2161 
2162     char *dump_text = crm_strdup_printf("%s: %s capacity:",
2163                                         comment, pe__node_name(node));
2164 
2165     g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text);
2166     out->list_item(out, NULL, "%s", dump_text);
2167     free(dump_text);
2168 
2169     return pcmk_rc_ok;
2170 }
2171 
2172 PCMK__OUTPUT_ARGS("node-capacity", "const pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2173 static int
2174 node_capacity_xml(pcmk__output_t *out, va_list args)
2175 {
2176     const pe_node_t *node = va_arg(args, pe_node_t *);
2177     const char *comment = va_arg(args, const char *);
2178 
2179     xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "capacity",
2180                                                        "node", node->details->uname,
2181                                                        "comment", comment,
2182                                                        NULL);
2183     g_hash_table_foreach(node->details->utilization, add_dump_node, xml_node);
2184 
2185     return pcmk_rc_ok;
2186 }
2187 
2188 PCMK__OUTPUT_ARGS("node-history-list", "pe_working_set_t *", "pe_node_t *", "xmlNodePtr",
     /* [previous][next][first][last][top][bottom][index][help] */
2189                   "GList *", "GList *", "uint32_t", "uint32_t")
2190 static int
2191 node_history_list(pcmk__output_t *out, va_list args) {
2192     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
2193     pe_node_t *node = va_arg(args, pe_node_t *);
2194     xmlNode *node_state = va_arg(args, xmlNode *);
2195     GList *only_node = va_arg(args, GList *);
2196     GList *only_rsc = va_arg(args, GList *);
2197     uint32_t section_opts = va_arg(args, uint32_t);
2198     uint32_t show_opts = va_arg(args, uint32_t);
2199 
2200     xmlNode *lrm_rsc = NULL;
2201     xmlNode *rsc_entry = NULL;
2202     int rc = pcmk_rc_no_output;
2203 
2204     lrm_rsc = find_xml_node(node_state, XML_CIB_TAG_LRM, FALSE);
2205     lrm_rsc = find_xml_node(lrm_rsc, XML_LRM_TAG_RESOURCES, FALSE);
2206 
2207     /* Print history of each of the node's resources */
2208     for (rsc_entry = first_named_child(lrm_rsc, XML_LRM_TAG_RESOURCE);
2209          rsc_entry != NULL; rsc_entry = crm_next_same_xml(rsc_entry)) {
2210         const char *rsc_id = crm_element_value(rsc_entry, XML_ATTR_ID);
2211         pe_resource_t *rsc = pe_find_resource(data_set->resources, rsc_id);
2212         const pe_resource_t *parent = pe__const_top_resource(rsc, false);
2213 
2214         /* We can't use is_filtered here to filter group resources.  For is_filtered,
2215          * we have to decide whether to check the parent or not.  If we check the
2216          * parent, all elements of a group will always be printed because that's how
2217          * is_filtered works for groups.  If we do not check the parent, sometimes
2218          * this will filter everything out.
2219          *
2220          * For other resource types, is_filtered is okay.
2221          */
2222         if (parent->variant == pe_group) {
2223             if (!pcmk__str_in_list(rsc_printable_id(rsc), only_rsc,
2224                                    pcmk__str_star_matches)
2225                 && !pcmk__str_in_list(rsc_printable_id(parent), only_rsc,
2226                                       pcmk__str_star_matches)) {
2227                 continue;
2228             }
2229         } else {
2230             if (rsc->fns->is_filtered(rsc, only_rsc, TRUE)) {
2231                 continue;
2232             }
2233         }
2234 
2235         if (!pcmk_is_set(section_opts, pcmk_section_operations)) {
2236             time_t last_failure = 0;
2237             int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
2238                                              NULL);
2239 
2240             if (failcount <= 0) {
2241                 continue;
2242             }
2243 
2244             if (rc == pcmk_rc_no_output) {
2245                 rc = pcmk_rc_ok;
2246                 out->message(out, "node", node, show_opts, false, only_node,
2247                              only_rsc);
2248             }
2249 
2250             out->message(out, "resource-history", rsc, rsc_id, false,
2251                          failcount, last_failure, false);
2252         } else {
2253             GList *op_list = get_operation_list(rsc_entry);
2254             pe_resource_t *rsc = pe_find_resource(data_set->resources,
2255                                                   crm_element_value(rsc_entry, XML_ATTR_ID));
2256 
2257             if (op_list == NULL) {
2258                 continue;
2259             }
2260 
2261             if (rc == pcmk_rc_no_output) {
2262                 rc = pcmk_rc_ok;
2263                 out->message(out, "node", node, show_opts, false, only_node,
2264                              only_rsc);
2265             }
2266 
2267             out->message(out, "resource-operation-list", data_set, rsc, node,
2268                          op_list, show_opts);
2269         }
2270     }
2271 
2272     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2273     return rc;
2274 }
2275 
2276 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2277 static int
2278 node_list_html(pcmk__output_t *out, va_list args) {
2279     GList *nodes = va_arg(args, GList *);
2280     GList *only_node = va_arg(args, GList *);
2281     GList *only_rsc = va_arg(args, GList *);
2282     uint32_t show_opts = va_arg(args, uint32_t);
2283     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
2284 
2285     int rc = pcmk_rc_no_output;
2286 
2287     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2288         pe_node_t *node = (pe_node_t *) gIter->data;
2289 
2290         if (!pcmk__str_in_list(node->details->uname, only_node,
2291                                pcmk__str_star_matches|pcmk__str_casei)) {
2292             continue;
2293         }
2294 
2295         PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Node List");
2296 
2297         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2298     }
2299 
2300     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2301     return rc;
2302 }
2303 
2304 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2305 static int
2306 node_list_text(pcmk__output_t *out, va_list args) {
2307     GList *nodes = va_arg(args, GList *);
2308     GList *only_node = va_arg(args, GList *);
2309     GList *only_rsc = va_arg(args, GList *);
2310     uint32_t show_opts = va_arg(args, uint32_t);
2311     bool print_spacer = va_arg(args, int);
2312 
2313     /* space-separated lists of node names */
2314     GString *online_nodes = NULL;
2315     GString *online_remote_nodes = NULL;
2316     GString *online_guest_nodes = NULL;
2317     GString *offline_nodes = NULL;
2318     GString *offline_remote_nodes = NULL;
2319 
2320     int rc = pcmk_rc_no_output;
2321 
2322     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2323         pe_node_t *node = (pe_node_t *) gIter->data;
2324         char *node_name = pe__node_display_name(node, pcmk_is_set(show_opts, pcmk_show_node_id));
2325 
2326         if (!pcmk__str_in_list(node->details->uname, only_node,
2327                                pcmk__str_star_matches|pcmk__str_casei)) {
2328             free(node_name);
2329             continue;
2330         }
2331 
2332         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc, "Node List");
2333 
2334         // Determine whether to display node individually or in a list
2335         if (node->details->unclean || node->details->pending
2336             || (node->details->standby_onfail && node->details->online)
2337             || node->details->standby || node->details->maintenance
2338             || pcmk_is_set(show_opts, pcmk_show_rscs_by_node)
2339             || pcmk_is_set(show_opts, pcmk_show_feature_set)
2340             || (pe__node_health(node) <= 0)) {
2341             // Display node individually
2342 
2343         } else if (node->details->online) {
2344             // Display online node in a list
2345             if (pe__is_guest_node(node)) {
2346                 pcmk__add_word(&online_guest_nodes, 1024, node_name);
2347 
2348             } else if (pe__is_remote_node(node)) {
2349                 pcmk__add_word(&online_remote_nodes, 1024, node_name);
2350 
2351             } else {
2352                 pcmk__add_word(&online_nodes, 1024, node_name);
2353             }
2354             free(node_name);
2355             continue;
2356 
2357         } else {
2358             // Display offline node in a list
2359             if (pe__is_remote_node(node)) {
2360                 pcmk__add_word(&offline_remote_nodes, 1024, node_name);
2361 
2362             } else if (pe__is_guest_node(node)) {
2363                 /* ignore offline guest nodes */
2364 
2365             } else {
2366                 pcmk__add_word(&offline_nodes, 1024, node_name);
2367             }
2368             free(node_name);
2369             continue;
2370         }
2371 
2372         /* If we get here, node is in bad state, or we're grouping by node */
2373         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2374         free(node_name);
2375     }
2376 
2377     /* If we're not grouping by node, summarize nodes by status */
2378     if (online_nodes != NULL) {
2379         out->list_item(out, "Online", "[ %s ]",
2380                        (const char *) online_nodes->str);
2381         g_string_free(online_nodes, TRUE);
2382     }
2383     if (offline_nodes != NULL) {
2384         out->list_item(out, "OFFLINE", "[ %s ]",
2385                        (const char *) offline_nodes->str);
2386         g_string_free(offline_nodes, TRUE);
2387     }
2388     if (online_remote_nodes) {
2389         out->list_item(out, "RemoteOnline", "[ %s ]",
2390                        (const char *) online_remote_nodes->str);
2391         g_string_free(online_remote_nodes, TRUE);
2392     }
2393     if (offline_remote_nodes) {
2394         out->list_item(out, "RemoteOFFLINE", "[ %s ]",
2395                        (const char *) offline_remote_nodes->str);
2396         g_string_free(offline_remote_nodes, TRUE);
2397     }
2398     if (online_guest_nodes != NULL) {
2399         out->list_item(out, "GuestOnline", "[ %s ]",
2400                        (const char *) online_guest_nodes->str);
2401         g_string_free(online_guest_nodes, TRUE);
2402     }
2403 
2404     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2405     return rc;
2406 }
2407 
2408 PCMK__OUTPUT_ARGS("node-list", "GList *", "GList *", "GList *", "uint32_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2409 static int
2410 node_list_xml(pcmk__output_t *out, va_list args) {
2411     GList *nodes = va_arg(args, GList *);
2412     GList *only_node = va_arg(args, GList *);
2413     GList *only_rsc = va_arg(args, GList *);
2414     uint32_t show_opts = va_arg(args, uint32_t);
2415     bool print_spacer G_GNUC_UNUSED = va_arg(args, int);
2416 
2417     out->begin_list(out, NULL, NULL, "nodes");
2418     for (GList *gIter = nodes; gIter != NULL; gIter = gIter->next) {
2419         pe_node_t *node = (pe_node_t *) gIter->data;
2420 
2421         if (!pcmk__str_in_list(node->details->uname, only_node,
2422                                pcmk__str_star_matches|pcmk__str_casei)) {
2423             continue;
2424         }
2425 
2426         out->message(out, "node", node, show_opts, true, only_node, only_rsc);
2427     }
2428     out->end_list(out);
2429 
2430     return pcmk_rc_ok;
2431 }
2432 
2433 PCMK__OUTPUT_ARGS("node-summary", "pe_working_set_t *", "GList *", "GList *",
     /* [previous][next][first][last][top][bottom][index][help] */
2434                   "uint32_t", "uint32_t", "bool")
2435 static int
2436 node_summary(pcmk__output_t *out, va_list args) {
2437     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
2438     GList *only_node = va_arg(args, GList *);
2439     GList *only_rsc = va_arg(args, GList *);
2440     uint32_t section_opts = va_arg(args, uint32_t);
2441     uint32_t show_opts = va_arg(args, uint32_t);
2442     bool print_spacer = va_arg(args, int);
2443 
2444     xmlNode *node_state = NULL;
2445     xmlNode *cib_status = pcmk_find_cib_element(data_set->input,
2446                                                 XML_CIB_TAG_STATUS);
2447     int rc = pcmk_rc_no_output;
2448 
2449     if (xmlChildElementCount(cib_status) == 0) {
2450         return rc;
2451     }
2452 
2453     for (node_state = first_named_child(cib_status, XML_CIB_TAG_STATE);
2454          node_state != NULL; node_state = crm_next_same_xml(node_state)) {
2455         pe_node_t *node = pe_find_node_id(data_set->nodes, ID(node_state));
2456 
2457         if (!node || !node->details || !node->details->online) {
2458             continue;
2459         }
2460 
2461         if (!pcmk__str_in_list(node->details->uname, only_node,
2462                                pcmk__str_star_matches|pcmk__str_casei)) {
2463             continue;
2464         }
2465 
2466         PCMK__OUTPUT_LIST_HEADER(out, print_spacer, rc,
2467                                  pcmk_is_set(section_opts, pcmk_section_operations) ? "Operations" : "Migration Summary");
2468 
2469         out->message(out, "node-history-list", data_set, node, node_state,
2470                      only_node, only_rsc, section_opts, show_opts);
2471     }
2472 
2473     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2474     return rc;
2475 }
2476 
2477 PCMK__OUTPUT_ARGS("node-weight", "const pe_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2478                   "const char *", "const char *")
2479 static int
2480 node_weight(pcmk__output_t *out, va_list args)
2481 {
2482     const pe_resource_t *rsc = va_arg(args, const pe_resource_t *);
2483     const char *prefix = va_arg(args, const char *);
2484     const char *uname = va_arg(args, const char *);
2485     const char *score = va_arg(args, const char *);
2486 
2487     if (rsc) {
2488         out->list_item(out, NULL, "%s: %s allocation score on %s: %s",
2489                        prefix, rsc->id, uname, score);
2490     } else {
2491         out->list_item(out, NULL, "%s: %s = %s", prefix, uname, score);
2492     }
2493 
2494     return pcmk_rc_ok;
2495 }
2496 
2497 PCMK__OUTPUT_ARGS("node-weight", "const pe_resource_t *", "const char *",
     /* [previous][next][first][last][top][bottom][index][help] */
2498                   "const char *", "const char *")
2499 static int
2500 node_weight_xml(pcmk__output_t *out, va_list args)
2501 {
2502     const pe_resource_t *rsc = va_arg(args, const pe_resource_t *);
2503     const char *prefix = va_arg(args, const char *);
2504     const char *uname = va_arg(args, const char *);
2505     const char *score = va_arg(args, const char *);
2506 
2507     xmlNodePtr node = pcmk__output_create_xml_node(out, "node_weight",
2508                                                    "function", prefix,
2509                                                    "node", uname,
2510                                                    "score", score,
2511                                                    NULL);
2512 
2513     if (rsc) {
2514         crm_xml_add(node, "id", rsc->id);
2515     }
2516 
2517     return pcmk_rc_ok;
2518 }
2519 
2520 PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
2521 static int
2522 op_history_text(pcmk__output_t *out, va_list args) {
2523     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2524     const char *task = va_arg(args, const char *);
2525     const char *interval_ms_s = va_arg(args, const char *);
2526     int rc = va_arg(args, int);
2527     uint32_t show_opts = va_arg(args, uint32_t);
2528 
2529     char *buf = op_history_string(xml_op, task, interval_ms_s, rc,
2530                                   pcmk_is_set(show_opts, pcmk_show_timing));
2531 
2532     out->list_item(out, NULL, "%s", buf);
2533 
2534     free(buf);
2535     return pcmk_rc_ok;
2536 }
2537 
2538 PCMK__OUTPUT_ARGS("op-history", "xmlNodePtr", "const char *", "const char *", "int", "uint32_t")
     /* [previous][next][first][last][top][bottom][index][help] */
2539 static int
2540 op_history_xml(pcmk__output_t *out, va_list args) {
2541     xmlNodePtr xml_op = va_arg(args, xmlNodePtr);
2542     const char *task = va_arg(args, const char *);
2543     const char *interval_ms_s = va_arg(args, const char *);
2544     int rc = va_arg(args, int);
2545     uint32_t show_opts = va_arg(args, uint32_t);
2546 
2547     char *rc_s = pcmk__itoa(rc);
2548     xmlNodePtr node = pcmk__output_create_xml_node(out, "operation_history",
2549                                                    "call", crm_element_value(xml_op, XML_LRM_ATTR_CALLID),
2550                                                    "task", task,
2551                                                    "rc", rc_s,
2552                                                    "rc_text", services_ocf_exitcode_str(rc),
2553                                                    NULL);
2554     free(rc_s);
2555 
2556     if (interval_ms_s && !pcmk__str_eq(interval_ms_s, "0", pcmk__str_casei)) {
2557         char *s = crm_strdup_printf("%sms", interval_ms_s);
2558         crm_xml_add(node, "interval", s);
2559         free(s);
2560     }
2561 
2562     if (pcmk_is_set(show_opts, pcmk_show_timing)) {
2563         const char *value = NULL;
2564         time_t epoch = 0;
2565 
2566         if ((crm_element_value_epoch(xml_op, XML_RSC_OP_LAST_CHANGE,
2567                                      &epoch) == pcmk_ok) && (epoch > 0)) {
2568             char *s = pcmk__epoch2str(&epoch, 0);
2569             crm_xml_add(node, XML_RSC_OP_LAST_CHANGE, s);
2570             free(s);
2571         }
2572 
2573         value = crm_element_value(xml_op, XML_RSC_OP_T_EXEC);
2574         if (value) {
2575             char *s = crm_strdup_printf("%sms", value);
2576             crm_xml_add(node, XML_RSC_OP_T_EXEC, s);
2577             free(s);
2578         }
2579         value = crm_element_value(xml_op, XML_RSC_OP_T_QUEUE);
2580         if (value) {
2581             char *s = crm_strdup_printf("%sms", value);
2582             crm_xml_add(node, XML_RSC_OP_T_QUEUE, s);
2583             free(s);
2584         }
2585     }
2586 
2587     return pcmk_rc_ok;
2588 }
2589 
2590 PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2591 static int
2592 promotion_score(pcmk__output_t *out, va_list args)
2593 {
2594     pe_resource_t *child_rsc = va_arg(args, pe_resource_t *);
2595     pe_node_t *chosen = va_arg(args, pe_node_t *);
2596     const char *score = va_arg(args, const char *);
2597 
2598     out->list_item(out, NULL, "%s promotion score on %s: %s",
2599                    child_rsc->id,
2600                    chosen? chosen->details->uname : "none",
2601                    score);
2602     return pcmk_rc_ok;
2603 }
2604 
2605 PCMK__OUTPUT_ARGS("promotion-score", "pe_resource_t *", "pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2606 static int
2607 promotion_score_xml(pcmk__output_t *out, va_list args)
2608 {
2609     pe_resource_t *child_rsc = va_arg(args, pe_resource_t *);
2610     pe_node_t *chosen = va_arg(args, pe_node_t *);
2611     const char *score = va_arg(args, const char *);
2612 
2613     xmlNodePtr node = pcmk__output_create_xml_node(out, "promotion_score",
2614                                                    "id", child_rsc->id,
2615                                                    "score", score,
2616                                                    NULL);
2617 
2618     if (chosen) {
2619         crm_xml_add(node, "node", chosen->details->uname);
2620     }
2621 
2622     return pcmk_rc_ok;
2623 }
2624 
2625 PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2626 static int
2627 resource_config(pcmk__output_t *out, va_list args) {
2628     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2629     bool raw = va_arg(args, int);
2630 
2631     char *rsc_xml = formatted_xml_buf(rsc, raw);
2632 
2633     out->output_xml(out, "xml", rsc_xml);
2634 
2635     free(rsc_xml);
2636     return pcmk_rc_ok;
2637 }
2638 
2639 PCMK__OUTPUT_ARGS("resource-config", "pe_resource_t *", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2640 static int
2641 resource_config_text(pcmk__output_t *out, va_list args) {
2642     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2643     bool raw = va_arg(args, int);
2644 
2645     char *rsc_xml = formatted_xml_buf(rsc, raw);
2646 
2647     pcmk__formatted_printf(out, "Resource XML:\n");
2648     out->output_xml(out, "xml", rsc_xml);
2649 
2650     free(rsc_xml);
2651     return pcmk_rc_ok;
2652 }
2653 
2654 PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2655 static int
2656 resource_history_text(pcmk__output_t *out, va_list args) {
2657     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2658     const char *rsc_id = va_arg(args, const char *);
2659     bool all = va_arg(args, int);
2660     int failcount = va_arg(args, int);
2661     time_t last_failure = va_arg(args, time_t);
2662     bool as_header = va_arg(args, int);
2663 
2664     char *buf = resource_history_string(rsc, rsc_id, all, failcount, last_failure);
2665 
2666     if (as_header) {
2667         out->begin_list(out, NULL, NULL, "%s", buf);
2668     } else {
2669         out->list_item(out, NULL, "%s", buf);
2670     }
2671 
2672     free(buf);
2673     return pcmk_rc_ok;
2674 }
2675 
2676 PCMK__OUTPUT_ARGS("resource-history", "pe_resource_t *", "const char *", "bool", "int", "time_t", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
2677 static int
2678 resource_history_xml(pcmk__output_t *out, va_list args) {
2679     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2680     const char *rsc_id = va_arg(args, const char *);
2681     bool all = va_arg(args, int);
2682     int failcount = va_arg(args, int);
2683     time_t last_failure = va_arg(args, time_t);
2684     bool as_header = va_arg(args, int);
2685 
2686     xmlNodePtr node = pcmk__output_xml_create_parent(out, "resource_history",
2687                                                      "id", rsc_id,
2688                                                      NULL);
2689 
2690     if (rsc == NULL) {
2691         pcmk__xe_set_bool_attr(node, "orphan", true);
2692     } else if (all || failcount || last_failure > 0) {
2693         char *migration_s = pcmk__itoa(rsc->migration_threshold);
2694 
2695         pcmk__xe_set_props(node, "orphan", "false",
2696                            "migration-threshold", migration_s,
2697                            NULL);
2698         free(migration_s);
2699 
2700         if (failcount > 0) {
2701             char *s = pcmk__itoa(failcount);
2702 
2703             crm_xml_add(node, PCMK__FAIL_COUNT_PREFIX, s);
2704             free(s);
2705         }
2706 
2707         if (last_failure > 0) {
2708             char *s = pcmk__epoch2str(&last_failure, 0);
2709 
2710             crm_xml_add(node, PCMK__LAST_FAILURE_PREFIX, s);
2711             free(s);
2712         }
2713     }
2714 
2715     if (!as_header) {
2716         pcmk__output_xml_pop_parent(out);
2717     }
2718 
2719     return pcmk_rc_ok;
2720 }
2721 
2722 static void
2723 print_resource_header(pcmk__output_t *out, uint32_t show_opts)
     /* [previous][next][first][last][top][bottom][index][help] */
2724 {
2725     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2726         /* Active resources have already been printed by node */
2727         out->begin_list(out, NULL, NULL, "Inactive Resources");
2728     } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
2729         out->begin_list(out, NULL, NULL, "Full List of Resources");
2730     } else {
2731         out->begin_list(out, NULL, NULL, "Active Resources");
2732     }
2733 }
2734 
2735 
2736 PCMK__OUTPUT_ARGS("resource-list", "pe_working_set_t *", "uint32_t", "bool",
     /* [previous][next][first][last][top][bottom][index][help] */
2737                   "GList *", "GList *", "bool")
2738 static int
2739 resource_list(pcmk__output_t *out, va_list args)
2740 {
2741     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
2742     uint32_t show_opts = va_arg(args, uint32_t);
2743     bool print_summary = va_arg(args, int);
2744     GList *only_node = va_arg(args, GList *);
2745     GList *only_rsc = va_arg(args, GList *);
2746     bool print_spacer = va_arg(args, int);
2747 
2748     GList *rsc_iter;
2749     int rc = pcmk_rc_no_output;
2750     bool printed_header = false;
2751 
2752     /* If we already showed active resources by node, and
2753      * we're not showing inactive resources, we have nothing to do
2754      */
2755     if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node) &&
2756         !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
2757         return rc;
2758     }
2759 
2760     /* If we haven't already printed resources grouped by node,
2761      * and brief output was requested, print resource summary */
2762     if (pcmk_is_set(show_opts, pcmk_show_brief) && !pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2763         GList *rscs = pe__filter_rsc_list(data_set->resources, only_rsc);
2764 
2765         PCMK__OUTPUT_SPACER_IF(out, print_spacer);
2766         print_resource_header(out, show_opts);
2767         printed_header = true;
2768 
2769         rc = pe__rscs_brief_output(out, rscs, show_opts);
2770         g_list_free(rscs);
2771     }
2772 
2773     /* For each resource, display it if appropriate */
2774     for (rsc_iter = data_set->resources; rsc_iter != NULL; rsc_iter = rsc_iter->next) {
2775         pe_resource_t *rsc = (pe_resource_t *) rsc_iter->data;
2776         int x;
2777 
2778         /* Complex resources may have some sub-resources active and some inactive */
2779         gboolean is_active = rsc->fns->active(rsc, TRUE);
2780         gboolean partially_active = rsc->fns->active(rsc, FALSE);
2781 
2782         /* Skip inactive orphans (deleted but still in CIB) */
2783         if (pcmk_is_set(rsc->flags, pe_rsc_orphan) && !is_active) {
2784             continue;
2785 
2786         /* Skip active resources if we already displayed them by node */
2787         } else if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2788             if (is_active) {
2789                 continue;
2790             }
2791 
2792         /* Skip primitives already counted in a brief summary */
2793         } else if (pcmk_is_set(show_opts, pcmk_show_brief) && (rsc->variant == pe_native)) {
2794             continue;
2795 
2796         /* Skip resources that aren't at least partially active,
2797          * unless we're displaying inactive resources
2798          */
2799         } else if (!partially_active && !pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
2800             continue;
2801 
2802         } else if (partially_active && !pe__rsc_running_on_any(rsc, only_node)) {
2803             continue;
2804         }
2805 
2806         if (!printed_header) {
2807             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
2808             print_resource_header(out, show_opts);
2809             printed_header = true;
2810         }
2811 
2812         /* Print this resource */
2813         x = out->message(out, crm_map_element_name(rsc->xml), show_opts, rsc,
2814                          only_node, only_rsc);
2815         if (x == pcmk_rc_ok) {
2816             rc = pcmk_rc_ok;
2817         }
2818     }
2819 
2820     if (print_summary && rc != pcmk_rc_ok) {
2821         if (!printed_header) {
2822             PCMK__OUTPUT_SPACER_IF(out, print_spacer);
2823             print_resource_header(out, show_opts);
2824             printed_header = true;
2825         }
2826 
2827         if (pcmk_is_set(show_opts, pcmk_show_rscs_by_node)) {
2828             out->list_item(out, NULL, "No inactive resources");
2829         } else if (pcmk_is_set(show_opts, pcmk_show_inactive_rscs)) {
2830             out->list_item(out, NULL, "No resources");
2831         } else {
2832             out->list_item(out, NULL, "No active resources");
2833         }
2834     }
2835 
2836     if (printed_header) {
2837         out->end_list(out);
2838     }
2839 
2840     return rc;
2841 }
2842 
2843 PCMK__OUTPUT_ARGS("resource-operation-list", "pe_working_set_t *", "pe_resource_t *",
     /* [previous][next][first][last][top][bottom][index][help] */
2844                   "pe_node_t *", "GList *", "uint32_t")
2845 static int
2846 resource_operation_list(pcmk__output_t *out, va_list args)
2847 {
2848     pe_working_set_t *data_set G_GNUC_UNUSED = va_arg(args, pe_working_set_t *);
2849     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2850     pe_node_t *node = va_arg(args, pe_node_t *);
2851     GList *op_list = va_arg(args, GList *);
2852     uint32_t show_opts = va_arg(args, uint32_t);
2853 
2854     GList *gIter = NULL;
2855     int rc = pcmk_rc_no_output;
2856 
2857     /* Print each operation */
2858     for (gIter = op_list; gIter != NULL; gIter = gIter->next) {
2859         xmlNode *xml_op = (xmlNode *) gIter->data;
2860         const char *task = crm_element_value(xml_op, XML_LRM_ATTR_TASK);
2861         const char *interval_ms_s = crm_element_value(xml_op,
2862                                                       XML_LRM_ATTR_INTERVAL_MS);
2863         const char *op_rc = crm_element_value(xml_op, XML_LRM_ATTR_RC);
2864         int op_rc_i;
2865 
2866         pcmk__scan_min_int(op_rc, &op_rc_i, 0);
2867 
2868         /* Display 0-interval monitors as "probe" */
2869         if (pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_casei)
2870             && pcmk__str_eq(interval_ms_s, "0", pcmk__str_null_matches | pcmk__str_casei)) {
2871             task = "probe";
2872         }
2873 
2874         /* If this is the first printed operation, print heading for resource */
2875         if (rc == pcmk_rc_no_output) {
2876             time_t last_failure = 0;
2877             int failcount = pe_get_failcount(node, rsc, &last_failure, pe_fc_default,
2878                                              NULL);
2879 
2880             out->message(out, "resource-history", rsc, rsc_printable_id(rsc), true,
2881                          failcount, last_failure, true);
2882             rc = pcmk_rc_ok;
2883         }
2884 
2885         /* Print the operation */
2886         out->message(out, "op-history", xml_op, task, interval_ms_s,
2887                      op_rc_i, show_opts);
2888     }
2889 
2890     /* Free the list we created (no need to free the individual items) */
2891     g_list_free(op_list);
2892 
2893     PCMK__OUTPUT_LIST_FOOTER(out, rc);
2894     return rc;
2895 }
2896 
2897 PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2898 static int
2899 resource_util(pcmk__output_t *out, va_list args)
2900 {
2901     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2902     pe_node_t *node = va_arg(args, pe_node_t *);
2903     const char *fn = va_arg(args, const char *);
2904 
2905     char *dump_text = crm_strdup_printf("%s: %s utilization on %s:",
2906                                         fn, rsc->id, pe__node_name(node));
2907 
2908     g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text);
2909     out->list_item(out, NULL, "%s", dump_text);
2910     free(dump_text);
2911 
2912     return pcmk_rc_ok;
2913 }
2914 
2915 PCMK__OUTPUT_ARGS("resource-util", "pe_resource_t *", "pe_node_t *", "const char *")
     /* [previous][next][first][last][top][bottom][index][help] */
2916 static int
2917 resource_util_xml(pcmk__output_t *out, va_list args)
2918 {
2919     pe_resource_t *rsc = va_arg(args, pe_resource_t *);
2920     pe_node_t *node = va_arg(args, pe_node_t *);
2921     const char *fn = va_arg(args, const char *);
2922 
2923     xmlNodePtr xml_node = pcmk__output_create_xml_node(out, "utilization",
2924                                                        "resource", rsc->id,
2925                                                        "node", node->details->uname,
2926                                                        "function", fn,
2927                                                        NULL);
2928     g_hash_table_foreach(rsc->utilization, add_dump_node, xml_node);
2929 
2930     return pcmk_rc_ok;
2931 }
2932 
2933 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
2934 static int
2935 ticket_html(pcmk__output_t *out, va_list args) {
2936     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
2937 
2938     if (ticket->last_granted > -1) {
2939         char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
2940 
2941         out->list_item(out, NULL, "%s:\t%s%s %s=\"%s\"", ticket->id,
2942                        ticket->granted ? "granted" : "revoked",
2943                        ticket->standby ? " [standby]" : "",
2944                        "last-granted", pcmk__s(epoch_str, ""));
2945         free(epoch_str);
2946     } else {
2947         out->list_item(out, NULL, "%s:\t%s%s", ticket->id,
2948                        ticket->granted ? "granted" : "revoked",
2949                        ticket->standby ? " [standby]" : "");
2950     }
2951 
2952     return pcmk_rc_ok;
2953 }
2954 
2955 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
2956 static int
2957 ticket_text(pcmk__output_t *out, va_list args) {
2958     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
2959 
2960     if (ticket->last_granted > -1) {
2961         char *epoch_str = pcmk__epoch2str(&(ticket->last_granted), 0);
2962 
2963         out->list_item(out, ticket->id, "%s%s %s=\"%s\"",
2964                        ticket->granted ? "granted" : "revoked",
2965                        ticket->standby ? " [standby]" : "",
2966                        "last-granted", pcmk__s(epoch_str, ""));
2967         free(epoch_str);
2968     } else {
2969         out->list_item(out, ticket->id, "%s%s",
2970                        ticket->granted ? "granted" : "revoked",
2971                        ticket->standby ? " [standby]" : "");
2972     }
2973 
2974     return pcmk_rc_ok;
2975 }
2976 
2977 PCMK__OUTPUT_ARGS("ticket", "pe_ticket_t *")
     /* [previous][next][first][last][top][bottom][index][help] */
2978 static int
2979 ticket_xml(pcmk__output_t *out, va_list args) {
2980     pe_ticket_t *ticket = va_arg(args, pe_ticket_t *);
2981 
2982     xmlNodePtr node = NULL;
2983 
2984     node = pcmk__output_create_xml_node(out, "ticket",
2985                                         "id", ticket->id,
2986                                         "status", ticket->granted ? "granted" : "revoked",
2987                                         "standby", pcmk__btoa(ticket->standby),
2988                                         NULL);
2989 
2990     if (ticket->last_granted > -1) {
2991         char *buf = pcmk__epoch2str(&ticket->last_granted, 0);
2992 
2993         crm_xml_add(node, "last-granted", buf);
2994         free(buf);
2995     }
2996 
2997     return pcmk_rc_ok;
2998 }
2999 
3000 PCMK__OUTPUT_ARGS("ticket-list", "pe_working_set_t *", "bool")
     /* [previous][next][first][last][top][bottom][index][help] */
3001 static int
3002 ticket_list(pcmk__output_t *out, va_list args) {
3003     pe_working_set_t *data_set = va_arg(args, pe_working_set_t *);
3004     bool print_spacer = va_arg(args, int);
3005 
3006     GHashTableIter iter;
3007     gpointer key, value;
3008 
3009     if (g_hash_table_size(data_set->tickets) == 0) {
3010         return pcmk_rc_no_output;
3011     }
3012 
3013     PCMK__OUTPUT_SPACER_IF(out, print_spacer);
3014 
3015     /* Print section heading */
3016     out->begin_list(out, NULL, NULL, "Tickets");
3017 
3018     /* Print each ticket */
3019     g_hash_table_iter_init(&iter, data_set->tickets);
3020     while (g_hash_table_iter_next(&iter, &key, &value)) {
3021         pe_ticket_t *ticket = (pe_ticket_t *) value;
3022         out->message(out, "ticket", ticket);
3023     }
3024 
3025     /* Close section */
3026     out->end_list(out);
3027     return pcmk_rc_ok;
3028 }
3029 
3030 static pcmk__message_entry_t fmt_functions[] = {
3031     { "ban", "default", ban_text },
3032     { "ban", "html", ban_html },
3033     { "ban", "xml", ban_xml },
3034     { "ban-list", "default", ban_list },
3035     { "bundle", "default", pe__bundle_text },
3036     { "bundle", "xml",  pe__bundle_xml },
3037     { "bundle", "html",  pe__bundle_html },
3038     { "clone", "default", pe__clone_default },
3039     { "clone", "xml",  pe__clone_xml },
3040     { "cluster-counts", "default", cluster_counts_text },
3041     { "cluster-counts", "html", cluster_counts_html },
3042     { "cluster-counts", "xml", cluster_counts_xml },
3043     { "cluster-dc", "default", cluster_dc_text },
3044     { "cluster-dc", "html", cluster_dc_html },
3045     { "cluster-dc", "xml", cluster_dc_xml },
3046     { "cluster-options", "default", cluster_options_text },
3047     { "cluster-options", "html", cluster_options_html },
3048     { "cluster-options", "log", cluster_options_log },
3049     { "cluster-options", "xml", cluster_options_xml },
3050     { "cluster-summary", "default", cluster_summary },
3051     { "cluster-summary", "html", cluster_summary_html },
3052     { "cluster-stack", "default", cluster_stack_text },
3053     { "cluster-stack", "html", cluster_stack_html },
3054     { "cluster-stack", "xml", cluster_stack_xml },
3055     { "cluster-times", "default", cluster_times_text },
3056     { "cluster-times", "html", cluster_times_html },
3057     { "cluster-times", "xml", cluster_times_xml },
3058     { "failed-action", "default", failed_action_default },
3059     { "failed-action", "xml", failed_action_xml },
3060     { "failed-action-list", "default", failed_action_list },
3061     { "group", "default",  pe__group_default},
3062     { "group", "xml",  pe__group_xml },
3063     { "maint-mode", "text", cluster_maint_mode_text },
3064     { "node", "default", node_text },
3065     { "node", "html", node_html },
3066     { "node", "xml", node_xml },
3067     { "node-and-op", "default", node_and_op },
3068     { "node-and-op", "xml", node_and_op_xml },
3069     { "node-capacity", "default", node_capacity },
3070     { "node-capacity", "xml", node_capacity_xml },
3071     { "node-history-list", "default", node_history_list },
3072     { "node-list", "default", node_list_text },
3073     { "node-list", "html", node_list_html },
3074     { "node-list", "xml", node_list_xml },
3075     { "node-weight", "default", node_weight },
3076     { "node-weight", "xml", node_weight_xml },
3077     { "node-attribute", "default", node_attribute_text },
3078     { "node-attribute", "html", node_attribute_html },
3079     { "node-attribute", "xml", node_attribute_xml },
3080     { "node-attribute-list", "default", node_attribute_list },
3081     { "node-summary", "default", node_summary },
3082     { "op-history", "default", op_history_text },
3083     { "op-history", "xml", op_history_xml },
3084     { "primitive", "default",  pe__resource_text },
3085     { "primitive", "xml",  pe__resource_xml },
3086     { "primitive", "html",  pe__resource_html },
3087     { "promotion-score", "default", promotion_score },
3088     { "promotion-score", "xml", promotion_score_xml },
3089     { "resource-config", "default", resource_config },
3090     { "resource-config", "text", resource_config_text },
3091     { "resource-history", "default", resource_history_text },
3092     { "resource-history", "xml", resource_history_xml },
3093     { "resource-list", "default", resource_list },
3094     { "resource-operation-list", "default", resource_operation_list },
3095     { "resource-util", "default", resource_util },
3096     { "resource-util", "xml", resource_util_xml },
3097     { "ticket", "default", ticket_text },
3098     { "ticket", "html", ticket_html },
3099     { "ticket", "xml", ticket_xml },
3100     { "ticket-list", "default", ticket_list },
3101 
3102     { NULL, NULL, NULL }
3103 };
3104 
3105 void
3106 pe__register_messages(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
3107     pcmk__register_messages(out, fmt_functions);
3108 }

/* [previous][next][first][last][top][bottom][index][help] */