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

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