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. no_quorum_policy_text
  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. PCMK__OUTPUT_ARGS
  42. failed_action_friendly
  43. failed_action_technical
  44. PCMK__OUTPUT_ARGS
  45. PCMK__OUTPUT_ARGS
  46. PCMK__OUTPUT_ARGS
  47. status_node
  48. PCMK__OUTPUT_ARGS
  49. node_text_status
  50. PCMK__OUTPUT_ARGS
  51. health_text
  52. node_variant_text
  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. PCMK__OUTPUT_ARGS
  75. PCMK__OUTPUT_ARGS
  76. PCMK__OUTPUT_ARGS
  77. print_resource_header
  78. PCMK__OUTPUT_ARGS
  79. PCMK__OUTPUT_ARGS
  80. PCMK__OUTPUT_ARGS
  81. PCMK__OUTPUT_ARGS
  82. ticket_status
  83. ticket_standby_text
  84. PCMK__OUTPUT_ARGS
  85. PCMK__OUTPUT_ARGS
  86. PCMK__OUTPUT_ARGS
  87. pe__register_messages

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

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