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