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

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